单元测试
测试 Base 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])
运行 tests
中列出的 Julia 单元测试,tests
可以是字符串或字符串数组,使用 ncores
个处理器。如果 exit_on_error
为 false
,当一个测试失败时,其他文件中所有剩余的测试仍然会运行;否则,当 exit_on_error == true
时,它们将被丢弃。如果 revise
为 true
,则 Revise
包将用于在运行测试之前加载对 Base
或标准库的任何修改。如果通过关键字参数提供种子,则它将用于在运行测试的上下文中为全局 RNG 播种;否则,将随机选择种子。
基本单元测试
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
中执行,如果它为 true
,则返回 Pass
Result
;如果它为 false
,则返回 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
或导致异常。如果它为true
,则返回Broken
Result
;如果表达式计算为true
,则返回Error
Result
。当cond==false
时,会评估常规的@test ex
。skip=cond
表示不应执行的测试,但在cond==true
时,应将其包含在测试摘要报告中,作为Broken
。这对于间歇性失败的测试或尚未实现的功能的测试非常有用。当cond==false
时,会评估常规的@test ex
。
示例
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 was called with a negative real argument but 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] [options...] ["description"] begin test_ex end
@testset [CustomTestSet] [options...] ["description $v"] for v in itr test_ex end
@testset [CustomTestSet] [options...] ["description $v, $w"] for v in itrv, w in itrw test_ex end
@testset [CustomTestSet] [options...] ["description"] test_func()
@testset let v = v, w = w; test_ex; 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 test_func()
需要至少 Julia 1.8。
failfast
需要至少 Julia 1.9。
描述字符串接受来自循环索引的插值。如果没有提供描述,则根据变量构造一个描述。如果提供函数调用,则将使用其名称。显式描述字符串将覆盖此行为。
默认情况下,@testset
宏将返回测试集对象本身,但此行为可以在其他测试集类型中自定义。如果使用 for
循环,则宏将收集并返回 finish
方法的返回值列表,默认情况下该方法将返回每个迭代中使用的测试集对象的列表。
在执行 @testset
的主体之前,会隐式调用 Random.seed!(seed)
,其中 seed
是全局 RNG 的当前种子。此外,在执行主体之后,全局 RNG 的状态将恢复到 @testset
之前的状态。这样做是为了在发生错误时简化可重复性,并允许在不影响全局 RNG 状态的情况下无缝重新排列 @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
当使用 @testset for
时,宏将为提供的循环的每次迭代启动一个新的测试。每个测试集的语义与 begin/end
案例相同(如果用于每个循环迭代)。
@testset let
当使用 @testset let
时,宏将启动一个透明测试集,并将给定对象添加为其中包含的任何失败测试的上下文对象。当对一个较大的对象执行一组相关的测试时,这很有用,并且在任何单个测试失败时希望打印这个较大的对象。透明测试集不会在测试集层次结构中引入额外的嵌套级别,而是直接传递给父测试集(将上下文对象附加到任何失败的测试)。
@testset let
要求至少使用 Julia 1.9。
从 Julia 1.10 开始支持多个 let
赋值。
示例
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
julia> @testset let logi = log(im), op = !iszero
@test imag(logi) == π/2
@test op(real(logi))
end
Test Failed at none:3
Expression: op(real(logi))
Context: logi = 0.0 + 1.5707963267948966im
op = !iszero
ERROR: There was an error during testing
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
使用 collect_test_logs
收集 expression
生成的日志记录列表,检查它们是否与序列 log_patterns
匹配,并返回 expression
的值。keywords
提供了一些简单的日志记录过滤:min_level
关键字控制将为测试收集的最小日志级别,match_mode
关键字定义如何执行匹配(默认的 :all
检查所有日志和模式是否成对匹配;使用 :any
检查模式是否至少在序列中的某个地方匹配一次)。
最实用的日志模式是 (level,message)
形式的简单元组。可以使用不同数量的元组元素来匹配其他日志元数据,对应于通过 handle_message
函数传递给 AbstractLogger
的参数:(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
我们可以使用以下方法测试信息消息
@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
次的约定。
另请参阅: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
:日志事件的 IDfile
:包含日志事件的文件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
您可以通过分别在 ≈
比较之后设置 isapprox
的 rtol
和 atol
关键字参数来指定相对容差和绝对容差
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)
的类型与推断的类型匹配 modulo 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
,如果表达式评估为 true
,则返回 Error
Result
。这等效于 @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)
测试结果类型
Test.Result
— 类型Test.Result
所有测试都会产生一个结果对象。这个对象可能会被存储,也可能不会被存储,取决于测试是否是测试集的一部分。
Test.Pass
— 类型Test.Pass <: Test.Result
测试条件为真,即表达式计算结果为真或抛出了正确的异常。
Test.Fail
— 类型Test.Fail <: Test.Result
测试条件为假,即表达式计算结果为假或没有抛出正确的异常。
Test.Error
— 类型Test.Error <: Test.Result
由于异常导致测试条件无法评估,或者评估结果不是 Bool
。在 @test_broken
的情况下,它用于指示发生了意外的 Pass
Result
。
Test.Broken
— 类型Test.Broken <: Test.Result
测试条件是断裂测试的预期(失败)结果,或者使用 @test_skip
显式跳过。
创建自定义 AbstractTestSet
类型
包可以通过实现 record
和 finish
方法来创建自己的 AbstractTestSet
子类型。子类型应该有一个带描述字符串的单参数构造函数,并将任何选项作为关键字参数传入。
Test.record
— 函数record(ts::AbstractTestSet, res::Result)
将结果记录到测试集。每次包含的 @test
宏完成时,此函数都会被 @testset
基础设施调用,并给出测试结果(可能是 Error
)。如果在测试块内但在 @test
上下文之外抛出异常,也会调用此函数,并传入一个 Error
。
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
可用于测试对 AbstractArray
接口进行编程的通用数组 API,以确保函数可以与除标准 Array
类型之外的数组类型一起使用。
Test.GenericDict
— 类型GenericDict
可用于测试对 AbstractDict
接口进行编程的通用字典 API,以确保函数可以与除标准 Dict
类型之外的关联类型一起使用。
Test.GenericOrder
— 类型GenericOrder
可用于测试 API 对通用有序类型的支持。
Test.GenericSet
— 类型GenericSet
可用于测试对 AbstractSet
接口进行编程的通用集合 API,以确保函数可以与除标准 Set
和 BitSet
类型之外的集合类型一起使用。
Test.GenericString
— 类型GenericString
可用于测试对 AbstractString
接口进行编程的通用字符串 API,以确保函数可以与除标准 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
。
有关 allowed_undefineds
的解释,请参见 Test.detect_unbound_args
。
allowed_undefineds
至少需要 Julia 1.8。
Test.detect_unbound_args
— 函数detect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)
返回可能具有未绑定类型参数的 Method
的向量。使用 recursive=true
在所有子模块中进行测试。
默认情况下,任何未定义的符号都会触发警告。可以通过提供要跳过警告的 GlobalRef
集合来抑制此警告。例如,设置
allowed_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
,并向其中添加一些测试。
注意:请注意,我们无需在
test
环境的Project.toml
中指定添加Example
。这是 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
测试我们的包
现在我们已经添加了测试和 test
中的 runtests.jl
脚本,我们可以通过返回 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
,可以为包添加更复杂的测试,但理想情况下,这应该能使开发人员了解如何开始测试自己创建的包。