Единые тесты
Тестирование базовой Julia
Julia активно развивается и имеет обширную тестовую suite для проверки функциональности на разных платформах. Если вы компилируете Julia из исходного кода, вы можете запустить этот набор тестов с помощью make test. В случае установки двоичного файла, вы можете запустить тестовую suite с помощью Base.runtests().
Base.runtestsФункция
runtests([tests=["all"] [, numcores=ceil(Int, Sys.CPU_CORES / 2) ]])
Запускает единичные тесты Julia, перечисленные в tests, которые могут быть строкой или массивом строк, используя numcores процессоры. (не экспортируется)
Базовые единичные тесты
Модуль Base.Test предоставляет простую функциональность единичных тестов. Единичный тест – это способ проверить корректность вашего кода, проверяя, соответствуют ли результаты вашим ожиданиям. Это может быть полезно для проверки того, что ваш код по-прежнему работает после внесения изменений, а также может использоваться при разработке для указания поведения, которое ваш код должен иметь при завершении.
Простые единичные тесты могут выполняться с помощью макросов @test() и @test_throws():
Base.Test.@testМакрос
@test ex @test f(args...) key=val ...
Проверяет, что выражение ex вычисляется до true. Возвращает Pass Result если это так, Fail Result если это false, и Error Result если оно не могло быть вычислено.
Форма @test f(args...) key=val... эквивалентна написанию @test f(args..., key=val...), что может быть полезно, когда выражение является вызовом с использованием инфиксной синтаксической конструкции, например, приблизительных сравнений:
@test a ≈ b atol=ε
Это эквивалентно более громоздкому тесту @test ≈(a, b, atol=ε). Недопустимо использовать более одного выражения, если первое не является выражением вызова, а остальные — присваиваниями (k=v).
Base.Test.@test_throwsМакрос
@test_throws extype ex
Проверяет, что выражение ex вызывает исключение типа extype. Обратите внимание, что @test_throws не поддерживает форму с последующим ключевым словом.
Например, предположим, что мы хотим проверить, что наша новая функция 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
julia> @test foo("fizz") >= 10
Test Passed
Если условие ложно, возвращается Fail и генерируется исключение:
julia> @test foo("f") == 20
Test Failed
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.
Base.Test.@testsetМакрос
@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
Stacktrace:
[...]
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.
Другие макросы тестов
Так как вычисления с плавающей точкой могут быть неточными, вы можете выполнить проверки приблизительного равенства, используя либо @test a ≈ b (где ≈, вводимое с помощью автодополнения \approx, является функцией isapprox()) или использовать isapprox() напрямую.
julia> @test 1 ≈ 0.999999999 Test Passed julia> @test 1 ≈ 0.999999 Test Failed Expression: 1 ≈ 0.999999 Evaluated: 1 ≈ 0.999999 ERROR: There was an error during testing
Base.Test.@inferredМакрос
@inferred f(x)
Проверяет, что выражение вызова f(x) возвращает значение того же типа, что и тип, выведенный компилятором. Это полезно для проверки стабильности типов.
f(x) может быть любым выражением вызова. Возвращает результат f(x) если типы совпадают, и Error Result если обнаруживаются разные типы.
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)
Variables:
#self# <optimized out>
a <optimized out>
b::Int64
c <optimized out>
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}
Stacktrace:
[1] error(::String) at ./error.jl:21
julia> @inferred max(1,2)
2
исходный код
Base.Test.@test_warnМакрос
@test_warn msg expr
Проверяет, приводит ли вычисление expr к выводу в STDERR, содержащему строку msg или соответствующему регулярному выражению msg. Если msg — это булева функция, проверяет, возвращает ли msg(output) значение true. Если msg — кортеж или массив, проверяет, содержит ли вывод об ошибке каждый элемент в msg. Возвращает результат вычисления expr.
См. также @test_nowarn для проверки отсутствия вывода об ошибке.
Base.Test.@test_nowarnМакрос
@test_nowarn expr
Проверяет, приводит ли вычисление expr к пустому выводу в STDERR (без предупреждений или других сообщений). Возвращает результат вычисления expr.
Неисправные тесты
Если тест постоянно терпит неудачу, его можно изменить, используя макрос @test_broken(). Это обозначит тест как Broken если тест продолжает терпит неудачу и уведомит пользователя через Error если тест пройден.
Base.Test.@test_brokenМакрос
@test_broken ex @test_broken f(args...) key=val ...
Указывает тест, который должен пройти, но в настоящее время постоянно терпит неудачу. Проверяет, что выражение ex вычисляется до false или вызывает исключение. Возвращает Broken Result если это так, или Error Result если выражение вычисляется до true.
Форма @test_broken f(args...) key=val... работает так же, как для макроса @test.
@test_skip() также доступен для пропуска теста без оценки, но учитывая пропущенный тест в отчете набора тестов. Тест не будет выполнен, но выдаст Broken Result.
Base.Test.@test_skipМакрос
@test_skip ex @test_skip f(args...) key=val ...
Помечает тест, который не должен выполняться, но должен быть включен в отчет о наборе тестов как Broken. Это может быть полезно для тестов, которые периодически терпят неудачу, или для тестов еще не реализованных функций.
Форма @test_skip f(args...) key=val... работает так же, как для макроса @test.
Создание настраиваемых типов AbstractTestSet
Пакеты могут создавать свои собственные подтипы AbstractTestSet , реализуя методы record и finish . Подтип должен иметь одноаргументный конструктор, принимающий строку описания, а все параметры передавать в качестве ключевых аргументов.
Base.Test.recordФункция
record(ts::AbstractTestSet, res::Result)
Запишите результат в наборе тестов. Эта функция вызывается инфраструктурой @testset каждый раз, когда завершается содержащий @test макрос, и получает результат теста (который может быть Error). Это также будет вызвано с Error , если исключение было сгенерировано внутри блока теста, но вне контекста @test.
Base.Test.finishФункция
finish(ts::AbstractTestSet)
Выполните все необходимые заключительные операции для данного набора тестов. Это вызывается инфраструктурой @testset после выполнения блока теста. Одним из распространённых применений этой функции является запись набора тестов в список результатов родительского элемента, используя get_testset.
Base.Test отвечает за поддержание стека вложенных наборов тестов по мере их выполнения, но любая аккумуляция результатов — ответственность подтипа AbstractTestSet. Вы можете получить доступ к этому стеку с помощью методов get_testset и get_testset_depth. Обратите внимание, что эти функции не экспортируются.
Base.Test.get_testsetФункция
get_testset()
Получить активный набор тестов из локального хранилища задачи. Если активный набор тестов отсутствует, используется резервный набор тестов по умолчанию.
исходный код
Base.Test.get_testset_depthФункция
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
struct 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.6/stdlib/test/