Spec-Zone.ru › Julia 1.9

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

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

исходный код

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

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

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

Test.@testМакрос

@test ex
@test f(args...) key=val ...
@test ex broken=true
@test ex skip=true

Проверить, что выражение ex вычисляется до true. Если выполняется внутри @testset, возвращает Pass Result, если верно, Fail Result, если ложно, и 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

Ключевые аргументы broken и skip требуют как минимум Julia 1.7.

Test.@test_throwsМакрос

@test_throws exception expr

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

Возможность указать что-либо кроме типа или значения как exception требует Julia v1.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 создаёт локальную область видимости при запуске тестов.

Макрос @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
@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.

@testset foo() требует как минимум Julia 1.8.

failfast требует как минимум Julia 1.9.

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

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

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

@testset for

Когда используется `for`, макрос запускает новый тест для каждой итерации заданного цикла. Семантика каждого набора тестов в противном случае идентична случаю с `@testset` (как если бы он использовался для каждой итерации цикла).

@testset let

Когда используется `@testset`, макрос запускает прозрачный набор тестов с указанным объектом, добавленным в качестве контекстного объекта в любой терпящий неудачу тест, содержащийся в нём. Это полезно при выполнении набора связанных тестов для одного большего объекта, и при желании вывести этот больший объект, когда любой из отдельных тестов терпит неудачу. Прозрачные наборы тестов не вводят дополнительных уровней вложенности в иерархию наборов тестов и передаются непосредственно в родительский набор тестов (с контекстным объектом, добавленным в любой терпящий неудачу тест).

@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
END_OF_DOCUMENT_MARKER ```

Test.TestSetExceptionТип

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Макрос

@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:

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 that the expression logs no messages when the logger level is warn:
@test_logs min_level=Logging.Warn @info("Some information") # passes
@test_logs min_level=Logging.Warn @warn("Some information") # fails

Если вы хотите проверить отсутствие предупреждений (или сообщений об ошибках) в stderr, которые не генерируются @warn, см. @test_nowarn.

Test.TestLoggerТип

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Тип

LogRecord

Хранит результаты одного события журнала. Поля:

  • level: уровень LogLevel сообщения журнала
  • message: текстовое содержание сообщения журнала
  • _module: модуль события журнала
  • group: группа логирования (по умолчанию имя файла, содержащего событие журнала)
  • id: идентификатор события журнала
  • file: файл, содержащий событие журнала
  • line: строка в файле события журнала
  • kwargs: любые ключевые аргументы, переданные в событие журнала

Другие макросы тестов

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

Вы можете указать относительные и абсолютные допуски, установив ключевые аргументы 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Макрос

@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 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_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 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Макрос

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

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

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

Test.finishФункция

finish(ts::AbstractTestSet)

Выполнение необходимой финальной обработки для данного набора тестов. Вызывается инфраструктурой @testset после выполнения блока теста.

Пользовательские подтипы AbstractTestSet должны вызывать record у своего родителя (если он есть), чтобы добавить себя в дерево результатов тестов. Это может быть реализовано следующим образом:

if get_testset_depth() != 0
    # Attach this test set to the parent test set
    parent_ts = get_testset()
    record(parent_ts, self)
    return self
end

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

Утилиты тестирования

Test.GenericArrayТип

GenericArray может использоваться для тестирования универсальных API массивов, ориентированных на интерфейс AbstractArray, чтобы убедиться, что функции могут работать с типами массивов помимо стандартного типа Array.

Test.GenericDictТип

GenericDict может использоваться для тестирования универсальных API словарей, ориентированных на интерфейс AbstractDict, чтобы убедиться, что функции могут работать с ассоциативными типами помимо стандартного типа Dict.

Test.GenericOrderТип

GenericOrder может использоваться для тестирования API для поддержки универсальных упорядоченных типов.

Test.GenericSetТип

GenericSet может использоваться для тестирования универсальных API множеств, ориентированных на интерфейс AbstractSet, чтобы убедиться, что функции могут работать с типами множеств помимо стандартных типов Set и BitSet.

Test.GenericStringТип

GenericString может использоваться для тестирования универсальных API строк, ориентированных на интерфейс AbstractString, чтобы убедиться, что функции могут работать с типами строк помимо стандартного типа String.

Test.detect_ambiguitiesФункция

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.

Для использования allowed_undefineds требуется Julia 1.8 или выше.

Test.detect_unbound_argsФункция

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.

Для использования allowed_undefineds требуется 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. Стандартной практикой является создание файла в директории test с именем runtests.jl, содержащего наборы тестов, которые мы хотим запустить. Создайте этот файл в директории 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 в среду test пакета Project.toml. Это преимущество системы тестирования 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

Написание тестов для greeting_tests.jl

Используя наши знания о Test.jl, вот некоторые примеры тестов, которые мы можем добавить в math_tests.jl:

@testset "Testset 3" begin
    @test "Hello world!" == greet()
    @test_throws MethodError greet("Antonia")
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, можно добавить более сложные тесты для пакетов, но это, по идее, должно направить разработчиков в сторону того, как начать тестирование собственных созданных пакетов.

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

Spec-Zone.ru

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