Unit Testing
Testing Base Julia
Juliaは急速に開発されており、複数のプラットフォームでの機能を検証するための広範なテストスイートがあります。ソースからJuliaをビルドする場合、make test
を使用してこのテストスイートを実行できます。バイナリインストールの場合、Base.runtests()
を使用してテストスイートを実行できます。
Base.runtests
— FunctionBase.runtests(tests=["all"]; ncores=ceil(Int, Sys.CPU_THREADS / 2),
exit_on_error=false, revise=false, [seed])
tests
にリストされたJuliaのユニットテストを、ncores
プロセッサを使用して実行します。tests
は文字列または文字列の配列のいずれかです。exit_on_error
がfalse
の場合、1つのテストが失敗しても、他のファイルの残りのテストは実行されます。exit_on_error == true
の場合は、それらは破棄されます。revise
がtrue
の場合、テストを実行する前にBase
や標準ライブラリへの変更を読み込むためにRevise
パッケージが使用されます。キーワード引数を介してシードが提供されると、それはテストが実行されるコンテキストでグローバルRNGのシードとして使用されます。そうでない場合、シードはランダムに選ばれます。
Basic Unit Tests
Test
モジュールはシンプルな 単体テスト 機能を提供します。単体テストは、結果が期待通りであることを確認することで、コードが正しいかどうかを確認する方法です。変更を加えた後でもコードが正常に動作することを保証するのに役立ち、完成時にコードが持つべき動作を指定する方法として開発中に使用できます。また、adding tests to your Julia Package のドキュメントも参照することをお勧めします。
単純なユニットテストは、@test
および @test_throws
マクロを使用して実行できます:
Test.@test
— Macro@test ex
@test f(args...) key=val ...
@test ex broken=true
@test ex skip=true
式 ex
が true
に評価されることをテストします。@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
引数には任意のキーを使用できますが、broken
と skip
は @test
の文脈で特別な意味を持ちます:
broken=cond
は、テストが通るべきだが現在はcond==true
のときに一貫して失敗することを示します。式ex
がfalse
に評価されるか、例外を引き起こすテストです。これが成立すればBroken
Result
を返し、式がtrue
に評価されればError
Result
を返します。通常の@test ex
はcond==false
のときに評価されます。skip=cond
は、実行されるべきではないが、cond==true
のときにテストサマリー報告にBroken
として含めるべきテストをマークします。これは、断続的に失敗するテストや、まだ実装されていない機能のテストに便利です。通常の@test ex
はcond==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
broken
および skip
キーワード引数は、少なくとも Julia 1.7 が必要です。
```
Test.@test_throws
— Macro@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)
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
通常、大量のテストが使用されて、関数がさまざまな入力に対して正しく動作することを確認します。テストが失敗した場合、デフォルトの動作は即座に例外をスローすることです。しかし、通常は、テストの残りを最初に実行して、テストされているコードにどれだけのエラーがあるかをよりよく把握することが望ましいです。
@testset
は、その中でテストを実行する際に独自のローカルスコープを作成します。
@testset
マクロは、テストを セット にグループ化するために使用できます。テストセット内のすべてのテストが実行され、テストセットの最後に要約が印刷されます。テストのいずれかが失敗した場合、またはエラーのために評価できなかった場合、テストセットは TestSetException
をスローします。
Test.@testset
— Macro@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
を介してグローバルに設定することもできます。
@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 "三角関数の恒等式" 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つの大きなオブジェクトに対して関連するテストのセットを実行する際に便利で、個々のテストが失敗したときにこの大きなオブジェクトを印刷することが望ましい場合に役立ちます。透明なテストセットは、テストセット階層に追加のネストレベルを導入せず、親テストセットに直接渡されます(コンテキストオブジェクトは失敗したテストに追加されます)。
@testset let
は少なくとも Julia 1.9 が必要です。
複数の 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: テスト中にエラーが発生しました
Test.TestSetException
— TypeTestSetException
テストセットが終了し、すべてのテストが合格しなかったときにスローされます。
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_logs
— Macro@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
を参照してください。
Test.TestLogger
— TypeTestLogger(; 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
Test.LogRecord
— TypeLogRecord
単一のログイベントの結果を保存します。フィールド:
level
: ログメッセージのLogLevel
message
: ログメッセージのテキスト内容_module
: ログイベントのモジュールgroup
: ロギンググループ(デフォルトでは、ログイベントを含むファイルの名前)id
: ログイベントのIDfile
: ログイベントを含むファイルline
: ログイベントのファイル内の行kwargs
: ログイベントに渡された任意のキーワード引数
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
相対および絶対の許容誤差は、≈
比較の後に 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
— Macro@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
— Macro@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"
Test.@test_warn
— Macro@test_warn msg expr
expr
を評価した結果がmsg
文字列を含むか、msg
正規表現に一致するstderr
出力を生成するかどうかをテストします。msg
がブール関数の場合、msg(output)
がtrue
を返すかどうかをテストします。msg
がタプルまたは配列の場合、エラー出力がmsg
の各アイテムを含む/一致するかどうかをチェックします。expr
を評価した結果を返します。
エラー出力の不在を確認するには、@test_nowarn
も参照してください。
注意: @warn
によって生成された警告は、このマクロでテストすることはできません。代わりに@test_logs
を使用してください。
Test.@test_nowarn
— Macro@test_nowarn expr
expr
を評価した結果が空のstderr
出力(警告やその他のメッセージなし)であるかどうかをテストします。expr
を評価した結果を返します。
注意: @warn
によって生成された警告の不在はこのマクロではテストできません。代わりに@test_logs
を使用してください。
Broken Tests
テストが一貫して失敗する場合は、@test_broken
マクロを使用するように変更できます。これにより、テストが失敗し続ける場合はBroken
としてマークされ、テストが成功した場合はError
を介してユーザーに警告します。
Test.@test_broken
— Macro@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
— Macro@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 types
Test.Result
— TypeTest.Result
すべてのテストは結果オブジェクトを生成します。このオブジェクトは、テストがテストセットの一部であるかどうかに応じて、保存される場合とされない場合があります。
Test.Pass
— TypeTest.Pass <: Test.Result
テスト条件は真でした。すなわち、式は真に評価されるか、正しい例外がスローされました。
Test.Fail
— TypeTest.Fail <: Test.Result
テスト条件が偽でした。つまり、式が偽に評価されたか、正しい例外がスローされませんでした。
Test.Error
— TypeTest.Error <: Test.Result
テスト条件は例外のため評価できなかったか、Bool
以外の何かに評価されました。@test_broken
の場合、予期しない Pass
Result
が発生したことを示すために使用されます。
Test.Broken
— TypeTest.Broken <: Test.Result
テスト条件は、壊れたテストの期待される(失敗した)結果であるか、または明示的に @test_skip
でスキップされたものである。
Creating Custom AbstractTestSet
Types
パッケージは、record
およびfinish
メソッドを実装することで独自のAbstractTestSet
サブタイプを作成できます。サブタイプは、説明文字列を受け取る1引数のコンストラクタを持ち、オプションはキーワード引数として渡されるべきです。
Test.record
— Functionrecord(ts::AbstractTestSet, res::Result)
テストセットに結果を記録します。この関数は、含まれている @test
マクロが完了するたびに @testset
インフラストラクチャによって呼び出され、テスト結果(Error
である可能性があります)が渡されます。また、テストブロック内で例外がスローされたが @test
コンテキストの外である場合にも Error
と共に呼び出されます。
Test.finish
— Functionfinish(ts::AbstractTestSet)
与えられたテストセットに必要な最終処理を行います。これは、テストブロックが実行された後に @testset
インフラストラクチャによって呼び出されます。
カスタム AbstractTestSet
サブタイプは、テスト結果のツリーに自分自身を追加するために、親(もしあれば)の record
を呼び出す必要があります。これは次のように実装されるかもしれません:
if get_testset_depth() != 0
# このテストセットを親テストセットに添付する
parent_ts = get_testset()
record(parent_ts, self)
return self
end
Test
は、実行される際にネストされたテストセットのスタックを維持する責任を負いますが、結果の蓄積は AbstractTestSet
サブタイプの責任です。このスタックには get_testset
および get_testset_depth
メソッドを使用してアクセスできます。これらの関数はエクスポートされていないことに注意してください。
Test.get_testset
— Functionget_testset()
タスクのローカルストレージからアクティブなテストセットを取得します。アクティブなテストセットがない場合は、フォールバックのデフォルトテストセットを使用します。
Test.get_testset_depth
— Functionget_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)
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.TestCounts
— TypeTestCounts
テストセットの結果を再帰的に収集するための状態を保持します。
フィールド:
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
として。
Test.get_test_counts
— Function" gettestcounts(::AbstractTestSet) -> TestCounts
テストセット内の各タイプのテスト結果の数を直接カウントし、子テストセット全体の合計を計算する再帰関数です。
カスタム AbstractTestSet
は、この関数を実装して、合計をカウントし、DefaultTestSet
とともに表示する必要があります。
カスタム TestSet
に対してこれが実装されていない場合、印刷は失敗に対して x
を報告し、所要時間に対して ?s
にフォールバックします。
Test.format_duration
— Functionformat_duration(::AbstractTestSet)
テストセットが実行された期間を印刷するためのフォーマットされた文字列を返します。
定義されていない場合は、"?s"
にフォールバックします。
Test.print_test_results
— Functionprint_test_results(ts::AbstractTestSet, depth_pad=0)
AbstractTestSet
の結果をフォーマットされたテーブルとして出力します。
depth_pad
は、すべての出力の前に追加されるパディングの量を指します。
Test.finish
の内部で呼び出され、finish
されたテストセットが最上位のテストセットである場合。
Test utilities
Test.GenericArray
— TypeGenericArray
は、AbstractArray
インターフェースにプログラムする一般的な配列APIをテストするために使用でき、標準のArray
型以外の配列型でも関数が機能することを保証します。
Test.GenericDict
— TypeGenericDict
は、AbstractDict
インターフェースにプログラムする汎用辞書APIをテストするために使用でき、標準のDict
型以外の関連型でも関数が機能することを確認します。
Test.GenericOrder
— TypeGenericOrder
は、APIが汎用順序型をサポートしているかどうかをテストするために使用できます。
Test.GenericSet
— TypeGenericSet
は、AbstractSet
インターフェースにプログラムする一般的なセットAPIをテストするために使用でき、標準のSet
およびBitSet
タイプ以外のセットタイプでも関数が機能することを確認します。
Test.GenericString
— TypeGenericString
は、AbstractString
インターフェースにプログラムする一般的な文字列APIをテストするために使用でき、標準のString
型以外の文字列型でも関数が機能することを保証します。
Test.detect_ambiguities
— Functiondetect_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
— Functiondetect_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 が必要です。
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.jl
と greeting_tests.jl
の2つの含まれたファイルを作成し、それらにいくつかのテストを追加する必要があります。
注意:
test
環境のProject.toml
にExample
を追加する必要がなかったことに注目してください。これは、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以降、カバレッジはパッケージのプリコンパイルフェーズ中には収集されません。