Julia Language
Тестирование устройства
Поиск…
Синтаксис
- @test [expr]
- @test_throws [Исключение] [expr]
- @testset "[name]" begin; [Тесты]; конец
- Pkg.test ([пакет])
замечания
Стандартная библиотечная документация для Base.Test
охватывает дополнительные материалы, кроме тех, что показаны в этих примерах.
Тестирование пакета
Для запуска модульных тестов для пакета используйте функцию Pkg.test
. Для пакета с именем MyPackage
команда будет
julia> Pkg.test("MyPackage")
Ожидаемый результат будет аналогичен
INFO: Computing test dependencies for MyPackage...
INFO: Installing BaseTestNext v0.2.2
INFO: Testing MyPackage
Test Summary: | Pass Total
Data | 66 66
Test Summary: | Pass Total
Monetary | 107 107
Test Summary: | Pass Total
Basket | 47 47
Test Summary: | Pass Total
Mixed | 13 13
Test Summary: | Pass Total
Data Access | 35 35
INFO: MyPackage tests passed
INFO: Removing BaseTestNext v0.2.2
хотя, очевидно, нельзя ожидать, что он будет точно соответствовать указанному выше, поскольку разные пакеты используют разные фреймворки.
Эта команда запускает файл test/runtests.jl
в чистой среде.
Можно проверить все установленные пакеты одновременно с помощью
julia> Pkg.test()
но это обычно занимает очень много времени.
Написание простого теста
test/runtests.jl
тесты объявляются в файле test/runtests.jl
в пакете. Как правило, этот файл начинается
using MyModule
using Base.Test
Базовой единицей тестирования является макрос @test
. Этот макрос подобен утверждению. Любое булевское выражение может быть проверено в макросе @test
:
@test 1 + 1 == 2
@test iseven(10)
@test 9 < 10 || 10 < 9
Мы можем попробовать макрос @test
в REPL:
julia> using Base.Test
julia> @test 1 + 1 == 2
Test Passed
Expression: 1 + 1 == 2
Evaluated: 2 == 2
julia> @test 1 + 1 == 3
Test Failed
Expression: 1 + 1 == 3
Evaluated: 2 == 3
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test(::Base.Test.Returned, ::Expr) at ./test.jl:281
Тест-макрос можно использовать практически в любом месте, например, в циклах или функциях:
# For positive integers, a number's square is at least as large as the number
for i in 1:10
@test i^2 ≥ i
end
# Test that no two of a, b, or c share a prime factor
function check_pairwise_coprime(a, b, c)
@test gcd(a, b) == 1
@test gcd(a, c) == 1
@test gcd(b, c) == 1
end
check_pairwise_coprime(10, 23, 119)
Написание тестового набора
В версии v0.5 тестовые наборы встроены в стандартный модуль Base.Test
библиотеки, и вам не нужно ничего делать (кроме using Base.Test
), чтобы использовать их.
Тестовые наборы не являются частью библиотеки Base.Test
Julia Base.Test
. Вместо этого, вы должны REQUIRE
в BaseTestNext
модуль и добавить с using BaseTestNext
в файл. Чтобы поддерживать как версии 0.4, так и 0.5, вы можете использовать
if VERSION ≥ v"0.5.0-dev+7720"
using Base.Test
else
using BaseTestNext
const Test = BaseTestNext
end
Полезно группировать связанные @test
s вместе в тестовом наборе. В дополнение к более четкой организации тестирования тестовые комплекты обеспечивают лучшую производительность и большую настраиваемость.
Чтобы определить тестовый набор, просто оберните любое количество @test
s блоком @testset
:
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "*" begin
@test 1 * 1 == 1
@test 2 * 2 == 4
end
Запуск этих тестовых наборов выводит следующий результат:
Test Summary: | Pass Total
+ | 2 2
Test Summary: | Pass Total
* | 2 2
Даже если тестовый набор содержит тест с ошибкой, весь тестовый набор будет запущен до завершения, и сбои будут записываться и сообщаться:
@testset "-" begin
@test 1 - 1 == 0
@test 2 - 2 == 1
@test 3 - () == 3
@test 4 - 4 == 0
end
Выполнение этого набора тестов приводит к
-: Test Failed
Expression: 2 - 2 == 1
Evaluated: 0 == 1
in record(::Base.Test.DefaultTestSet, ::Base.Test.Fail) at ./test.jl:428
...
-: Error During Test
Test threw an exception of type MethodError
Expression: 3 - () == 3
MethodError: no method matching -(::Int64, ::Tuple{})
...
Test Summary: | Pass Fail Error Total
- | 2 1 1 4
ERROR: Some tests did not pass: 2 passed, 1 failed, 1 errored, 0 broken.
...
Наборы тестов могут быть вложенными, позволяя произвольно глубокую организацию
@testset "Int" begin
@testset "+" begin
@test 1 + 1 == 2
@test 2 + 2 == 4
end
@testset "-" begin
@test 1 - 1 == 0
end
end
Если тесты пройдут, то это покажет только результаты для самого внешнего тестового набора:
Test Summary: | Pass Total
Int | 3 3
Но если тесты терпят неудачу, то сообщается о детализации тестового набора и теста, вызывающего отказ.
Макрос @testset
можно использовать с циклом for
для создания множества тестовых наборов одновременно:
@testset for i in 1:5
@test 2i == i + i
@test i^2 == i * i
@test i ÷ i == 1
end
который сообщает
Test Summary: | Pass Total
i = 1 | 3 3
Test Summary: | Pass Total
i = 2 | 3 3
Test Summary: | Pass Total
i = 3 | 3 3
Test Summary: | Pass Total
i = 4 | 3 3
Test Summary: | Pass Total
i = 5 | 3 3
Общей структурой является наличие внешних тестовых наборов для тестирования компонентов или типов. Внутри этих внешних наборов тестов внутренний тест устанавливает поведение теста. Например, предположим, что мы создали тип UniversalSet
с экземпляром singleton, который содержит все. Прежде чем мы даже реализуем этот тип, мы можем использовать основанные на тестах принципы разработки и выполнить тесты:
@testset "UniversalSet" begin
U = UniversalSet.instance
@testset "egal/equal" begin
@test U === U
@test U == U
end
@testset "in" begin
@test 1 in U
@test "Hello World" in U
@test Int in U
@test U in U
end
@testset "subset" begin
@test Set() ⊆ U
@test Set(["Hello World"]) ⊆ U
@test Set(1:10) ⊆ U
@test Set([:a, 2.0, "w", Set()]) ⊆ U
@test U ⊆ U
end
end
Затем мы можем начать реализацию нашей функциональности до тех пор, пока она не пройдет наши тесты. Первый шаг - определить тип:
immutable UniversalSet <: Base.AbstractSet end
Только два наших теста проходят прямо сейчас. Мы можем реализовать in
:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Это также делает некоторые из наших тестов подмножества. Однако issubset
( ⊆
) не работает для UniversalSet
, потому что резервное копирование пытается перебрать элементы, чего мы не сможем сделать. Мы можем просто определить специализацию, которая делает issubset
return true
для любого набора:
immutable UniversalSet <: Base.AbstractSet end
Base.in(x, ::UniversalSet) = true
Base.issubset(x::Base.AbstractSet, ::UniversalSet) = true
И теперь все наши тесты проходят!
Исключения для тестирования
Исключения, возникающие при запуске теста, не пройдут проверку, и если тест не находится в тестовом наборе, завершите работу механизма тестирования. Обычно это хорошо, потому что в большинстве ситуаций исключения не являются желаемым результатом. Но иногда хочется конкретно проверить, что возникает определенное исключение. Макрос @test_throws
облегчает это.
julia> @test_throws BoundsError [1, 2, 3][4]
Test Passed
Expression: ([1,2,3])[4]
Thrown: BoundsError
Если @test_throws
неправильное исключение, @test_throws
все равно не будет выполнено:
julia> @test_throws TypeError [1, 2, 3][4]
Test Failed
Expression: ([1,2,3])[4]
Expected: TypeError
Thrown: BoundsError
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test_throws(::Base.Test.Threw, ::Expr, ::Type{T}) at ./test.jl:329
и если исключение не будет @test_throws
, @test_throws
также не будет выполнено:
julia> @test_throws BoundsError [1, 2, 3, 4][4]
Test Failed
Expression: ([1,2,3,4])[4]
Expected: BoundsError
No exception thrown
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test_throws(::Base.Test.Returned, ::Expr, ::Type{T}) at ./test.jl:329
Приближенное равенство
Какова сделка со следующим?
julia> @test 0.1 + 0.2 == 0.3
Test Failed
Expression: 0.1 + 0.2 == 0.3
Evaluated: 0.30000000000000004 == 0.3
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test(::Base.Test.Returned, ::Expr) at ./test.jl:281
Ошибка вызвана тем фактом, что ни один из 0.1
, 0.2
и 0.3
не представлен на компьютере как именно эти значения - 1//10
, 2//10
и 3//10
. Вместо этого они аппроксимируются значениями, которые очень близки. Но, как видно из вышеприведенного теста, при добавлении двух приближений результат может быть немного хуже приближения, чем это возможно. Этой теме гораздо больше , чем здесь.
Но нам не повезло! Чтобы проверить, что комбинация округления с числом с плавающей запятой и арифметикой с плавающей точкой является приблизительно правильной, даже если она не является точной, мы можем использовать функцию isapprox
(что соответствует оператору ≈
). Поэтому мы можем переписать наш тест как
julia> @test 0.1 + 0.2 ≈ 0.3
Test Passed
Expression: 0.1 + 0.2 ≈ 0.3
Evaluated: 0.30000000000000004 isapprox 0.3
Конечно, если наш код был полностью неправильным, тест все равно поймет, что:
julia> @test 0.1 + 0.2 ≈ 0.4
Test Failed
Expression: 0.1 + 0.2 ≈ 0.4
Evaluated: 0.30000000000000004 isapprox 0.4
ERROR: There was an error during testing
in record(::Base.Test.FallbackTestSet, ::Base.Test.Fail) at ./test.jl:397
in do_test(::Base.Test.Returned, ::Expr) at ./test.jl:281
Функция isapprox
использует эвристику, основанную на размере чисел и точности типа с плавающей запятой, чтобы определить допустимую погрешность. Он не подходит для всех ситуаций, но он работает в большинстве случаев и экономит много усилий, isapprox
собственную версию isapprox
.