Unit Testing

Testing Base Julia

Juliaは急速に開発されており、複数のプラットフォームでの機能を検証するための広範なテストスイートがあります。ソースからJuliaをビルドする場合、make testを使用してこのテストスイートを実行できます。バイナリインストールの場合、Base.runtests()を使用してテストスイートを実行できます。

Base.runtestsFunction
Base.runtests(tests=["all"]; ncores=ceil(Int, Sys.CPU_THREADS / 2),
              exit_on_error=false, revise=false, [seed])

testsにリストされたJuliaのユニットテストを、ncoresプロセッサを使用して実行します。testsは文字列または文字列の配列のいずれかです。exit_on_errorfalseの場合、1つのテストが失敗しても、他のファイルの残りのテストは実行されます。exit_on_error == trueの場合は、それらは破棄されます。revisetrueの場合、テストを実行する前にBaseや標準ライブラリへの変更を読み込むためにReviseパッケージが使用されます。キーワード引数を介してシードが提供されると、それはテストが実行されるコンテキストでグローバルRNGのシードとして使用されます。そうでない場合、シードはランダムに選ばれます。

source

Basic Unit Tests

Test モジュールはシンプルな 単体テスト 機能を提供します。単体テストは、結果が期待通りであることを確認することで、コードが正しいかどうかを確認する方法です。変更を加えた後でもコードが正常に動作することを保証するのに役立ち、完成時にコードが持つべき動作を指定する方法として開発中に使用できます。また、adding tests to your Julia Package のドキュメントも参照することをお勧めします。

単純なユニットテストは、@test および @test_throws マクロを使用して実行できます:

Test.@testMacro
@test ex
@test f(args...) key=val ...
@test ex broken=true
@test ex skip=true

extrue に評価されることをテストします。@testset 内で実行された場合、評価が成功すれば 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 引数には任意のキーを使用できますが、brokenskip@test の文脈で特別な意味を持ちます:

  • broken=cond は、テストが通るべきだが現在は cond==true のときに一貫して失敗することを示します。式 exfalse に評価されるか、例外を引き起こすテストです。これが成立すれば Broken Result を返し、式が true に評価されれば Error Result を返します。通常の @test excond==false のときに評価されます。
  • skip=cond は、実行されるべきではないが、cond==true のときにテストサマリー報告に Broken として含めるべきテストをマークします。これは、断続的に失敗するテストや、まだ実装されていない機能のテストに便利です。通常の @test excond==false のときに評価されます。

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

broken および skip キーワード引数は、少なくとも Julia 1.7 が必要です。

```

source
Test.@test_throwsMacro
@test_throws exception expr

exprexception をスローすることをテストします。例外は、型、文字列、表示されるエラーメッセージに含まれる正規表現、または文字列のリスト、マッチング関数、または値(フィールドを比較して等価性をテストします)を指定できます。@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)(マッチング関数)
source

例えば、新しい関数 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)
  The function `length` exists, but no method is defined for this combination of argument types.

  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

Working with Test Sets

通常、大量のテストが使用されて、関数がさまざまな入力に対して正しく動作することを確認します。テストが失敗した場合、デフォルトの動作は即座に例外をスローすることです。しかし、通常は、テストの残りを最初に実行して、テストされているコードにどれだけのエラーがあるかをよりよく把握することが望ましいです。

Note

@testsetは、その中でテストを実行する際に独自のローカルスコープを作成します。

@testset マクロは、テストを セット にグループ化するために使用できます。テストセット内のすべてのテストが実行され、テストセットの最後に要約が印刷されます。テストのいずれかが失敗した場合、またはエラーのために評価できなかった場合、テストセットは TestSetException をスローします。

Test.@testsetMacro
@testset [CustomTestSet] [options...] ["説明"] begin test_ex end
@testset [CustomTestSet] [options...] ["説明 $v"] for v in itr test_ex end
@testset [CustomTestSet] [options...] ["説明 $v, $w"] for v in itrv, w in itrw test_ex end
@testset [CustomTestSet] [options...] ["説明"] test_func()
@testset let v = v, w = w; test_ex; end

begin/end または関数呼び出しを使用した場合

@testset が使用されると、begin/end または単一の関数呼び出しと共に、マクロは与えられた式を評価するための新しいテストセットを開始します。

カスタムテストセットタイプが指定されていない場合、デフォルトで DefaultTestSet が作成されます。DefaultTestSet はすべての結果を記録し、Fail または Error がある場合、トップレベル(非ネスト)テストセットの最後で例外をスローし、テスト結果の要約を表示します。

任意のカスタムテストセットタイプ(AbstractTestSet のサブタイプ)を指定することができ、ネストされた @testset の呼び出しにも使用されます。指定されたオプションは、与えられたテストセットにのみ適用されます。デフォルトのテストセットタイプは、3つのブールオプションを受け入れます:

  • 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 の前の状態に復元されます。これは、失敗時の再現性を容易にし、グローバル RNG 状態への副作用に関係なく @testset のシームレスな再配置を可能にすることを目的としています。

julia> @testset "三角関数の恒等式" begin
           θ = 2/3*π
           @test sin(-θ) ≈ -sin(θ)
           @test cos(-θ) ≈ cos(θ)
           @test sin(2θ) ≈ 2*sin(θ)*cos(θ)
           @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2
       end;
テストの要約:            | 合格  合計  時間
三角関数の恒等式 |    4      4  0.2s

@testset for

@testset for が使用されると、マクロは提供されたループの各イテレーションに対して新しいテストを開始します。各テストセットの意味は、begin/end の場合と同じです(各ループイテレーションに対して使用されるかのように)。

@testset let

@testset let が使用されると、マクロは与えられたオブジェクトを含む透明なテストセットを開始します。このオブジェクトは、そこに含まれる失敗したテストにコンテキストオブジェクトとして追加されます。これは、1つの大きなオブジェクトに対して関連するテストのセットを実行する際に便利で、個々のテストが失敗したときにこの大きなオブジェクトを印刷することが望ましい場合に役立ちます。透明なテストセットは、テストセット階層に追加のネストレベルを導入せず、親テストセットに直接渡されます(コンテキストオブジェクトは失敗したテストに追加されます)。

Julia 1.9

@testset let は少なくとも Julia 1.9 が必要です。

Julia 1.10

複数の let 割り当ては Julia 1.10 以降でサポートされています。

julia> @testset let logi = log(im)
           @test imag(logi) == π/2
           @test !iszero(real(logi))
       end
テスト失敗: none:3
  式: !(iszero(real(logi)))
     コンテキスト: logi = 0.0 + 1.5707963267948966im

ERROR: テスト中にエラーが発生しました

julia> @testset let logi = log(im), op = !iszero
           @test imag(logi) == π/2
           @test op(real(logi))
       end
テスト失敗: none:3
  式: op(real(logi))
     コンテキスト: logi = 0.0 + 1.5707963267948966im
              op = !iszero

ERROR: テスト中にエラーが発生しました
source
Test.TestSetExceptionType
TestSetException

テストセットが終了し、すべてのテストが合格しなかったときにスローされます。

source

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.

Testing Log Statements

@test_logs マクロを使用してログステートメントをテストすることができます。または、TestLogger を使用することもできます。

Test.@test_logsMacro
@test_logs [log_patterns...] [keywords] expression

expressionによって生成されたログレコードのリストをcollect_test_logsを使用して収集し、それらがlog_patternsのシーケンスと一致することを確認し、expressionの値を返します。keywordsはログレコードの簡単なフィルタリングを提供します:min_levelキーワードはテストのために収集される最小ログレベルを制御し、match_modeキーワードは一致の方法を定義します(デフォルトの:allはすべてのログとパターンがペアで一致することを確認します;:anyを使用すると、パターンがシーケンスのどこかで少なくとも1回一致することを確認します)。

最も便利なログパターンは、形式が(level,message)の単純なタプルです。異なる数のタプル要素を使用して、handle_message関数を介してAbstractLoggerに渡される引数に対応する他のログメタデータと一致させることができます:(level,message,module,group,id,file,line)。存在する要素は、デフォルトで==を使用してログレコードフィールドとペアで一致します。特別なケースとして、Symbolは標準ログレベルに使用でき、パターン内のRegexは文字列またはSymbolフィールドに対して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を適切に設定できます:

# ロガーレベルがwarnのときに式がメッセージをログに記録しないことをテスト:
@test_logs min_level=Logging.Warn @info("Some information") # 成功
@test_logs min_level=Logging.Warn @warn("Some information") # 失敗

@warnによって生成されないstderrの警告(またはエラーメッセージ)がないことをテストしたい場合は、@test_nowarnを参照してください。

source
Test.TestLoggerType
TestLogger(; min_level=Info, catch_exceptions=false)

logs::Vector{LogRecord} フィールドにログメッセージをキャプチャする TestLogger を作成します。

min_level を設定して LogLevel を制御し、catch_exceptions を使用してログイベント生成の一部としてスローされた例外をキャッチするかどうかを指定し、respect_maxlog を使用して、整数 n に対して 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
source
Test.LogRecordType
LogRecord

単一のログイベントの結果を保存します。フィールド:

  • level: ログメッセージの LogLevel
  • message: ログメッセージのテキスト内容
  • _module: ログイベントのモジュール
  • group: ロギンググループ(デフォルトでは、ログイベントを含むファイルの名前)
  • id: ログイベントのID
  • file: ログイベントを含むファイル
  • line: ログイベントのファイル内の行
  • kwargs: ログイベントに渡された任意のキーワード引数
source

Other Test Macros

浮動小数点値の計算は不正確な場合があるため、@test a ≈ b(ここで \approx のタブ補完を使用して入力される)を使用するか、直接 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

相対および絶対の許容誤差は、 比較の後に isapproxrtol および 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.@inferredMacro
@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}
[...]
source
Test.@test_deprecatedMacro
@test_deprecated [pattern] expression

--depwarn=yes の場合、expression が非推奨警告を発することをテストし、expression の値を返します。ログメッセージ文字列は、デフォルトで r"deprecated"i に対して一致します。

--depwarn=no の場合、単に expression を実行した結果を返します。--depwarn=error の場合、ErrorException がスローされることを確認します。

# julia 0.7 で非推奨
@test_deprecated num2hex(1)

# 戻り値は @test でチェーンすることでテストできます:
@test (@test_deprecated num2hex(1)) == "0000000000000001"
source
Test.@test_warnMacro
@test_warn msg expr

exprを評価した結果がmsg文字列を含むか、msg正規表現に一致するstderr出力を生成するかどうかをテストします。msgがブール関数の場合、msg(output)trueを返すかどうかをテストします。msgがタプルまたは配列の場合、エラー出力がmsgの各アイテムを含む/一致するかどうかをチェックします。exprを評価した結果を返します。

エラー出力の不在を確認するには、@test_nowarnも参照してください。

注意: @warnによって生成された警告は、このマクロでテストすることはできません。代わりに@test_logsを使用してください。

source
Test.@test_nowarnMacro
@test_nowarn expr

exprを評価した結果が空のstderr出力(警告やその他のメッセージなし)であるかどうかをテストします。exprを評価した結果を返します。

注意: @warnによって生成された警告の不在はこのマクロではテストできません。代わりに@test_logsを使用してください。

source

Broken Tests

テストが一貫して失敗する場合は、@test_brokenマクロを使用するように変更できます。これにより、テストが失敗し続ける場合はBrokenとしてマークされ、テストが成功した場合はErrorを介してユーザーに警告します。

Test.@test_brokenMacro
@test_broken ex
@test_broken f(args...) key=val ...

現在一貫して失敗するべきテストを示します。式 exfalse に評価されるか、例外を引き起こすことをテストします。これが行われた場合は 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)
source

@test_skipは、評価なしでテストをスキップするためにも利用可能ですが、スキップされたテストをテストセットの報告にカウントします。テストは実行されませんが、Broken Resultを返します。

Test.@test_skipMacro
@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)
source

Test result types

Test.ResultType
Test.Result

すべてのテストは結果オブジェクトを生成します。このオブジェクトは、テストがテストセットの一部であるかどうかに応じて、保存される場合とされない場合があります。

source
Test.PassType
Test.Pass <: Test.Result

テスト条件は真でした。すなわち、式は真に評価されるか、正しい例外がスローされました。

source
Test.FailType
Test.Fail <: Test.Result

テスト条件が偽でした。つまり、式が偽に評価されたか、正しい例外がスローされませんでした。

source
Test.ErrorType
Test.Error <: Test.Result

テスト条件は例外のため評価できなかったか、Bool 以外の何かに評価されました。@test_broken の場合、予期しない Pass Result が発生したことを示すために使用されます。

source
Test.BrokenType
Test.Broken <: Test.Result

テスト条件は、壊れたテストの期待される(失敗した)結果であるか、または明示的に @test_skip でスキップされたものである。

source

Creating Custom AbstractTestSet Types

パッケージは、recordおよびfinishメソッドを実装することで独自のAbstractTestSetサブタイプを作成できます。サブタイプは、説明文字列を受け取る1引数のコンストラクタを持ち、オプションはキーワード引数として渡されるべきです。

Test.recordFunction
record(ts::AbstractTestSet, res::Result)

テストセットに結果を記録します。この関数は、含まれている @test マクロが完了するたびに @testset インフラストラクチャによって呼び出され、テスト結果(Error である可能性があります)が渡されます。また、テストブロック内で例外がスローされたが @test コンテキストの外である場合にも Error と共に呼び出されます。

source
Test.finishFunction
finish(ts::AbstractTestSet)

与えられたテストセットに必要な最終処理を行います。これは、テストブロックが実行された後に @testset インフラストラクチャによって呼び出されます。

カスタム AbstractTestSet サブタイプは、テスト結果のツリーに自分自身を追加するために、親(もしあれば)の record を呼び出す必要があります。これは次のように実装されるかもしれません:

if get_testset_depth() != 0
    # このテストセットを親テストセットに添付する
    parent_ts = get_testset()
    record(parent_ts, self)
    return self
end
source

Test は、実行される際にネストされたテストセットのスタックを維持する責任を負いますが、結果の蓄積は AbstractTestSet サブタイプの責任です。このスタックには get_testset および get_testset_depth メソッドを使用してアクセスできます。これらの関数はエクスポートされていないことに注意してください。

Test.get_testsetFunction
get_testset()

タスクのローカルストレージからアクティブなテストセットを取得します。アクティブなテストセットがない場合は、フォールバックのデフォルトテストセットを使用します。

source
Test.get_testset_depthFunction
get_testset_depth()

デフォルトのテストセットを除く、アクティブなテストセットの数を返します。

source

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)
        return ts
    end

    # so the results are printed if we are at the top level
    Test.print_test_results(ts)
    return 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.get_test_countsを定義します。これは次のようになります:

using Test: AbstractTestSet, Pass, Fail, Error, Broken, get_test_counts, TestCounts, format_duration

function Test.get_test_counts(ts::CustomTestSet)
    passes, fails, errors, broken = 0, 0, 0, 0
    # cumulative results
    c_passes, c_fails, c_errors, c_broken = 0, 0, 0, 0

    for t in ts.results
        # count up results
        isa(t, Pass)   && (passes += 1)
        isa(t, Fail)   && (fails  += 1)
        isa(t, Error)  && (errors += 1)
        isa(t, Broken) && (broken += 1)
        # handle children
        if isa(t, AbstractTestSet)
            tc = get_test_counts(t)::TestCounts
            c_passes += tc.passes + tc.cumulative_passes
            c_fails  += tc.fails + tc.cumulative_fails
            c_errors += tc.errors + tc.cumulative_errors
            c_broken += tc.broken + tc.cumulative_broken
        end
    end
    # get a duration, if we have one
    duration = format_duration(ts)
    return TestCounts(true, passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration)
end
Test.TestCountsType
TestCounts

テストセットの結果を再帰的に収集するための状態を保持します。

フィールド:

  • customized: get_test_counts 関数がこのカウントオブジェクトのための AbstractTestSet に対してカスタマイズされたかどうか。カスタムメソッドが定義されている場合は、常にコンストラクタに true を渡してください。
  • passes: 合格した @test の呼び出しの数。
  • fails: 不合格の @test の呼び出しの数。
  • errors: エラーのある @test の呼び出しの数。
  • broken: 壊れた @test の呼び出しの数。
  • passes: 合格した @test の呼び出しの累積数。
  • fails: 不合格の @test の呼び出しの累積数。
  • errors: エラーのある @test の呼び出しの累積数。
  • broken: 壊れた @test の呼び出しの累積数。
  • duration: 該当する AbstractTestSet が実行された合計時間をフォーマットされた String として。
source
Test.get_test_countsFunction

" gettestcounts(::AbstractTestSet) -> TestCounts

テストセット内の各タイプのテスト結果の数を直接カウントし、子テストセット全体の合計を計算する再帰関数です。

カスタム AbstractTestSet は、この関数を実装して、合計をカウントし、DefaultTestSet とともに表示する必要があります。

カスタム TestSet に対してこれが実装されていない場合、印刷は失敗に対して x を報告し、所要時間に対して ?s にフォールバックします。

source
Test.format_durationFunction
format_duration(::AbstractTestSet)

テストセットが実行された期間を印刷するためのフォーマットされた文字列を返します。

定義されていない場合は、"?s"にフォールバックします。

source
Test.print_test_resultsFunction
print_test_results(ts::AbstractTestSet, depth_pad=0)

AbstractTestSetの結果をフォーマットされたテーブルとして出力します。

depth_padは、すべての出力の前に追加されるパディングの量を指します。

Test.finishの内部で呼び出され、finishされたテストセットが最上位のテストセットである場合。

source

Test utilities

Test.GenericArrayType

GenericArrayは、AbstractArrayインターフェースにプログラムする一般的な配列APIをテストするために使用でき、標準のArray型以外の配列型でも関数が機能することを保証します。

source
Test.GenericDictType

GenericDictは、AbstractDictインターフェースにプログラムする汎用辞書APIをテストするために使用でき、標準のDict型以外の関連型でも関数が機能することを確認します。

source
Test.GenericOrderType

GenericOrderは、APIが汎用順序型をサポートしているかどうかをテストするために使用できます。

source
Test.GenericSetType

GenericSetは、AbstractSetインターフェースにプログラムする一般的なセットAPIをテストするために使用でき、標準のSetおよびBitSetタイプ以外のセットタイプでも関数が機能することを確認します。

source
Test.GenericStringType

GenericStringは、AbstractStringインターフェースにプログラムする一般的な文字列APIをテストするために使用でき、標準のString型以外の文字列型でも関数が機能することを保証します。

source
Test.detect_ambiguitiesFunction
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 を参照してください。

Julia 1.8

allowed_undefineds は少なくとも Julia 1.8 が必要です。

source
Test.detect_unbound_argsFunction
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 が必要です。

source

Workflow for Testing Packages

前のセクションで利用可能なツールを使用して、パッケージを作成し、それにテストを追加するための潜在的なワークフローは以下の通りです。

Generating an Example Package

このワークフローでは、Exampleというパッケージを作成します:

pkg> generate Example
shell> cd Example
shell> mkdir test
pkg> activate .

Creating Sample Functions

パッケージをテストするための最も重要な要件は、テストする機能があることです。そのために、テストできるいくつかの簡単な関数を 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

export greet, simple_add, type_multiply

end

Creating a Test Environment

Exampleパッケージのルートから、testディレクトリに移動し、そこで新しい環境をアクティブにし、Testパッケージをその環境に追加します:

shell> cd test
pkg> activate .
(test) pkg> add Test

Testing Our Package

今、私たちは Example にテストを追加する準備ができました。テストを実行したいテストセットを含む runtests.jl というファイルを test ディレクトリ内に作成するのが標準的な手法です。そのファイルを 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 の2つの含まれたファイルを作成し、それらにいくつかのテストを追加する必要があります。

注意: test 環境の Project.tomlExample を追加する必要がなかったことに注目してください。これは、Julia のテストシステムの利点であり、あなたは read about more here を行うことができます。

Writing Tests for 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

Writing Tests for greeting_tests.jl

Test.jlを使用して、greeting_tests.jlに追加できるいくつかの例のテストは次のとおりです:

@testset "Testset 3" begin
    @test "Hello world!" == greet()
    @test_throws MethodError greet("Antonia")
end

Testing Our Package

テストと 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を使用すると、パッケージに対してより複雑なテストを追加できますが、これは理想的には開発者が自分で作成したパッケージのテストを始める方法を示すべきです。

Code Coverage

テスト中のコードカバレッジ追跡は、pkg> test --coverage フラグを使用して有効にできます(または、--code-coverage julia 引数を使用して低レベルで)。これは、julia-runtest GitHub アクションでデフォルトでオンになっています。

カバレッジを評価するには、ローカルでソースファイルの横に生成された .cov ファイルを手動で検査するか、CIでは julia-processcoverage GitHub アクションを使用します。

Julia 1.11

Julia 1.11以降、カバレッジはパッケージのプリコンパイルフェーズ中には収集されません。