單元測試

測試基本 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_errorfalse,當一個測試失敗時,其他檔案中所有剩餘的測試仍會執行;否則,當 exit_on_error == true 時,它們會被捨棄。如果 revisetrue,則會使用 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 外執行,則擲回例外,而不是傳回 FailError

範例

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 參數使用任何鍵,但 brokenskip 除外,它們在 @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
Julia 1.7

brokenskip 關鍵字參數至少需要 Julia 1.7。

來源
Test.@test_throws巨集
@test_throws exception expr

測試表達式 expr 是否拋出 exception。例外狀況可以指定類型、字串、正規表示法或出現在顯示錯誤訊息中的字串清單、配對函數或值(會透過比較欄位來測試相等性)。請注意,@test_throws 不支援尾端關鍵字形式。

Julia 1.8

除了類型或值之外,指定其他任何內容作為 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 或單一函數呼叫,巨集會啟動一個新的測試集合,用來評估給定的表達式。

如果未給定自訂測試集合類型,它預設會建立 DefaultTestSetDefaultTestSet 會記錄所有結果,並且如果出現任何 FailError,它會在頂層(非巢狀)測試集合結束時拋出例外,以及測試結果摘要。

可以給定任何自訂測試集合類型(AbstractTestSet 的子類型),它也會用於任何巢狀 @testset 呼叫。給定的選項僅套用於給定選項的測試集合。預設測試集合類型接受三個布林選項

  • verbose:如果為 true,即使所有巢狀測試集合都通過,也會顯示結果摘要(預設為 false)。
  • showtiming:如果為 true,將顯示每個顯示測試集合的持續時間(預設為 true)。
  • failfast:如果為 true,任何測試失敗或錯誤都將導致測試集合和任何子測試集合立即返回(預設為 false)。這也可以透過環境變數 JULIA_TEST_FAILFAST 全域設定。
Julia 1.8

@testset test_func() 至少需要 Julia 1.8。

Julia 1.9

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 時,巨集會開始一個透明測試集,其中給定的物件會新增為其中任何失敗測試的內容物件。當對一個較大的物件執行一組相關測試,並且希望在任何個別測試失敗時列印這個較大的物件時,這會很有用。透明測試集不會在測試集階層中引入額外的巢狀層級,並會直接傳遞到父測試集(將內容物件附加到任何失敗測試)。

Julia 1.9

@testset let 至少需要 Julia 1.9。

Julia 1.10

自 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
來源

我們可以將 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:記錄事件的 ID
  • file:包含記錄事件的檔案
  • 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

您可以在 比較之後,分別設定 isapproxrtolatol 關鍵字參數,以指定相對容差和絕對容差

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 也會確保巢狀的 `@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.GenericSet類型

GenericSet 可用於測試編寫至 AbstractSet 介面的通用集合 API,以確保函式除了標準 SetBitSet 類型外,還能與集合類型搭配使用。

來源
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

Julia 1.8

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_replBase.active_repl_backend 的警告。

Julia 1.8

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.jlgreeting_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,可以為套件新增更複雜的測試,但理想情況下這應該指引開發人員如何開始測試他們自己建立的套件。