Spec-Zone.ru › Julia 1.10

Тестирование модулей

Тестирование базовой Julia

Julia активно развивается и имеет обширную тестовую базу для проверки функциональности на нескольких платформах. Если вы собираете Julia из исходного кода, вы можете запустить этот набор тестов с помощью make test. В случае бинарной установки, вы можете запустить тестовый набор с помощью Base.runtests().

Base.runtestsФункция

Base.runtests(tests=["all"]; ncores=ceil(Int, Sys.CPU_THREADS / 2),
              exit_on_error=false, revise=false, [seed])

Запустите юнит-тесты Julia, перечисленные в tests, которые могут быть строкой или массивом строк, используя ncores процессоры. Если exit_on_error равно false, при неудаче одного теста, все оставшиеся тесты в других файлах всё равно будут выполнены; в противном случае они будут отброшены, когда exit_on_error == true. Если revise равно true, пакет Revise используется для загрузки любых изменений в Base или в стандартные библиотеки перед запуском тестов. Если seed (начальное значение) предоставляется через ключевой аргумент, оно используется для инициализации глобального генератора случайных чисел в контексте, где выполняются тесты; в противном случае seed выбирается случайным образом.

исходный код

Базовые юнит-тесты

Модуль Test предоставляет простую функциональность юнит-тестирования. Юнит-тестирование — это способ проверки правильности вашего кода путём проверки того, соответствуют ли результаты ожидаемым значениям. Это может быть полезно для обеспечения работоспособности кода после внесения изменений и может использоваться при разработке для указания поведения, которое ваш код должен иметь при завершении. Вы также можете обратиться к документации по добавлению тестов в ваш пакет Julia.

Простые юнит-тесты могут быть выполнены с помощью макросов @test и @test_throws.

Test.@testМакрос

@test ex
@test f(args...) key=val ...
@test ex broken=true
@test ex skip=true

Проверить, что выражение ex вычисляется до true. Если выполняется внутри @testset, возвращает Pass Result, если это так, Fail Result, если это false, и Error Result, если вычисление невозможно. Если выполняется вне @testset, вместо возврата Fail или Error генерируется исключение.

Примеры

julia> @test true
Test Passed

julia> @test [1, 2] + [2, 1] == [3, 3]
Test Passed

Форма @test f(args...) key=val... эквивалентна записи @test f(args..., key=val...), что может быть полезно, когда выражение представляет собой вызов с использованием инфиксной синтаксической конструкции, такой как приблизительные сравнения:

julia> @test π ≈ 3.14 atol=0.01
Test Passed

Это эквивалентно более громоздкому тесту @test ≈(π, 3.14, atol=0.01). Не допускается предоставление более одного выражения, если первое не является выражением вызова, а остальные — присваиваниями (k=v).

Вы можете использовать любой ключ для аргументов key=val, за исключением broken и skip, которые имеют специальное значение в контексте @test:

  • broken=cond указывает на тест, который должен пройти, но в настоящее время постоянно терпит неудачу, когда cond==true. Тестирует, что выражение ex вычисляется до false или вызывает исключение. Возвращает Broken Result в случае успеха, или Error Result в случае, если выражение вычисляется до true. Регулярное @test ex вычисляется, когда cond==false.
  • skip=cond отмечает тест, который не должен выполняться, но должен включаться в отчет о результатах тестирования как Broken, когда cond==true. Это может быть полезно для тестов, которые время от времени терпят неудачу, или тестов для функциональности, которая еще не реализована. Регулярное @test ex вычисляется, когда cond==false.

Примеры

julia> @test 2 + 2 ≈ 6 atol=1 broken=true
Test Broken
  Expression: ≈(2 + 2, 6, atol = 1)

julia> @test 2 + 2 ≈ 5 atol=1 broken=false
Test Passed

julia> @test 2 + 2 == 5 skip=true
Test Broken
  Skipped: 2 + 2 == 5

julia> @test 2 + 2 == 4 skip=false
Test Passed

Ключевые аргументы broken и skip требуют как минимум Julia 1.7.

исходный код

Test.@test_throwsМакрос

@test_throws exception expr

Проверяет, что выражение expr вызывает исключение exception. Исключение может указывать либо тип, либо строку, либо регулярное выражение, либо список строк, встречающихся в сообщении об ошибке, функцию сопоставления или значение (которое будет проверено на равенство путем сравнения полей). Обратите внимание, что @test_throws не поддерживает заключительную форму ключевого слова.

Возможность указывать что-либо, кроме типа или значения, как exception требует Julia v1.8 или более поздней версии.

Примеры

julia> @test_throws BoundsError [1, 2, 3][4]
Test Passed
      Thrown: BoundsError

julia> @test_throws DimensionMismatch [1, 2, 3] + [1, 2]
Test Passed
      Thrown: DimensionMismatch

julia> @test_throws "Try sqrt(Complex" sqrt(-1)
Test Passed
     Message: "DomainError with -1.0:\nsqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x))."

В последнем примере, вместо сопоставления одной строки, можно было бы использовать:

  • ["Try", "Complex"] (список строк)
  • r"Try sqrt\([Cc]omplex" (регулярное выражение)
  • str -> occursin("complex", str) (функция сопоставления)
исходный код

Например, предположим, что мы хотим проверить, что наша новая функция foo(x) работает как ожидается:

julia> using Test

julia> foo(x) = length(x)^2
foo (generic function with 1 method)

Если условие истинно, возвращается Pass:

julia> @test foo("bar") == 9
Test Passed

julia> @test foo("fizz") >= 10
Test Passed

Если условие ложно, возвращается Fail и генерируется исключение:

julia> @test foo("f") == 20
Test Failed at none:1
  Expression: foo("f") == 20
   Evaluated: 1 == 20

ERROR: There was an error during testing

Если условие невозможно оценить из-за возникновения исключения, что происходит в данном случае, потому что length не определено для символов, возвращается объект Error и генерируется исключение:

julia> @test foo(:cat) == 1
Error During Test
  Test threw an exception of type MethodError
  Expression: foo(:cat) == 1
  MethodError: no method matching length(::Symbol)
  Closest candidates are:
    length(::SimpleVector) at essentials.jl:256
    length(::Base.MethodList) at reflection.jl:521
    length(::MethodTable) at reflection.jl:597
    ...
  Stacktrace:
  [...]
ERROR: There was an error during testing

Если мы ожидаем, что вычисление выражения должно вызвать исключение, мы можем использовать @test_throws для проверки этого:

julia> @test_throws MethodError foo(:cat)
Test Passed
      Thrown: MethodError

Работа с наборами тестов

Обычно используется большое количество тестов, чтобы убедиться, что функции работают правильно на широком диапазоне входных данных. В случае неудачи теста, стандартное поведение — немедленно сгенерировать исключение. Однако, обычно предпочтительнее сначала запустить остальные тесты, чтобы получить более полное представление о количестве ошибок в тестируемом коде.

@testset создаст локальную область видимости при запуске тестов внутри неё.

Макрос @testset может использоваться для группирования тестов в наборы. Все тесты в наборе будут выполнены, и в конце набора будет напечатан сводный отчет. Если какие-либо тесты завершились неудачно или не смогли быть выполнены из-за ошибки, набор тестов генерирует TestSetException.

Test.@testsetМакрос

@testset [CustomTestSet] [options...] ["description"] begin test_ex end
@testset [CustomTestSet] [options...] ["description $v"] for v in itr test_ex end
@testset [CustomTestSet] [options...] ["description $v, $w"] for v in itrv, w in itrw test_ex end
@testset [CustomTestSet] [options...] ["description"] test_func()
@testset let v = v, w = w; test_ex; end

С началом/концом или вызовом функции

При использовании @testset, с begin/end или одиночным вызовом функции, макрос начинает новый набор тестов, в котором нужно оценить заданное выражение.

Если тип пользовательского набора тестов не указан, по умолчанию создается DefaultTestSet. DefaultTestSet записывает все результаты и, если есть какие-либо Fail или Error , генерирует исключение в конце набора тестов верхнего уровня (не вложенных), вместе с резюме результатов тестов.

Любой пользовательский тип набора тестов (подтип AbstractTestSet) может быть задан, и он также будет использоваться для всех вложенных вызовов @testset. Заданные параметры применяются только к набору тестов, где они заданы. Тип набора тестов по умолчанию принимает три логических параметра:

  • verbose: если true, то резюме результатов вложенных наборов тестов отображается даже при их успешном выполнении (по умолчанию false).
  • showtiming: если true, то длительность отображения каждого набора тестов отображается (по умолчанию true).
  • failfast: если true, любое тестовое завершение неудачей или ошибка заставят набор тестов и любые дочерние наборы тестов возвратиться немедленно (по умолчанию false). Это также можно установить глобально через переменную среды JULIA_TEST_FAILFAST.

@testset test_func() требует как минимум Julia 1.8.

failfast требует как минимум Julia 1.9.

Строка описания принимает интерполяцию из индексов цикла. Если описание не предоставлено, оно создается на основе переменных. Если предоставлен вызов функции, его имя будет использоваться. Явные строки описания переопределяют это поведение.

По умолчанию макрос @testset возвращает сам объект набора тестов, хотя это поведение можно настроить в других типах наборов тестов. Если используется цикл for, макрос собирает и возвращает список возвращаемых значений метода finish, который по умолчанию возвращает список объектов наборов тестов, используемых в каждом цикле.

Перед выполнением тела @testset, неявно вызывается Random.seed!(seed), где seed - текущее значение генератора случайных чисел глобального RNG. Кроме того, после выполнения тела состояние глобального RNG восстанавливается до состояния до вызова @testset. Это призвано облегчить воспроизводимость в случае сбоя и позволить беспрепятственную перестановку @testset независимо от их влияния на состояние глобального RNG.

Примеры

julia> @testset "trigonometric identities" begin
           θ = 2/3*π
           @test sin(-θ) ≈ -sin(θ)
           @test cos(-θ) ≈ cos(θ)
           @test sin(2θ) ≈ 2*sin(θ)*cos(θ)
           @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2
       end;
Test Summary:            | Pass  Total  Time
trigonometric identities |    4      4  0.2s

@testset for

При использовании @testset for, макрос запускает новый тест для каждого цикла предоставленного цикла. Семантика каждого набора тестов в остальном идентична случаю begin/end (как если бы он использовался для каждого цикла).

@testset let

При использовании @testset let, макрос запускает прозрачный набор тестов с добавлением заданного объекта в качестве контекстного объекта к любому не пройденному тесту, содержащемуся в нём. Это полезно при выполнении набора связанных тестов на одном большом объекте, и желательно напечатать этот большой объект, когда любой из отдельных тестов завершится неудачей. Прозрачные наборы тестов не добавляют дополнительных уровней вложенности в иерархии наборов тестов и передаются напрямую родительскому набору тестов (с добавленным контекстным объектом к любым не пройденным тестам).

@testset let требует как минимум Julia 1.9.

Несколько let присвоений поддерживаются начиная с Julia 1.10.

Примеры

julia> @testset let logi = log(im)
           @test imag(logi) == π/2
           @test !iszero(real(logi))
       end
Test Failed at none:3
  Expression: !(iszero(real(logi)))
     Context: logi = 0.0 + 1.5707963267948966im

ERROR: There was an error during testing

julia> @testset let logi = log(im), op = !iszero
           @test imag(logi) == π/2
           @test op(real(logi))
       end
Test Failed at none:3
  Expression: op(real(logi))
     Context: logi = 0.0 + 1.5707963267948966im
              op = !iszero

ERROR: There was an error during testing
источник

Test.TestSetExceptionТип

TestSetException

Выбрасывается, когда набор тестов завершается, и не все тесты пройдены.

источник

Мы можем поместить наши тесты для функции foo(x) в набор тестов:

julia> @testset "Foo Tests" begin
           @test foo("a")   == 1
           @test foo("ab")  == 4
           @test foo("abc") == 9
       end;
Test Summary: | Pass  Total  Time
Foo Tests     |    3      3  0.0s

Наборы тестов также могут быть вложены:

julia> @testset "Foo Tests" begin
           @testset "Animals" begin
               @test foo("cat") == 9
               @test foo("dog") == foo("cat")
           end
           @testset "Arrays $i" for i in 1:3
               @test foo(zeros(i)) == i^2
               @test foo(fill(1.0, i)) == i^2
           end
       end;
Test Summary: | Pass  Total  Time
Foo Tests     |    8      8  0.0s

А также вызывать функции:

julia> f(x) = @test isone(x)
f (generic function with 1 method)

julia> @testset f(1);
Test Summary: | Pass  Total  Time
f             |    1      1  0.0s

Это можно использовать для факторизации наборов тестов, что облегчает запуск отдельных наборов тестов путём запуска соответствующих функций вместо этого. Обратите внимание, что в случае функций набор тестов будет обозначен именем вызываемой функции. В случае, если вложенный набор тестов не имеет ошибок, как произошло здесь, он будет скрыт в сводке, если не передан параметр verbose=true:

julia> @testset verbose = true "Foo Tests" begin
           @testset "Animals" begin
               @test foo("cat") == 9
               @test foo("dog") == foo("cat")
           end
           @testset "Arrays $i" for i in 1:3
               @test foo(zeros(i)) == i^2
               @test foo(fill(1.0, i)) == i^2
           end
       end;
Test Summary: | Pass  Total  Time
Foo Tests     |    8      8  0.0s
  Animals     |    2      2  0.0s
  Arrays 1    |    2      2  0.0s
  Arrays 2    |    2      2  0.0s
  Arrays 3    |    2      2  0.0s

Если у нас есть сбой теста, будут показаны только подробности о не пройденных наборах тестов:

julia> @testset "Foo Tests" begin
           @testset "Animals" begin
               @testset "Felines" begin
                   @test foo("cat") == 9
               end
               @testset "Canines" begin
                   @test foo("dog") == 9
               end
           end
           @testset "Arrays" begin
               @test foo(zeros(2)) == 4
               @test foo(fill(1.0, 4)) == 15
           end
       end

Arrays: Test Failed
  Expression: foo(fill(1.0, 4)) == 15
   Evaluated: 16 == 15
[...]
Test Summary: | Pass  Fail  Total  Time
Foo Tests     |    3     1      4  0.0s
  Animals     |    2            2  0.0s
  Arrays      |    1     1      2  0.0s
ERROR: Some tests did not pass: 3 passed, 1 failed, 0 errored, 0 broken.

Тестирование заявлений журналов

Можно использовать макрос @test_logs для тестирования заявлений журналов или использовать TestLogger.

Test.@test_logsМакрос

@test_logs [log_patterns...] [keywords] expression

Собирает список записей журналов, сгенерированных с помощью expression с использованием collect_test_logs, проверяет, что они соответствуют последовательности log_patterns, и возвращает значение expression. keywords предоставляют некоторую простую фильтрацию записей журналов: ключевое слово min_level управляет минимальным уровнем журнала, который будет собран для теста, ключевое слово match_mode определяет, как будет выполняться соответствие (по умолчанию :all проверяет, что все журналы и шаблоны соответствуют попарно; используйте :any чтобы проверить, что шаблон соответствует хотя бы один раз где-то в последовательности).

Наиболее полезный шаблон журнала - это простой кортеж вида (level,message). Разное количество элементов кортежа может быть использовано для соответствия другой метаданным журнала, соответствующим аргументам, переданным в AbstractLogger через функцию handle_message: (level,message,module,group,id,file,line). Присутствующие элементы будут сопоставлены попарно с полями записей журналов с помощью == по умолчанию, при этом специальные случаи, что Symbol могут быть использованы для стандартных уровней журналов, а Regex в шаблоне будут соответствовать строковым или символьным полям с помощью occursin.

Примеры

Рассмотрим функцию, которая записывает предупреждение и несколько сообщений отладки:

function foo(n)
    @info "Doing foo with n=$n"
    for i=1:n
        @debug "Iteration $i"
    end
    42
end

Мы можем проверить сообщение info с помощью

@test_logs (:info,"Doing foo with n=2") foo(2)

Если мы также хотели проверить сообщения отладки, их нужно включить с помощью ключевого слова min_level:

using Logging
@test_logs (:info,"Doing foo with n=2") (:debug,"Iteration 1") (:debug,"Iteration 2") min_level=Logging.Debug foo(2)

Если вы хотите проверить, что генерируются некоторые определённые сообщения, игнорируя остальные, вы можете установить ключевое слово match_mode=:any:

using Logging
@test_logs (:info,) (:debug,"Iteration 42") min_level=Logging.Debug match_mode=:any foo(100)

Макрос можно объединить с @test , чтобы также проверить возвращаемое значение:

@test (@test_logs (:info,"Doing foo with n=2") foo(2)) == 42

Если вы хотите проверить отсутствие предупреждений, вы можете опустить указание шаблонов журналов и соответствующим образом установить min_level:

# test that the expression logs no messages when the logger level is warn:
@test_logs min_level=Logging.Warn @info("Some information") # passes
@test_logs min_level=Logging.Warn @warn("Some information") # fails

Если вы хотите проверить отсутствие предупреждений (или сообщений об ошибках) в stderr, которые не генерируются @warn, см. @test_nowarn.

источник

Test.TestLoggerТип

TestLogger(; min_level=Info, catch_exceptions=false)

Создает TestLogger, который собирает зарегистрированные сообщения в своём поле logs::Vector{LogRecord}.

Установите min_level для управления LogLevel, catch_exceptions для того, чтобы исключения, брошенные как часть генерации событий журнала, обрабатывались, и respect_maxlog для того, чтобы следовать соглашению о регистрации сообщений с maxlog=n для некоторого целого n не более n раз.

См. также: LogRecord.

Пример

julia> using Test, Logging

julia> f() = @info "Hi" number=5;

julia> test_logger = TestLogger();

julia> with_logger(test_logger) do
           f()
           @info "Bye!"
       end

julia> @test test_logger.logs[1].message == "Hi"
Test Passed

julia> @test test_logger.logs[1].kwargs[:number] == 5
Test Passed

julia> @test test_logger.logs[2].message == "Bye!"
Test Passed
источник

Test.LogRecordТип

LogRecord

Хранит результаты одного события журнала. Поля:

  • level: LogLevel сообщения журнала
  • message: текстовое содержимое сообщения журнала
  • _module: модуль события журнала
  • group: группа регистрации (по умолчанию, имя файла, содержащего событие журнала)
  • id: идентификатор события журнала
  • file: файл, содержащий событие журнала
  • line: строка в файле события журнала
  • kwargs: любые ключевые аргументы, переданные в событие журнала
источник

Другие макросы тестов

Поскольку вычисления с плавающей запятой могут быть неточными, вы можете выполнить проверки приблизительного равенства, используя либо @test a ≈ b (где ≈, введённый с помощью автодополнения \approx, является функцией isapprox) или напрямую использовать isapprox.

julia> @test 1 ≈ 0.999999999
Test Passed

julia> @test 1 ≈ 0.999999
Test Failed at none:1
  Expression: 1 ≈ 0.999999
   Evaluated: 1 ≈ 0.999999

ERROR: There was an error during testing

Вы можете указать относительные и абсолютные погрешности, установив ключевые аргументы rtol и atol макроса isapprox соответственно, после сравнения ≈:

julia> @test 1 ≈ 0.999999  rtol=1e-5
Test Passed

Обратите внимание, что это не специальная функция ≈, а скорее общая функция макроса @test: @test a <op> b key=val преобразуется макросом в @test op(a, b, key=val). Однако это особенно полезно для ≈ тестов.

Test.@inferredМакрос

@inferred [AllowedType] f(x)

Проверяет, что выражение вызова f(x) возвращает значение того же типа, что и тип, определённый компилятором. Это полезно для проверки стабильности типов.

f(x) может быть любым выражением вызова. Возвращает результат f(x), если типы совпадают, и Error Result, если обнаруживаются разные типы.

Необязательно, AllowedType ослабляет проверку, делая её успешной, если тип f(x) совпадает с выведенным типом по модулю AllowedType, или если возвращаемый тип является подтипом AllowedType. Это полезно для проверки стабильности типов функций, возвращающих небольшое объединение, например, Union{Nothing, T} или Union{Missing, T}.

julia> f(a) = a > 1 ? 1 : 1.0
f (generic function with 1 method)

julia> typeof(f(2))
Int64

julia> @code_warntype f(2)
MethodInstance for f(::Int64)
  from f(a) @ Main none:1
Arguments
  #self#::Core.Const(f)
  a::Int64
Body::UNION{FLOAT64, INT64}
1 ─ %1 = (a > 1)::Bool
└──      goto #3 if not %1
2 ─      return 1
3 ─      return 1.0

julia> @inferred f(2)
ERROR: return type Int64 does not match inferred return type Union{Float64, Int64}
[...]

julia> @inferred max(1, 2)
2

julia> g(a) = a < 10 ? missing : 1.0
g (generic function with 1 method)

julia> @inferred g(20)
ERROR: return type Float64 does not match inferred return type Union{Missing, Float64}
[...]

julia> @inferred Missing g(20)
1.0

julia> h(a) = a < 10 ? missing : f(a)
h (generic function with 1 method)

julia> @inferred Missing h(20)
ERROR: return type Int64 does not match inferred return type Union{Missing, Float64, Int64}
[...]
исходный код

Test.@test_deprecatedМакрос

@test_deprecated [pattern] expression

Если --depwarn=yes, проверяет, что expression генерирует предупреждение о устаревании и возвращает значение expression. Строка сообщения журнала будет сопоставлена с pattern, по умолчанию r"deprecated"i.

Если --depwarn=no, просто возвращает результат выполнения expression. Если --depwarn=error, проверяет, что возникает исключение ErrorException.

Примеры

# Deprecated in julia 0.7
@test_deprecated num2hex(1)

# The returned value can be tested by chaining with @test:
@test (@test_deprecated num2hex(1)) == "0000000000000001"
исходный код

Test.@test_warnМакрос

@test_warn msg expr

Проверяет, приводит ли вычисление expr к выводу stderr, содержащему строку msg или соответствует ли регулярному выражению msg. Если msg — это булева функция, проверяет, возвращает ли msg(output) значение true. Если msg — это кортеж или массив, проверяет, содержит ли вывод об ошибках каждый элемент в msg. Возвращает результат вычисления expr.

См. также @test_nowarn для проверки отсутствия вывода об ошибках.

Примечание: Предупреждения, сгенерированные @warn, не могут быть проверены с помощью этого макроса. Используйте @test_logs вместо этого.

исходный код

Test.@test_nowarnМакрос

@test_nowarn expr

Проверяет, приводит ли вычисление expr к пустому выводу stderr (без предупреждений или других сообщений). Возвращает результат вычисления expr.

Примечание: Отсутствие предупреждений, сгенерированных @warn, не может быть проверено с помощью этого макроса. Используйте @test_logs вместо этого.

исходный код

Сломанные тесты

Если тест постоянно завершается ошибкой, его можно изменить, используя макрос @test_broken. Это обозначит тест как Broken, если тест по-прежнему завершается ошибкой, и предупредит пользователя с помощью Error при успешном выполнении теста.

Test.@test_brokenМакрос

@test_broken ex
@test_broken f(args...) key=val ...

Указывает на тест, который должен пройти, но в настоящее время постоянно завершается ошибкой. Проверяет, что выражение ex вычисляется в false или вызывает исключение. Возвращает Broken Result в случае успеха или Error Result в случае, если выражение вычисляется в true. Это эквивалентно @test ex broken=true.

Форма @test_broken f(args...) key=val... работает так же, как для макроса @test.

Примеры

julia> @test_broken 1 == 2
Test Broken
  Expression: 1 == 2

julia> @test_broken 1 == 2 atol=0.1
Test Broken
  Expression: ==(1, 2, atol = 0.1)
исходный код

@test_skip также доступен для пропуска теста без вычисления, но учитывая пропущенный тест в отчете о наборе тестов. Тест не будет выполнен, но предоставит Broken Result.

Test.@test_skipМакрос

@test_skip ex
@test_skip f(args...) key=val ...

Помечает тест, который не должен выполняться, но должен быть включён в отчёт о наборе тестов как Broken. Это может быть полезно для тестов, которые непредсказуемо завершаются ошибкой, или для тестов ещё не реализованных функций. Это эквивалентно @test ex skip=true.

Форма @test_skip f(args...) key=val... работает так же, как для макроса @test.

Примеры

julia> @test_skip 1 == 2
Test Broken
  Skipped: 1 == 2

julia> @test_skip 1 == 2 atol=0.1
Test Broken
  Skipped: ==(1, 2, atol = 0.1)
исходный код

Типы результатов тестов

Test.ResultТип

Test.Result

Все тесты производят объект результата. Этот объект может быть или не быть сохранён, в зависимости от того, является ли тест частью набора тестов.

исходный код

Test.PassТип

Test.Pass <: Test.Result

Условие теста было истинным, т.е. выражение вычислялось в true или было выброшено правильное исключение.

исходный код

Test.FailТип

Test.Fail <: Test.Result

Условие теста было ложным, т.е. выражение вычислялось в false или не было выброшено правильное исключение.

исходный код

Test.ErrorТип

Test.Error <: Test.Result

Условие теста не удалось вычислить из-за исключения или оно вычислилось в значение, отличное от Bool. В случае @test_broken оно используется для указания того, что произошла непредвиденная Pass Result.

исходный код

Test.BrokenТип

Test.Broken <: Test.Result

Условие теста является ожидаемым (неуспешным) результатом сломанного теста или было явно пропущено с помощью @test_skip.

исходный код

Создание настраиваемых типов AbstractTestSet

Пакеты могут создавать свои собственные подтипы AbstractTestSet путём реализации методов record и finish. Подтип должен иметь конструктор с одним аргументом, принимающим строку описания, а все параметры передавать в качестве ключевых аргументов.

Test.recordФункция

record(ts::AbstractTestSet, res::Result)

Записывает результат в набор тестов. Эта функция вызывается инфраструктурой @testset каждый раз, когда завершается содержащий макрос @test, и получает результат теста (который может быть Error). Также она будет вызвана с Error при возникновении исключения внутри блока теста, но вне контекста @test.

исходный код

Test.finishФункция

finish(ts::AbstractTestSet)

Выполняет все необходимые действия по окончательной обработке для данного набора тестов. Это вызывается инфраструктурой @testset после выполнения блока теста.

Пользовательские подтипы AbstractTestSet должны вызывать record у своего родителя (если он есть), чтобы добавить себя в дерево результатов тестов. Это может быть реализовано как:

if get_testset_depth() != 0
    # Attach this test set to the parent test set
    parent_ts = get_testset()
    record(parent_ts, self)
    return self
end
исходный код

Test несёт ответственность за поддержание стека вложенных наборов тестов по мере их выполнения, но любая аккумулируемая информация — это ответственность подтипа AbstractTestSet. Вы можете получить доступ к этому стеку с помощью методов get_testset и get_testset_depth. Обратите внимание, что эти функции не экспортируются.

Test.get_testsetФункция

get_testset()

Получить активный набор тестов из локального хранилища задачи. Если активный набор тестов отсутствует, использовать резервный набор по умолчанию.

исходный код

Test.get_testset_depthФункция

get_testset_depth()

Возвращает количество активных наборов тестов, не включая набор тестов по умолчанию.

исходный код

Test также гарантирует, что вложенные @testset вызовы используют тот же AbstractTestSet подтип, что и их родитель, если он не задан явно. Он не передает никаких свойств набора тестов. Поведение наследования опций может быть реализовано пакетами, использующими инфраструктуру стека, которую Test предоставляет.

Определение базового AbstractTestSet подтипа может выглядеть так:

import Test: Test, record, finish
using Test: AbstractTestSet, Result, Pass, Fail, Error
using Test: get_testset_depth, get_testset
struct CustomTestSet <: Test.AbstractTestSet
    description::AbstractString
    foo::Int
    results::Vector
    # constructor takes a description string and options keyword arguments
    CustomTestSet(desc; foo=1) = new(desc, foo, [])
end

record(ts::CustomTestSet, child::AbstractTestSet) = push!(ts.results, child)
record(ts::CustomTestSet, res::Result) = push!(ts.results, res)
function finish(ts::CustomTestSet)
    # just record if we're not the top-level parent
    if get_testset_depth() > 0
        record(get_testset(), ts)
    end
    ts
end

Использование этого набора тестов выглядит так:

@testset CustomTestSet foo=4 "custom testset inner 2" begin
    # this testset should inherit the type, but not the argument.
    @testset "custom testset inner" begin
        @test true
    end
end

Утилиты тестирования

Test.GenericArrayТип

GenericArray может использоваться для тестирования API универсальных массивов, которые программируют на интерфейсе AbstractArray, чтобы гарантировать, что функции могут работать с типами массивов помимо стандартного типа Array.

исходный код

Test.GenericDictТип

GenericDict может использоваться для тестирования API универсальных словарей, которые программируют на интерфейсе AbstractDict, чтобы гарантировать, что функции могут работать с ассоциативными типами помимо стандартного типа Dict.

исходный код

Test.GenericOrderТип

GenericOrder может использоваться для тестирования API в отношении поддержки универсальных упорядоченных типов.

исходный код

Test.GenericSetТип

GenericSet может использоваться для тестирования API универсальных множеств, которые программируют на интерфейсе AbstractSet, чтобы гарантировать, что функции могут работать с типами множеств помимо стандартных типов Set и BitSet.

исходный код

Test.GenericStringТип

GenericString может использоваться для тестирования API универсальных строк, которые программируют на интерфейсе AbstractString, чтобы гарантировать, что функции могут работать с типами строк помимо стандартного типа String.

исходный код

Test.detect_ambiguitiesФункция

detect_ambiguities(mod1, mod2...; recursive=false,
                                  ambiguous_bottom=false,
                                  allowed_undefineds=nothing)

Возвращает вектор пар (Method,Method) неоднозначных методов, определенных в указанных модулях. Используйте recursive=true для проверки во всех подмодулях.

ambiguous_bottom управляет включением неоднозначностей, возникающих только из-за Union{} параметров типа; в большинстве случаев вы, вероятно, захотите установить это значение в false. См. Base.isambiguous.

См. Test.detect_unbound_args для объяснения allowed_undefineds.

allowed_undefineds требует как минимум Julia 1.8.

исходный код

Test.detect_unbound_argsФункция

detect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)

Возвращает вектор Method которые могут иметь неопределённые параметры типа. Используйте recursive=true для проверки во всех подмодулях.

По умолчанию любые неопределённые символы вызывают предупреждение. Это предупреждение можно подавить, предоставив коллекцию GlobalRef для которых предупреждение можно пропустить. Например,

allowed_undefineds = Set([GlobalRef(Base, :active_repl),
                          GlobalRef(Base, :active_repl_backend)])

подавало бы предупреждения о Base.active_repl и Base.active_repl_backend.

allowed_undefineds требует как минимум Julia 1.8.

исходный код

Порядок работы для тестирования пакетов

Используя инструменты, доступные в предыдущих разделах, вот возможный порядок работы по созданию пакета и добавлению тестов к нему.

Генерация примерного пакета

Для этого порядка работы мы создадим пакет под названием Example:

pkg> generate Example
shell> cd Example
shell> mkdir test
pkg> activate .

Создание образцовых функций

Основное требование для тестирования пакета — наличие функциональности для тестирования. Для этого мы добавим несколько простых функций в Example, которые можно протестировать. Добавьте следующее в src/Example.jl:

module Example

function greet()
    "Hello world!"
end

function simple_add(a, b)
    a + b
end

function type_multiply(a::Float64, b::Float64)
    a * b
end

end

Создание тестовой среды

Из корня пакета Example перейдите в каталог test, активируйте новую среду там и добавьте пакет Test в среду:

shell> cd test
pkg> activate .
(test) pkg> add Test

Тестирование нашего пакета

Теперь мы готовы добавить тесты в Example. Стандартная практика — создание файла в каталоге test под названием runtests.jl, содержащего наборы тестов, которые мы хотим запустить. Создайте этот файл в каталоге test и добавьте в него следующий код:

using Example
using Test

@testset "Example tests" begin

    @testset "Math tests" begin
        include("math_tests.jl")
    end

    @testset "Greeting tests" begin
        include("greeting_tests.jl")
    end
end

Нам нужно создать эти два включённых файла, math_tests.jl и greeting_tests.jl, и добавить в них некоторые тесты.

Примечание: Обратите внимание, что нам не нужно было указывать добавление Example в среду test с Project.toml. Это преимущество системы тестирования Julia, о котором вы можете подробнее прочитать здесь.

Написание тестов для math_tests.jl

Используя наши знания о Test.jl, вот примеры тестов, которые мы можем добавить в math_tests.jl:

@testset "Testset 1" begin
    @test 2 == simple_add(1, 1)
    @test 3.5 == simple_add(1, 2.5)
        @test_throws MethodError simple_add(1, "A")
        @test_throws MethodError simple_add(1, 2, 3)
end

@testset "Testset 2" begin
    @test 1.0 == type_multiply(1.0, 1.0)
        @test isa(type_multiply(2.0, 2.0), Float64)
    @test_throws MethodError type_multiply(1, 2.5)
end

Написание тестов для greeting_tests.jl

Используя наши знания о Test.jl, вот примеры тестов, которые мы можем добавить в math_tests.jl:

@testset "Testset 3" begin
    @test "Hello world!" == greet()
    @test_throws MethodError greet("Antonia")
end

Тестирование нашего пакета

Теперь, когда мы добавили наши тесты и скрипт runtests.jl в test, мы можем протестировать наш пакет Example перейдя в корень среды пакета Example и активировав среду Example:

shell> cd ..
pkg> activate .

Оттуда мы можем, наконец, запустить наш набор тестов следующим образом:

(Example) pkg> test
     Testing Example
      Status `/tmp/jl_Yngpvy/Project.toml`
  [fa318bd2] Example v0.1.0 `/home/src/Projects/tmp/errata/Example`
  [8dfed614] Test `@stdlib/Test`
      Status `/tmp/jl_Yngpvy/Manifest.toml`
  [fa318bd2] Example v0.1.0 `/home/src/Projects/tmp/errata/Example`
  [2a0f44e3] Base64 `@stdlib/Base64`
  [b77e0a4c] InteractiveUtils `@stdlib/InteractiveUtils`
  [56ddb016] Logging `@stdlib/Logging`
  [d6f4376e] Markdown `@stdlib/Markdown`
  [9a3f8284] Random `@stdlib/Random`
  [ea8e919c] SHA `@stdlib/SHA`
  [9e88b42a] Serialization `@stdlib/Serialization`
  [8dfed614] Test `@stdlib/Test`
     Testing Running tests...
Test Summary: | Pass  Total
Example tests |    9      9
     Testing Example tests passed

И если всё прошло правильно, вы должны увидеть аналогичный вывод, как и выше. Используя Test.jl, можно добавить более сложные тесты для пакетов, но это должно направить разработчиков в направлении того, как начать тестирование собственных созданных пакетов.

© 2009–2024 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.10/stdlib/Test/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API