Модульное тестирование
Тестирование Base Julia
Julia находится в процессе быстрой разработки и располагает обширным набором тестов для проверки функциональности на различных платформах. Если вы выполняете сборку Julia из исходного кода, этот набор тестов можно запустить с помощью make test
. При бинарной установке набор тестов можно запустить с помощью Base.runtests()
.
#
Base.runtests
— Function
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
или стандартных библиотек перед запуском тестов. Если с помощью именованного аргумента указано начальное значение, оно используется для заполнения глобального генератора случайных чисел в контексте выполнения тестов. В противном случае значение выбирается случайным образом.
Базовые модульные тесты
Модуль Test
обеспечивает простую функциональность модульного тестирования. С помощью модульного тестирования можно убедиться в правильности кода, проверив соответствие результатов ожиданиям. Этот вид тестирования может быть полезен для обеспечения работоспособности кода после внесения в него изменений, а также использоваться при разработке как способ указания поведения, которым в итоге должен обладать код. Возможно, вам также будет интересно ознакомиться с документацией по добавлению тестов в пакет Julia.
Простейшее модульное тестирование можно выполнить с помощью макросов @test
и @test_throws
:
#
Test.@test
— Macro
@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
Совместимость: Julia 1.7
Для именованных аргументов |
#
Test.@test_throws
— Macro
@test_throws exception expr
Тестирует, что выражение (expr
) вызывает исключение (exception
). Исключение может указывать либо тип, строку, регулярное выражение, либо список строк, встречающихся в выводимом сообщении об ошибке, либо функцию соответствия, либо значение (которое будет проверяться на равенство путем сравнения полей). Обратите внимание, что @test_throws
не поддерживает форму с ключевым словом в конце.
Совместимость: Julia 1.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 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
можно использовать для группировки тестов в наборы. Будут выполнены все тесты из набора тестов, а в конце набора тестов будет выведена сводка. В случае сбоя какого-либо теста или невозможности выполнить оценку из-за ошибки набор тестов выдаст исключение TestSetException
.
#
Test.@testset
— Macro
@testset [CustomTestSet] [option=val ...] ["description"] begin ... end
@testset [CustomTestSet] [option=val ...] ["description $v"] for v in (...) ... end
@testset [CustomTestSet] [option=val ...] ["description $v, $w"] for v in (...), w in (...) ... end
@testset [CustomTestSet] [option=val ...] ["description"] foo()
@testset let v = (...) ... end
begin/end или вызов функции
При использовании @testset с begin/end или отдельным вызовом функции макрос запускает новый тестовый набор, в котором вычисляется указанное выражение.
Если тип пользовательского набора тестов не указан, по умолчанию создается набор DefaultTestSet
. DefaultTestSet
записывает все результаты и, если имеются сбои (Fail
) или ошибки (Error
), вызывает исключение в конце набора тестов верхнего уровня (не вложенного) и выводит сводку результатов теста.
Можно задать любой пользовательский тип набора тестов (подтип AbstractTestSet
), который также будет использоваться для всех вызовов вложенного макроса @testset
. Заданные параметры применяются только к тому набору тестов, в котором они заданы. Тип набора тестов по умолчанию принимает три логических параметра:
-
verbose
: еслиtrue
, отображается сводка результатов вложенных наборов тестов, даже если
все они пройдены (по умолчанию используется false
).
-
showtiming
: еслиtrue
, отображается предположительность каждого отображаемого набора тестов
(по умолчанию используется true
).
-
failfast
: еслиtrue
, любой сбой или ошибка теста приводит к немедленному возвращению управления
набором тестов и всеми его дочерними наборами тестов (по умолчанию false
). Можно также задать глобально с помощью переменной среды JULIA_TEST_FAILFAST
.
Совместимость: Julia 1.8
Для |
Совместимость: Julia 1.9
Для |
Строка описания принимает интерполяцию от индексов цикла. Если описание отсутствует, оно формируется на основе переменных. Если указан вызов функции, будет использоваться имя функции. Это поведение можно переопределить строками явного описания.
По умолчанию макрос @testset
возвратит сам объект набора тестов, хотя это поведение можно настроить в других типах набора тестов. Если используется цикл for
, макрос собирает и возвращает список возвращаемых значений метода finish
, который по умолчанию возвращает список объектов набора тестов, используемых в каждой итерации.
Перед выполнением тела @testset
происходит неявный вызов Random.seed!(seed)
, где seed
является текущим начальным значением глобального генератора случайных чисел. Более того, после выполнения тела состояние глобального 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
макрос запускает прозрачный набор тестов с добавлением указанного объекта в качестве объекта контекста для каждого непройденного теста в наборе. Это полезно при выполнении набора связанных тестов для одного большого объекта, если нужно, чтобы этот объект выводился при непрохождении каждого отдельного теста. Прозрачные наборы тестов не вводят дополнительные уровни вложения в иерархию наборов тестов и передаются напрямую в родительский набор тестов (с добавлением объекта контекста для каждого непройденного теста).
Совместимость: Julia 1.9
Для |
Примеры
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
#
Test.TestSetException
— Type
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
— Macro
@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
Информационное сообщение можно проверить с помощью
@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_logs min_level=Logging.Warn @info("Some information") # выполняется @test_logs min_level=Logging.Warn @warn("Some information") # завершается сбоем
Если требуется протестировать отсутствие предупреждений (или сообщений об ошибках) в stderr
, которые не генерируются макросом @warn
, см. описание @test_nowarn
.
#
Test.TestLogger
— Type
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
— Type
LogRecord
Хранит результаты одного события журнала. Поля:
-
level
:LogLevel
сообщения журнала -
message
: текстовое содержимое сообщения журнала -
_module
: модуль события журнала -
group
: группа ведения журнала (по умолчанию — имя файла, содержащего событие журнала) -
id
: ИД события журнала -
file
: файл, содержащий событие журнала -
line
: строка в файле или событии журнала -
kwargs
: любые именованные аргументы, переданные событию журнала
Другие макросы тестирования
Поскольку вычисления со значениями с плавающей запятой могут быть неточными, можно выполнить приближенную проверку равенства, используя либо @test a ≈ b
(где ≈
, введенный путем автозаполнения по нажатию клавиши TAB \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
— Macro
@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, если не %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
— Macro
@test_deprecated [pattern] expression
Когда --depwarn=yes
, тестирует, что выражение (expression
) выражение выдает предупреждение о выводе из использования и возвращает значение выражения (expression
). Сообщение журнала будет сопоставлено с шаблоном (pattern
), имеющим по умолчанию значение r"deprecated"i
.
Когда --depwarn=no
, просто возвращает результат выполнения выражения (expression
). Когда --depwarn=error
, проверяет, что возникает исключение ErrorException.
Примеры
# Устарело в julia 0.7 @test_deprecated num2hex(1) # Возвращаемое значение можно протестировать с помощью соединения с @test: @test (@test_deprecated num2hex(1)) == "0000000000000001"
#
Test.@test_warn
— Macro
@test_warn msg expr
Тестирует, возвращает ли вычисление выражения (expr
) выходные данные stderr
, содержащие строку msg
, или соответствует регулярному выражению msg
. Если msg
является логической функцией, тестирует, возвращает ли msg(output)
true
. Если msg
является кортежем или массивом, проверяет, что вывод ошибки содержит каждый элемент в msg
. Возвращает результат вычисления выражения (expr
).
См. также описание макроса @test_nowarn
для проверки на отсутствие вывода ошибки.
Примечание. С помощью этого макроса нельзя протестировать предупреждения, генерируемые макросом @warn
. Для проверки того, активен ли все еще таймер, используйте вместо него макрос @test_logs
.
#
Test.@test_nowarn
— Macro
@test_nowarn expr
Тестирует, возвращает ли вычисление выражения (expr
) пустые выходные данные stderr
(без предупреждений или других сообщений). Возвращает результат вычисления выражения (expr
).
Примечание. С помощью этого макроса нельзя протестировать отсутствие предупреждений, сгенерированных макросом @warn
. Используйте вместо него макрос @test_logs
.
Неработающие тесты
Если тест постоянно завершается сбоем, его можно изменить так, чтобы в нем использовался макрос @test_broken
. При этом тест будет обозначен как Broken
, если он продолжает завершаться ошибкой, и будет указан как Error
, если он выполнен успешно.
#
Test.@test_broken
— Macro
@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
— Macro
@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)
Создание пользовательских типов AbstractTestSet
Пакеты могут создавать собственные подтипы AbstractTestSet
, реализуя методы record
и finish
. Подтип должен иметь одноаргументный конструктор, принимающий строку описания, а любые параметры передаются в качестве именованных аргументов.
#
Test.record
— Function
record(ts::AbstractTestSet, res::Result)
Записывает результат в набор тестов. Эта функция вызывается инфраструктурой @testset
при каждом завершении вложенного макроса @test
и получает результат тестирования (которым может быть ошибка (Error
)). Эта функция будет вызвана с ошибкой (Error
), если исключение возникло внутри тестового блока, но вне контекста @test
.
#
Test.finish
— Function
finish(ts::AbstractTestSet)
Выполняет окончательную обработку, необходимую для заданного набора тестов. Эта функция вызывается инфраструктурой @testset
посте выполнения тестового блока.
Пользовательские подтипы AbstractTestSet
должны вызывать функцию record
для своего родительского типа (при наличии), чтобы добавить себя в дерево результатов тестирования. Это может быть реализовано следующим образом:
if get_testset_depth() != 0
# Прикрепить этот набор тестов к родительскому набору тестов
parent_ts = get_testset()
record(parent_ts, self)
return self
end
Test
поддерживает стек вложенных наборов тестов по мере их выполнения, но за накопление результатов отвечает подтип AbstractTestSet
. Доступ к этому стеку можно получить с помощью методов get_testset
и get_testset_depth
. Обратите внимание, что эти функции не экспортируются.
#
Test.get_testset
— Function
get_testset()
Получает активный набор тестов из локального хранилища задачи. Если активный набор тестов отсутствует, используйте резервный набор тестов.
#
Test.get_testset_depth
— Function
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
# конструктор принимает строку описания и именованные аргументы параметров
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)
# просто запись, если мы не являемся родительским элементом верхнего уровня
if get_testset_depth() > 0
record(get_testset(), ts)
end
ts
end
Использование этого набора тестов выглядит следующим образом:
@testset CustomTestSet foo=4 "custom testset inner 2" begin
# этот набор тестов должен наследовать тип, но не аргумент.
@testset "custom testset inner" begin
@test true
end
end
Тестовые служебные программы
#
Test.GenericArray
— Type
GenericArray
можно использовать для тестирования универсальных API массивов, программирующих для интерфейса AbstractArray
, чтобы убедиться, что функции могут работать с типами массивов помимо стандартного типа Array
.
#
Test.GenericDict
— Type
GenericDict
можно использовать для тестирования универсальных API словарей, программирующих для интерфейса AbstractDict
, чтобы убедиться, что функции могут работать с ассоциативными типами помимо стандартного типа Dict
.
#
Test.GenericOrder
— Type
GenericOrder
можно использовать для тестирования поддержки API универсальных упорядоченных типов.
#
Test.GenericSet
— Type
GenericSet
можно использовать для тестирования универсальных API наборов, программирующих для интерфейса AbstractSet
, чтобы убедиться, что функции могут работать с типами наборов помимо стандартных типов Set
и BitSet
.
#
Test.GenericString
— Type
GenericString
можно использовать для тестирования универсальных API строк, программирующих для интерфейса AbstractString
, чтобы убедиться, что функции могут работать со строковыми типами помимо стандартного типа String
.
#
Test.detect_ambiguities
— Function
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
.
Совместимость: Julia 1.8
Для |
#
Test.detect_unbound_args
— Function
detect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)
Возвращает вектор методов (Method
), которые могут иметь несвязанные параметры типа. Используйте recursive=true
для тестирования во всех подмодулях.
По умолчанию любые неопределенные символы выводят предупреждение. Это предупреждение можно подавить, указав коллекцию ссылок GlobalRef
, для которых предупреждение может быть пропущено. Например, при задании
allow_undefineds = Set([GlobalRef(Base, :active_repl), GlobalRef(Base, :active_repl_backend)])
будут подавляться предупреждения о Base.active_repl
и Base.active_repl_backend
.
Совместимость: 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
. Для выполняемых наборов тестов принято создавать файл с именем runtests.jl
в каталоге test
. Создайте такой файл в каталоге 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
в файлеProject.toml
средыtest
. Это преимущество системы тестирования 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
Тестирование пакета
Добавив тесты и скрипт 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
можно добавлять и более сложные тесты для пакетов, но эти инструкции являются хорошей отправной точкой для разработчиков, желающих приступить к тестированию собственных пакетов.