Spec-Zone.ru › Julia 1.6

Единые тесты

Тестирование базовой Julia

Julia активно развивается и имеет обширную тестовую suite для проверки функциональности на нескольких платформах. Если вы собираете Julia из исходного кода, вы можете запустить эту тестовую suite с помощью make test. В случае бинарной установки вы можете запустить тестовую suite используя 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, он используется для инициализации глобального генератора случайных чисел (RNG) в контексте запуска тестов; в противном случае seed выбирается случайным образом.

исходный код

Основные единые тесты

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

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

Test.@testМакрос

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

Проверяет, что выражение ex вычисляется как true. Возвращает Pass Result, если это так, Fail Result, если это false, и 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 создаст локальную область видимости при запуске тестов внутри неё.

Макрос @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. Указанные параметры применяются только к набору тестов, где они заданы. По умолчанию тип набора тестов принимает boolean параметр verbose: если true, вывод сводного отчёта о вложенных наборах тестов будет показан даже если все они пройдены (по умолчанию false).

Строка описания допускает интерполяцию из индексов цикла. Если описание не указано, оно строится на основе переменных.

По умолчанию, макрос @testset вернёт сам объект набора тестов, хотя это поведение можно настроить в других типах наборов тестов. Если используется цикл for , макрос собирает и возвращает список возвращаемых значений метода finish, который по умолчанию возвращает список объектов наборов тестов, используемых в каждом итерации.

Перед выполнением тела @testset, неявно вызывается Random.seed!(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
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

В случае, если вложенный набор тестов не имеет неудач, как произошло здесь, он будет скрыт в сводном отчёте, если не указан параметр 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
Foo Tests     |    8      8
  Animals     |    2      2
  Arrays 1    |    2      2
  Arrays 2    |    2      2
  Arrays 3    |    2      2

Если у нас есть провал теста, будут показаны только детали наборов тестов, завершившихся неудачно:

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

исходный код

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

Если тест постоянно терпит неудачу, его можно изменить с использованием макроса @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–2021 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.6.0/stdlib/Test/

Spec-Zone.ru

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