Juliaのユニットテスト
Julia Advent Calendar 9日目の記事です。なんとか途切れさせないでいきましょう。
Juliaと言えばユニットテストが書きやすい言語として有名です[要出典]。 何故書きやすいのかといえば、もちろんマクロがあるからです。 マクロのおかげで、よく分からないアサーションのコードをたくさん覚えなくて済みます。 この記事では、そのへんの理由や、Juliaでの最近のユニットテストの書き方について説明しようと思います。
Juliaのユニットテストの良いところ
以下のテストコードはsum
という関数の動作をテストしている想定です:
julia> using Base.Test # ok julia> @test sum([1,2,3]) == 6 # not ok julia> @test sum([1,2,3]) == 7 ERROR: test failed: 6 == 7 in expression: sum([1,2,3]) == 7 in error at /usr/local/julia/v0.4/lib/julia/sys.dylib in default_handler at test.jl:30 in do_test at test.jl:53 # ok julia> @test isa(sum([1,2,3]), Int) # not ok julia> @test isa(sum([1,2,3]), Float64) ERROR: test failed: isa(sum([1,2,3]),Float64) in expression: isa(sum([1,2,3]),Float64) in error at /usr/local/julia/v0.4/lib/julia/sys.dylib in default_handler at test.jl:30 in do_test at test.jl:53
ここで使われているのは@test
というマクロです。
テストが失敗している方に注目していただきたのですが、エラーメッセージにtest failed: 6 == 7
やtest failed: isa(sum([1,2,3]),Float64)
のように、どの条件が失敗したのかが分かりやすく現れています。これは、@test
マクロがテストの条件式をとり、その式自体を表示できるためです。特に、==
などの二項演算子の場合は、左辺の値 sum([1,2,3])
の結果もエラーメッセージに表示してくれていることに気をつけてください。
マクロのない言語だと、テストするだけでもアサーションの関数が山盛りになってしまいます。
例えば、Pythonのunittestだと、assertEqual(first, second, msg=None)
やassertIsInstance(obj, cls, msg=None)
のような、ある条件を判定するアサーション関数がたくさん用意されています。ちょっとコレを全部覚えるのは辛いですし、全部assertTrue(expr, msg=None)
を使うとテストがどう失敗したのかが分かりづらくなってしまいます。
モダンなユニットテストの方法
とは言え、@test
マクロだけではちょっと機能が不足している面もあるでしょう。現在のリリース版v0.4系に付属しているBase.Test
には、関係のあるテストをまとめる機能がありません。そのため、テストがフラットで把握しにくい構造になってしまいます。
次期バージョンのv0.5ではBase.Test
が一新され、より構造化しやすくなっています。v0.5では@testset
というマクロが新たに追加され、テストをまとめることができるようになりました。
これは、@testset ["description"] begin ... end
のように使い、テストの説明と複数のテストをまとめるブロックを取ることができるようになっています。さらに、テストセットの結果はまとめて報告されます:
julia> using Base.Test julia> @testset "sum" begin @test sum([1,2,3]) == 6 @test isa(sum([1,2,3]), Int) @test sum(1:3) == 6 end Test Summary: | Pass Total sum | 3 3 Base.Test.DefaultTestSet("sum",Any[Test Passed Expression: sum([1,2,3]) == 6 Evaluated: 6 == 6,Test Passed Expression: isa(sum([1,2,3]),Int),Test Passed Expression: sum(1:3) == 6 Evaluated: 6 == 6],false)
実は、テスト結果のサマリーには色もつくようになりました。
さらに、@testset
は以下のように何段でもネストできます:
@testset "basic functions" begin @testset "sum" begin @test ... @test ... end @testset "mean" begin @test ... @test ... end ... end
@testset
がブロックとして取れるのはbegin ... end
だけでなく、for ... end
も取ることができます。
したがって、パラメータa
を変えながらテストをしたい場合などは@testset for a in 1:10 ... end
とすればよいでしょう。
実はこの機能はちょっと前まで@testloop
として提供されていたのですが、@testset
と統合してはどうかと提案したらすんなり受け入れられて現在はそのようになっています。Juliaってオープンですね(´ε` )
他にも、テストのレポートの仕方を変えるなど様々な拡張が可能になりました。 詳しい仕様は、v0.5のドキュメントを参照してください。
v0.5の便利なユニットテストを今使う
v0.5からは標準で@testset
が使えるようになるわけですが、やっぱり今v0.4で使いたいですね。
そのためには、BaseTestNext.jlを使いましょう。
以下のように書けば、v0.4向けのパッケージでも次世代のBase.Test
を使うことができます。
if VERSION >= v"0.5-" using Base.Test else using BaseTestNext const Test = BaseTestNext end