💔
`@test_broken ..`よりも`@test .. broken=..`の方が良かった
これはJulia Advent Calendar 2023の5日目の記事です。
TL;DR
@test_broken hogehoge
よりも
@test hogehoge broken=true
の方が幸せになれるかも知れない。
Juliaのテストの書き方の復習
Juliaでは以下のようにテストが書けます。
-
@test ex
でex
がtrue
であることをテストする -
@testset
でグループ分け
@testset "arithmetic operations" begin
@test 1 + 1 == 2
@test 3 / 2 == 1.5
@test 3 ÷ 2 == 1
@test isnan(0/0)
end
- エラーをテストしたい場合は
@test_throws
を使う -
@test_throws
の最初の引数はエラーの型。
@testset "sqrt operations" begin
@test_throws DomainError sqrt(-1) # 複素数が欲しければ `sqrt(-1+0im)`と書けば良い
end
- 実装不備によってテストが失敗することをテストしたい場合は
@test_broken
を使う -
@test_broken
が成功(true
)ならエラーとして扱われる
my_sinc(x) = sin(x)/x
@test_broken my_sinc(0) == 1
以上の例を合わせてREPLで実行すると以下のような出力になります。
テスト結果の要約と実行時間の出力が見やすいですね。
ケース①
問題
Base.one
を自分で実装したmy_one
関数を作ってテストしてみましょう。
ただし、わざと間違えた定義で。
my_one(::Type{Float64}) = 1.0
my_one(::Type{Float32}) = 1.0 # `1f0`が正しい定義
@testset "sometimes broken" begin
@testset for T in (Float64, Float32) # `@testset`は`for`にも使える!
@test my_one(T) == 1
@test_broken my_one(T) isa T # `T`が`Float32`のときだけbroken
end
end
@test_broken
がT==Float64
のときにbrokenじゃないので、エラーになってしまいましたね。
状況整理:
-
for
文の中の@test_broken
なので、true
とfalse
両方のケースを扱う必要がある - つまり、
T===Float32
のときだけ@test_broken
で、T===Float64
のときは通常の@test
を使いたい
解決策
そこで@test hogehoge broken=..
ですよ!
これによってbroken
がtrue
のときは@test
が@test_broken
として扱われるようになります。
my_one(::Type{Float64}) = 1.0
my_one(::Type{Float32}) = 1.0
@testset "sometimes broken" begin
@testset for T in (Float64, Float32)
@test my_one(T) == 1
@test my_one(T) isa T broken=(T===Float32) # `T`が`Float32`のときだけbroken
end
end
エラーが発生しませんでした。
brokenとして扱うケースをtrue
/false
で指定できるので便利ですね。
ケース②
問題
自分で実装したmy_sin
関数の数値精度を確認するテストを書いてみましょう。
my_sin(x) = x-x^3/6+x^5/120 # テイラー展開の打ち切り
@testset "sometimes broken" begin
for x in range(-1,1,length=100)
@test_broken abs(my_sin(x) - sin(x)) < 0.0001 # 10/100 のケースでbroken
end
end
(長いエラー中略)
原点から離れるほど精度が落ちるので、その影響で左右5点ずつテストに通らなかったようですね。
解決策
ここでも@test hogehoge broken=..
ですよ!
my_sin(x) = x-x^3/6+x^5/120 # テイラー展開の打ち切り
@testset "sometimes broken" begin
for x in range(-1,1,length=100)
# テストに失敗するケースを明示できないので`broken`にテスト内容を直接記載すればOK
@test abs(my_sin(x) - sin(x)) < 0.0001 broken=!(abs(my_sin(x) - sin(x)) < 0.0001)
end
end
エラーが消えましたね。やったぜ
my_sin
を修正してbrokenじゃなくなった場合にbroken=..
を消し忘れる可能性もありますが、エラーになるよりはマシでしょう。
まとめ & 補足
-
@test
マクロはbroken
キーワード引数が便利。-
for
の中で部分的にbroken
になる場合に特に便利。 - このキーワード引数は Julia v1.7 以上でしか使えないことに注意。
-
-
公式ドキュメントで十分じゃなかったんですか?
- 公式ドキュメントには
@test 2 + 2 ≈ 5 atol=1 broken=false
や@test 2 + 2 ≈ 6 atol=1 broken=true
みたいな自明な例しか無く、broken
キーワード引数の本当の有り難みが伝わりにくい気がしました。 - なのでこの記事が存在します。
- 公式ドキュメントには
Discussion