Spec-Zone.ru › Julia 0.5

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

Тестирование базового 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. Возвращает Pass Result, если это так, Fail Result, если это false, и Error Result, если оно не может быть вычислено.

@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), если типы совпадают, и 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)
...
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 или вызывает исключение. Возвращает Broken Result если это так, или Error Result если выражение вычисляется до 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/

Spec-Zone.ru

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