Тестирование модулей
Тестирование базового Julia
Julia активно развивается и имеет обширную тестовую систему для проверки функциональности на нескольких платформах. Если вы компилируете Julia из исходного кода, вы можете запустить эту тестовую систему с помощью make test. В случае бинарной установки вы можете запустить тестовую систему с помощью Base.runtests().
-
runtests([tests=["all"][, numcores=ceil(Integer, Sys.CPU_CORES / 2)]]) -
Запустить тесты модуля Julia, перечисленные в
tests, которые могут быть строкой или массивом строк, используяnumcoresпроцессоры. (не экспортировано)
Основные тесты модулей
Модуль Base.Test предоставляет простую функциональность тестирования модулей. Тестирование модулей — это способ проверить правильность вашего кода, проверяя, соответствуют ли результаты ожидаемым. Это полезно для проверки работоспособности кода после внесения изменений, а также может использоваться при разработке для определения поведения вашего кода в полном виде.
Простые тесты модулей можно выполнить с помощью макросов @test() и @test_throws().
-
@test ex -
Проверяет, что выражение
exвычисляется до значенияtrue. ВозвращаетPassResult, если это так,FailResult, если этоfalse, иErrorResult, если оно не может быть вычислено.
-
@test_throws extype ex -
Проверяет, что выражение
exвызывает исключение типаextype.
Например, предположим, что мы хотим проверить, что наша новая функция foo(x) работает как ожидается:
julia> using Base.Test julia> foo(x) = length(x)^2 foo (generic function with 1 method)
Если условие истинно, возвращается Pass:
julia> @test foo("bar") == 9
Test Passed
Expression: foo("bar") == 9
Evaluated: 9 == 9
julia> @test foo("fizz") >= 10
Test Passed
Expression: foo("fizz") >= 10
Evaluated: 16 >= 10
Если условие ложно, возвращается Fail и выбрасывается исключение:
julia> @test foo("f") == 20
Test Failed
Expression: foo("f") == 20
Evaluated: 1 == 20
ERROR: There was an error during testing
in record at test.jl:268
in do_test at test.jl:191
Если условие не может быть вычислено из-за исключения, которое в этом случае возникает, потому что length() не определено для символов, возвращается объект Error и выбрасывается исключение:
julia> @test foo(:cat) == 1 Error During Test Test threw an exception of type MethodError Expression: foo(:cat) == 1 MethodError: `length` has no method matching length(::Symbol) in foo at none:1 in anonymous at test.jl:159 in do_test at test.jl:180 ERROR: There was an error during testing in record at test.jl:268 in do_test at test.jl:191
Если ожидается, что вычисление выражения должно вызвать исключение, мы можем использовать @test_throws() для проверки этого:
julia> @test_throws MethodError foo(:cat) Test Passed Expression: foo(:cat) Evaluated: MethodError
Работа с наборами тестов
Обычно для проверки корректной работы функций используются большое количество тестов с различными входными данными. В случае неудачи теста, по умолчанию происходит немедленное исключение. Однако обычно предпочтительно сначала запустить оставшиеся тесты, чтобы получить более полное представление о количестве ошибок в тестируемом коде.
Макрос @testset() может быть использован для группировки тестов в наборы. Все тесты в наборе будут запущены, а в конце набора тестов будет выведен сводный отчет. Если какие-либо тесты завершились неудачно или не смогли быть вычислены из-за ошибки, набор тестов вызовет TestSetException.
-
@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 -
Начинает новый набор тестов или несколько наборов тестов, если указан цикл
for.Если тип набора тестов не указан, по умолчанию создается
DefaultTestSet.DefaultTestSetзаписывает все результаты и, если есть какие-либоFailилиError, выбрасывает исключение в конце верхнего (не вложенного) набора тестов вместе со сводным отчетом о результатах тестирования.Любой пользовательский тип набора тестов (подтип
AbstractTestSet) может быть указан, и он также будет использоваться для всех вложенных вызовов@testset. Указанные параметры применяются только к набору тестов, где они указаны. Тип набора тестов по умолчанию не принимает никаких параметров.Строка описания принимает интерполяцию из индексов цикла. Если описание не предоставлено, оно формируется на основе переменных.
По умолчанию макрос
@testsetвернёт сам объект набора тестов, хотя это поведение может быть изменено в других типах наборов тестов. Если используется циклfor, макрос собирает и возвращает список возвращаемых значений методаfinish, который по умолчанию вернёт список объектов наборов тестов, используемых в каждой итерации.
Мы можем поместить наши тесты для функции foo(x) в набор тестов:
julia> @testset "Foo Tests" begin
@test foo("a") == 1
@test foo("ab") == 4
@test foo("abc") == 9
end
Test Summary: | Pass Total
Foo Tests | 3 3
Наборы тестов также могут быть вложенными:
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(ones(i)) == i^2
end
end
Test Summary: | Pass Total
Foo Tests | 8 8
В случае, если у вложенного набора тестов нет ошибок, как это произошло здесь, он будет скрыт в сводном отчете. Если у нас есть ошибка теста, будут показаны только детали для не пройденных наборов тестов:
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(ones(4)) == 15
end
end
Arrays: Test Failed
Expression: foo(ones(4)) == 15
Evaluated: 16 == 15
in record at test.jl:297
in do_test at test.jl:191
Test Summary: | Pass Fail Total
Foo Tests | 3 1 4
Animals | 2 2
Arrays | 1 1 2
ERROR: Some tests did not pass: 3 passed, 1 failed, 0 errored, 0 broken.
in finish at test.jl:362
Другие макросы тестирования
Поскольку вычисления с плавающей точкой могут быть неточными, вы можете выполнить проверку приблизительного равенства, используя либо @test a ≈ b (где ≈, вводимый с помощью автодополнения \approx, — функция isapprox() или напрямую использовать isapprox().
Альтернативой является макрос @test_approx_eq (который отличается от isapprox тем, что обрабатывает значения NaN как равные и имеет меньший порог по умолчанию) или @test_approx_eq_eps (который принимает дополнительный аргумент, указывающий относительный порог):
julia> @test 1 ≈ 0.999999999 julia> @test 1 ≈ 0.999999 ERROR: test failed: 1 isapprox 0.999999 in expression: 1 ≈ 0.999999 in error at error.jl:21 in default_handler at test.jl:30 in do_test at test.jl:53 julia> @test_approx_eq 1. 0.999999999 ERROR: assertion failed: |1.0 - 0.999999999| < 2.220446049250313e-12 1.0 = 1.0 0.999999999 = 0.999999999 in test_approx_eq at test.jl:75 in test_approx_eq at test.jl:80 julia> @test_approx_eq 1. 0.9999999999999 julia> @test_approx_eq_eps 1. 0.999 1e-2 julia> @test_approx_eq_eps 1. 0.999 1e-3 ERROR: assertion failed: |1.0 - 0.999| <= 0.001 1.0 = 1.0 0.999 = 0.999 difference = 0.0010000000000000009 > 0.001 in error at error.jl:22 in test_approx_eq at test.jl:68
Обратите внимание, что эти макросы завершатся немедленно и не совместимы с @testset(), поэтому использование @test isapprox рекомендуется при написании новых тестов.
-
@test_approx_eq(a, b) -
Проверить два числа с плавающей точкой
aиbна равенство с учётом незначительных числовых ошибок.
-
@test_approx_eq_eps(a, b, tol) -
Проверить два числа с плавающей точкой
aиbна равенство с учётом границ допуска, заданныхtol.
-
@inferred f(x) -
Проверяет, что выражение вызова
f(x)возвращает значение того же типа, что и определено компилятором. Это полезно для проверки стабильности типа.f(x)может быть любым выражением вызова. Возвращает результатf(x), если типы совпадают, иErrorResultесли обнаружены разные типы.julia> using Base.Test julia> f(a,b,c) = b > 1 ? 1 : 1.0 f (generic function with 1 method) julia> typeof(f(1,2,3)) Int64 julia> @code_warntype f(1,2,3) ... Body: begin unless (Base.slt_int)(1,b::Int64)::Bool goto 3 return 1 3: return 1.0 end::UNION{FLOAT64,INT64} julia> @inferred f(1,2,3) ERROR: return type Int64 does not match inferred return type Union{Float64,Int64} in error(::String) at ./error.jl:21 ... julia> @inferred max(1,2) 2
Непройденные тесты
Если тест постоянно не проходит, можно изменить его, используя макрос @test_broken(). Это обозначит тест как Broken если тест продолжает не проходить, и оповестит пользователя посредством Error если тест пройдёт.
-
@test_broken ex -
Указывает тест, который должен пройти, но в настоящее время постоянно не проходит. Проверяет, что выражение
exвычисляется доfalseили вызывает исключение. ВозвращаетBrokenResultесли это так, илиErrorResultесли выражение вычисляется доtrue.
@test_skip() также доступен для пропуска теста без оценки, но учитывая пропущенный тест в отчете набора тестов. Тест не будет запущен, но даст Broken Result.
-
@test_skip ex -
Помечает тест, который не должен выполняться, но должен быть включён в отчёт набора тестов как
Broken. Это может быть полезно для тестов, которые время от времени не проходят, или для тестов нереализованных функций.
Создание собственных типов AbstractTestSet
Пакеты могут создавать свои собственные подтипы AbstractTestSet путём реализации методов record и finish. Подтип должен иметь конструктор с одним аргументом, принимающим строку описания, а любые параметры передаются в качестве ключевых аргументов.
-
record(ts::AbstractTestSet, res::Result) -
Запись результата в набор тестов. Эта функция вызывается инфраструктурой
@testsetвсякий раз, когда завершается содержащий макрос@test, и получает результат теста (который может бытьError). Это также будет вызвано сErrorесли исключение выбрасывается внутри блока теста, но вне контекста@test.
-
finish(ts::AbstractTestSet) -
Выполнить все необходимые заключительные операции для данного набора тестов. Это вызывается инфраструктурой
@testsetпосле выполнения блока теста. Одно из распространённых применений этой функции — запись набора тестов в список результатов родителя, используяget_testset.
Base.Test несёт ответственность за поддержание стека вложенных наборов тестов во время их выполнения, но любая аккумуляция результатов — ответственность подтипа AbstractTestSet. Доступ к этому стеку можно получить с помощью методов get_testset и get_testset_depth . Обратите внимание, что эти функции не экспортированы.
-
get_testset() -
Получить активный набор тестов из локального хранилища задачи. Если активный набор тестов отсутствует, использовать набор тестов по умолчанию.
-
get_testset_depth() -
Возвращает количество активных наборов тестов, не включая набор тестов по умолчанию.
Base.Test также гарантирует, что вложенные вызовы @testset используют тот же тип подтипа AbstractTestSet , что и их родитель, если явно не задано другое значение. Он не распространяет никакие свойства набора тестов. Поведение наследования свойств можно реализовать в пакетах, использующих инфраструктуру стека, предоставляемую Base.Test.
Определение основного подтипа AbstractTestSet может выглядеть следующим образом:
import Base.Test: record, finish
using Base.Test: AbstractTestSet, Result, Pass, Fail, Error
using Base.Test: get_testset_depth, get_testset
immutable CustomTestSet <: Base.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
© 2009–2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/release-0.5/stdlib/test/