Spec-Zone.ru › Julia 1.5

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

Тестирование базовой 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 или стандартные библиотеки перед запуском тестов. Если для ключевого аргумента указано значение семени, оно используется для задания глобального генератора случайных чисел в контексте запуска тестов; в противном случае семя выбирается случайным образом.

исходный код

Базовые модульные тесты

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

Простые модульные тесты могут выполняться с помощью макросов @test и @test_throws:

Test.@testМакрос

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

Проверяет, что выражение ex вычисляется как true. Возвращает Pass Result если это так, Fail Result если это не так, и Error Result если оно не может быть вычислено.

Примеры

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).

исходный код

Test.@test_throwsМакрос

@test_throws exception expr

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

Примеры

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
исходный код

Например, предположим, что мы хотим проверить, что наша новая функция 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Макрос

@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, который по умолчанию возвращает список объектов наборов тестов, используемых в каждом цикле.

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

Примеры

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
trigonometric identities |    4      4
исходный код

Мы можем поместить наши тесты для функции 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(fill(1.0, 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(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
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 at none:1
  Expression: 1 ≈ 0.999999
   Evaluated: 1 ≈ 0.999999
ERROR: There was an error during testing

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)
Variables
  #self#::Core.Compiler.Const(f, false)
  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_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:

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

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

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

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

@test (@test_logs (:info,"Doing foo with n=2") foo(2)) == 42
исходный код

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 expr вместо этого.

исходный код

Неисправные тесты

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

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.

Примеры

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_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Функция

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

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

исходный код

Test.finishФункция

finish(ts::AbstractTestSet)

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

исходный код

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

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

Spec-Zone.ru

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