こんにちは永遠のJVM初心者です。 Property-Based-TestingのShrinkingとはなんぞやがよくわかってなかったのでまとめます。
TL;DR
- Property-Based-TestingにおけるShrinkingとは
Property-Based-TestingにおけるShrinkingとは
jqwikの作者のブログを読んでる感じだと以下の点で重要とのこと
Unless you already have an inkling of what the issue might be, the number itself does not give you an obvious hint. Even the fact that it’s rather large might be a coincidence. At this point you will either add additional logging, introduce assertions or even start up the debugger to get more information about the problem at hand.
言ってしまえばpbtによる確認はrandomにgenerateされたものに過ぎないので、失敗した数値だけでてもよくわかんないよねー。 ロギングを追加したり、アサーションを導入したり、デバッガを起動しないと問題の原因はわからんよね。 なので「最も単純な」例をテスト結果で出してほしいよね。 この探索フェーズは、元のサンプルから開始し、それをだんだん小さくして再度propertyを確認しようとするので、Shrinking(縮小)と呼びます。
ということらしい。
Kotestでの例
こんな感じで超簡単な素数判定を考える。 これにPBTをかけると以下のような結果になる
ここでは 592298304
でfailになったあと最小の数字までshrinkingしてくれているのがわかる。
最終的に4までShrinkingしてくれるおかげで、あぁなるほど4はそもそも素数じゃないなというのがわかってそもそもこのテスト自体が正しくないことが
わかる。
KotestでのShrinkingの実装
現状でのInt型のShrinkerは以下のような実装らしい。
object IntShrinker : Shrinker<Int> { override fun shrink(value: Int): List<Int> = when (value) { 0 -> emptyList() 1, -1 -> listOf(0) else -> { val a = listOf(abs(value), value / 3, value / 2, value * 2 / 3) val b = (1..5).map { value - it }.reversed().filter { it > 0 } (a + b).distinct() .filterNot { it == value } } } }
うーむコメントが少ないのでlistOfのルールがよくわかんない。
多分 value/3
がfailしたときのルールかな...(^見た感じだとfailしたら3でわって言っている様子が見えるかも)
ちょっと自力で実装するにはもう少しコード読まないといけなそう
Shrinkingのルール変更
ShrinkingModeで設定可能
こんな感じ
checkAll(PropTestConfig(shrinkingMode = ShrinkingMode.Bounded(50)),Arb.int(2..Int.MAX_VALUE)){ i -> isPrimeNumber(i) shouldBe true }
やめたいときは ShrinkingMode.Off
を指定すればいいらしい。
まとめ
PBTのツールではShrinkingが大事というのはよくドキュメントに書かれていたんだけど、実際に使ってみることで重要性がわかりました。 kotestでは今回の例でつかった intだけでなく long, string, listなどでも使用可能なのでbuild-inの方を組み合わせるだけでも十分テストでつかえそうです。 先人に感謝