單元測試
測試基本 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])
使用 ncores
處理器執行 tests
中列出的 Julia 單元測試,該測試可以是字串或字串陣列。如果 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
之前的狀態。這是為了在發生故障時簡化重製,並允許無縫重新排列 @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 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)
。可以使用不同數量的元組元素來比對其他記錄的元資料,這些元資料對應傳遞給 AbstractLogger
的引數,透過 handle_message
函式:(level,message,module,group,id,file,line)
。預設情況下,存在的元素會使用 ==
與記錄欄位成對比對,特殊情況是,標準記錄層級可以使用 Symbol
,而模式中的 Regex
會使用 occursin
比對字串或 Symbol
欄位。
範例
考慮一個記錄警告和多個偵錯訊息的函式
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
最多為 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
的 tab 補全輸入,是 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)
的類型與推論的類型模組 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
。這等於 @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
。
請參閱 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
集合來抑制此警告,其中可以略過警告。例如,設定
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
,並在其中新增一些測試。
注意:請注意我們不必明確將
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
測試我們的套件
現在我們已經在 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
,可以為套件新增更複雜的測試,但理想情況下這應該指引開發人員如何開始測試他們自己建立的套件。