プロパティベーステスト
Property-based testing
PBT
関数の持つ一般的な性質(プロパティ)を定義し、ランダムに生成した多数の入力に対してチェックするテスト手法
既存のテスト(Example-based testing: EBT)の問題点
開発者が、プログラムへの入力値と期待される出力値(Example)ペアを定義する
問題点
テストケースを作成するのは開発者なので、無意識にテストがパスするものになりやすい
また、テストに対する責任を開発者が負うと、テストの管理や修正に注力することになる
https://scrapbox.io/files/67d37509d885178b8fe2e1a6.png
https://scrapbox.io/files/67d3751c7381b419d5131306.png
PBT による解決
開発者が、いかなる入力値を与えても常に成り立つべき(不変条件 を満たす)ルール(プロパティ)を定義 する
EBT ではこの不変条件を 間接的に 示そうとするが限界がある
warning.icon EBT と PBT は相反するものではない
補完し合う関係性
EBT: Known known → Known known
「コードが自分たちの想定した通りに実行されるかチェックする」ための助けになる
リグレッション の検知にも役立つ
PBT: Known unknown → Known known
「プログラムがどのように振る舞うかを探索し、何ができて何ができないのか、自分たちの想定が正しいかをチェックする」ための助けになる
そのため、PBT が失敗した場合はテストが失敗する理由が明確ではないので、「本当にバグなのか?」または「そもそもそのプロパティが正しいのか?」を判断することをよく求められる
これにより「本当に満たすべき仕様は何か?」を考え直す機会が増える
https://scrapbox.io/files/67d375bcc6907206b68028a1.png
PBT では、一般的に フレームワーク を用いることが多い
このフレームワークの多くでは、以下のような機能を持つ
収縮(Shrinking): テストが失敗する入力値の中で、最小限(最も単純な形)のものを探し出す機能
通常 ゼロ値(数値だと 0(0.0)、リストだと空リスト)に向かって収縮するが、カスタマイズすることも可能
e.g. Proper: ?SHRINK / ?LETSHIRINK
ジェネレータ(Generator): テストの入力データをランダムに生成する機能
自身で独自のジェネレータを作成することも可能
標的型プロパティ(Targeted Properties)
通常のプロパティは各テストケースのデータ生成処理は独立している
一方、標的型プロパティは後続のデータ生成処理に対して影響を与えることができる
これにより、ジェネレータをチューニングすることなく、単純なジェネレータで狙ったデータを生成することが可能になる
具体的なフレームワーク
FP
Haskell: QuickCheck
Erlang: PropEr
Elixir: PropCheck
Clojure: test.check
FP 以外
TypeScript: fast-check
Go: Rapid / GOPTER
Rust: Proptest
Ruby: pbt / Rantly
Python: Hypothesis
プロパティには大きく 2 つの種類がある
ステートレスプロパティ(状態を持たない)
ステートフルプロパティ(状態を持つ)
プロパティを見つける方法
参考
実践プロパティベーステスト ― PropErとErlang/Elixirではじめよう
https://speakerdeck.com/twada/intro-to-property-based-testing