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, propagate_project=true, [seed], [julia_args::Cmd])testsにリストされたJuliaユニットテストを、ncoresプロセッサを使用して実行します。testsは文字列または文字列の配列のいずれかです。exit_on_errorがfalseの場合、1つのテストが失敗しても、他のファイルの残りのテストは引き続き実行されます。exit_on_error == trueの場合は、それ以外のテストは破棄されます。reviseがtrueの場合、テストを実行する前にBaseや標準ライブラリへの変更を読み込むためにReviseパッケージが使用されます。propagate_projectがtrueの場合、現在のプロジェクトがテスト環境に伝播されます。キーワード引数を介してシードが提供されると、それはテストが実行されるコンテキストでグローバルRNGのシードとして使用されます。そうでない場合、シードはランダムに選ばれます。引数julia_argsは、テストプロセスにカスタムjuliaコマンドラインフラグを渡すために使用できます。
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に評価されるか、例外を引き起こすテストです。これが成立すればBrokenResultを返し、式がtrueに評価されればErrorResultを返します。通常の@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 PassedTest.@test_throws — Macro@test_throws exception expr式 expr が exception をスローすることをテストします。例外は、表示されるエラーメッセージに含まれる型、文字列、正規表現、または文字列のリストを指定することができ、マッチング関数や値(フィールドを比較して等価性をテストされます)を指定することもできます。@test_throws はトレーリングキーワード形式をサポートしていないことに注意してください。
例
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: MethodErrorWorking with Test Sets
通常、大量のテストが使用されて、関数がさまざまな入力に対して正しく動作することを確認します。テストが失敗した場合、デフォルトの動作は即座に例外をスローすることです。しかし、通常は、テストの残りを最初に実行して、テストされているコードにどれだけのエラーがあるかをよりよく把握することが望ましいです。
@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; endbegin/end または関数呼び出しを使用した場合
@testset が使用されると、begin/end または単一の関数呼び出しとともに、マクロは与えられた式を評価するための新しいテストセットを開始します。
カスタムテストセットタイプが指定されていない場合、デフォルトで DefaultTestSet が作成されます。DefaultTestSet はすべての結果を記録し、Fail または Error がある場合、最上位(非ネスト)テストセットの最後で例外をスローし、テスト結果の要約を表示します。
任意のカスタムテストセットタイプ(AbstractTestSet のサブタイプ)を指定でき、ネストされた @testset の呼び出しにも使用されます。指定されたオプションは、与えられたテストセットにのみ適用されます。デフォルトのテストセットタイプは、以下のオプションを受け入れます:
verbose::Bool:trueの場合、すべてのネストされたテストセットが合格している場合でも、結果の要約が表示されます(デフォルトはfalse)。showtiming::Bool:trueの場合、表示される各テストセットの所要時間が表示されます(デフォルトはtrue)。failfast::Bool:trueの場合、テストの失敗またはエラーが発生すると、テストセットとその子テストセットは即座に戻ります(デフォルトはfalse)。これは、環境変数JULIA_TEST_FAILFASTを介してグローバルに設定することもできます。rng::Random.AbstractRNG: 指定された乱数生成器(RNG)をテストセットのグローバルなものとして使用します。rngはcopy!可能でなければなりません。このオプションは、グローバルRNGの状態に依存する確率的テストの失敗をローカルに再現するのに役立ちます。
説明文字列はループインデックスからの補間を受け入れます。説明が提供されていない場合、変数に基づいて構築されます。関数呼び出しが提供されている場合、その名前が使用されます。明示的な説明文字列はこの動作を上書きします。
デフォルトでは、@testset マクロはテストセットオブジェクト自体を返しますが、この動作は他のテストセットタイプでカスタマイズできます。for ループが使用される場合、マクロは finish メソッドの戻り値のリストを収集して返します。デフォルトでは、これは各反復で使用されるテストセットオブジェクトのリストを返します。
@testset の本体の実行前に、copy!(Random.default_rng(), rng) への暗黙の呼び出しが行われます。ここで rng は現在のタスクのRNG、または rng オプションを介して渡されたRNGの値です。さらに、ボディの実行後、グローバルRNGの状態は @testset の前の状態に復元されます。これは、失敗時の再現性を容易にし、グローバルRNGの状態に対する副作用に関係なく @testset のシームレスな再配置を可能にすることを目的としています。
rng オプションで変更されない限り、すべてのネストされたテストセットの最初に同じRNGが設定されます。テストセットに失敗があるときに画面に表示されるRNGは、最も外側のテストセットのグローバルRNGであり、内部のテストセットがユーザーによって手動で設定された異なるRNGを持っていても同様です。
例
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 begin の特別な暗黙のワールドエイジのインクリメント
@testset begin 内のワールドエイジは、各ステートメントの後に暗黙的にインクリメントされます。これは通常のトップレベルコードの動作と一致しますが、通常の begin/end ブロックの動作とは一致しません。すなわち、ワールドエイジに関しては、@testset begin は begin/end ブロックの本体がトップレベルで書かれているかのように振る舞います。
例
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] expressionexpressionによって生成されたログレコードのリストをcollect_test_logsを使用して収集し、それらがlog_patternsのシーケンスと一致することを確認し、expressionの値を返します。keywordsはログレコードの簡単なフィルタリングを提供します:min_levelキーワードはテストのために収集される最小ログレベルを制御し、match_modeキーワードは一致の方法を定義します(デフォルトの:allはすべてのログとパターンがペアで一致することを確認します;:anyを使用すると、パターンがシーケンスのどこかで少なくとも1回一致することを確認します)。
最も便利なログパターンは、形式が(level,message)の単純なタプルです。異なる数のタプル要素を使用して、AbstractLoggerにhandle_message関数を介して渡される引数に対応する他のログメタデータと一致させることができます:(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 PassedTest.LogRecord — TypeLogRecord単一のログイベントの結果を格納します。フィールド:
level: ログメッセージのLogLevelmessage: ログメッセージのテキスト内容_module: ログイベントのモジュールgroup: ロググループ(デフォルトでは、ログイベントを含むファイルの名前)id: ログイベントのIDfile: ログイベントを含むファイルline: ログイベントのファイル内の行kwargs: ログイベントに渡された任意のキーワード引数
Other Test Macros
浮動小数点値の計算は不正確な場合があるため、@test a ≈ b を使用して近似等価性チェックを行うことができます(ここで ≈ は \approx のタブ補完で入力されるもので、isapprox 関数です)または 4d61726b646f776e2e436f64652822222c20226973617070726f782229_40726566 を直接使用することができます。
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 = :>::Core.Const(>)
│ %2 = (%1)(a, 1)::Bool
└── goto #3 if not %2
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 exprexprを評価した結果がmsg文字列を含むか、msg正規表現に一致するstderr出力を生成するかどうかをテストします。msgがブール関数の場合、msg(output)がtrueを返すかどうかをテストします。msgがタプルまたは配列の場合、エラー出力がmsgの各アイテムを含む/一致するかどうかをチェックします。exprを評価した結果を返します。
エラー出力の不在を確認するには、@test_nowarnも参照してください。
注意: @warnによって生成された警告は、このマクロでテストすることはできません。代わりに@test_logsを使用してください。
Test.@test_nowarn — Macro@test_nowarn exprexprを評価した結果が空の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
endTest は、実行される際にネストされたテストセットのスタックを維持する責任を負いますが、結果の蓄積は 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)
endTest.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は、標準のDict型以外の連想型で関数が動作することを確認するために、AbstractDictインターフェースにプログラムする一般的な辞書APIをテストするために使用できます。
Test.GenericOrder — TypeGenericOrderは、APIが一般的な順序付き型をサポートしているかどうかをテストするために使用できます。
Test.GenericSet — TypeGenericSetは、標準のSetおよびBitSetタイプ以外のセットタイプで関数が動作することを確認するために、AbstractSetインターフェースにプログラムする一般的なセットAPIをテストするために使用できます。
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 を参照してください。
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 に関する警告が抑制されます。
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
export greet, simple_add, type_multiply
function greet()
"Hello world!"
end
function simple_add(a, b)
a + b
end
function type_multiply(a::Float64, b::Float64)
a * b
end
endCreating a Test Environment
Exampleパッケージのルートから、testディレクトリに移動し、そこで新しい環境をアクティブにし、Testパッケージをその環境に追加します:
shell> cd test
pkg> activate .
(test) pkg> add TestTesting Our Package
今、私たちは 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これらの2つの含まれるファイル、math_tests.jl と greeting_tests.jl を作成し、それらにいくつかのテストを追加する必要があります。
注意:
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)
endWriting Tests for greeting_tests.jl
Test.jlの知識を活用して、greeting_tests.jlに追加できるいくつかのテストの例を以下に示します:
@testset "Testset 3" begin
@test "Hello world!" == greet()
@test_throws MethodError greet("Antonia")
endTesting 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 アクションを使用します。