Тестирование модулей
Тестирование базовой 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или вызывает исключение. ВозвращаетBrokenResultв случае успеха, илиErrorResultв случае, если выражение вычисляется до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/