2024年初アウトプットはしずみーになりました。
あけましておめでとうございます、今年も知的好奇心の赴くままアウトプットをしていきたいと思います。
去年から読み進めていた「実践プロパティベーステスト」を読み終えたので感想を書いておこうと思います。
読んだモチベーション
去年「単体テストの考え方/使い方」を読んで以降「質の良い単体テストを書きたい」というモチベーションでキャッチアップを続けている。
単体テストを書きやすいようにドメイン部分をシステムから隔離し、実装するテクニックというか勘所的なものが知りたくてエヴァンス本でくじけたDDDの学習をしたり。
単体テストが書きやすい関数というのは極論、関数型プログラミングにおける純粋関数であるため関数型プログラミングを再度学ぼうとElmに入門してみたり。
「実践プロパティベーステスト」を読んだのもその延長。実際、買うつもりはなかったのだけどFindyのオンラインイベントを視聴して、プロパティベーステストを書くには知識と経験とテクニックが必要だと感じ、日本語でその情報を取得できるのは本書だけだと感じたため購入。
プロパティベーステストに対する誤解
これはわたしの誤解。
プロパティベーステストは関数型プログラミングの世界で使われることが多く、副作用のない純粋関数に対して無数の入力パターンを試すことのできるテスト手法。通常の単体テスト(これを事例ベーステスト EBTというらしい)よりもテストで網羅できるパターンが遥かに多いので上位のテスト手法であり、可能な限りプロパティベーステスト(PBT)を書くようにするのが理想。
と思っていたがこれは誤りです。Findyのイベントでtwadaさんが言ってたことだがわたしたちが通常テストと呼ぶものは実際にやってることとニュアンスに違いがありどちらかと言えばChecking。(TDD本の最後の方にここらへん書いてあったような気がしなくもない)。PBTがやっているのは「Explorer(探索)」。テストという行為はこれら二つを掛け合わせ「Test = Checking + Explorer」となる。
だそうで、EBTとPBTは共に補完し合う関係のためPBTがEBTに置き換わるということはない。
EBTは「既にわかっている事象をテストする」。PBTは「わかっていないことをテストする」。既知の仕様を網羅するためにEBTをしっかりと書き、さらにまだ見ぬバグを探索するためにPBTを書くというのが理想的。
プロパティの書き方
本書を読む前に何度かプロパティベーステストを書いたことはある。KotlinのテスティングフレームワークのKotestの機能の一部として使ってみたり、Elm書いた時にさらっと書いてみたり。ただ、書いたはいいがまったくピンとこず、テストできてるのかよくわらなかった。
これは「プロパティの書き方がわからなかった」からだと思う。PBTはテスト対称の処理結果をアサーションする必要があるが、そのアサーションが結局テスト対称のコードとほとんど一緒になってしまうと、まったくテストしてないことになる。何も知らずにプロパティを書き始めるとこのようにテスト対処と同じような処理を書いてしまい「これテスト書く意味ある?」みたいな気持ちになってしまうのです。
本書では「モデル化」「事例テストの汎化」「不変条件」「対称プロパティ」といったプロパティを書くためのテクニックを教えてくれる。この情報をどこかでキャッチアップするのは特に日本語では困難なためこれらのテクニックを知りたいと感じた方は本書を購入するしかないでしょう。
ステートフルプロパティ
PBTには状態をもたない「ステートレスプロパティ」と状態を持つ「ステートフルプロパティ」が存在する。PBTで調べて出てくるテスト例はほとんどがステートレスプロパティを使用したテストだろう。
twadaさんも言っていたがステートフルプロパティによるPBTの書き方の詳細を知ることができるのは貴重。
GoとPBT
わたしは最近Goを書くことが多いので本書のサンプルもGoで書き直して読み進めた。GoでPBTをやる場合、gopterとrapidというOSSの二択。gopterの方が先で高機能、rapidは後発でシンプルさを推している。今回はrapidを使用してみたが書き心地は悪くなかった。できたらせっかくなので比較情報をzennの記事かスクラップにまとめときたい。
rapidでもプリミティブの値生成するデフォルトのジェネレーターは揃ってるし、カスタムジェネレーターを定義できる関数も用意されている。本書で紹介されるようなジェネレーターのメトリクスを集計するような機能や収縮の設定をいじったりはrapidではできない。収縮の設定はgopterにはあったかもしれない。
あと出現確率を設定できるような確率ジェネレーターや再帰ジェネレーターについては言語的な話な気がするがそういった本書で紹介されるような機能もrapidでは用意されていない。これはgopterにもたぶんない。
全体的な感想
むずい。わかってたけど本書のサンプルコードはErlangとElixirで書かれている。雰囲気でわかるかなと思ったけど全然頭に入ってこなかった。なんなら、二つとも静的言語だと思ってたら動的言語だったのね。なんか変だと思ったんだよ。それがわかったところで読むのは難しい。これはErlangのクセが強いのか、関数型を読み慣れていないのかどっちだろうか。買う人は頑張って読んでください。
あとは上述したように貴重な情報が詰まってる。プロパティの書き方とステートフルプロパティの書き方を学べるのはたぶん本書だけ。ほんとにPBTを使っていきたいと思ってるなら買った方がいいです。
GoでPBTを書くことについてはシンプルな利用なら快適に書ける。本書で紹介されてるような使い倒し方をする場合、rapidでは不十分かもしれない。本書はErlangのProErというフレームワークを使い倒してるが、それと同じことをしようとしてもほとんどの他言語フレームワークでは実現できないのではなかろうか。プロパティを書く -> テスト動かす -> 失敗する -> プロパティor実装直す -> テスト動かす(テスト通るまで繰り返し)というようなサイクルを回しまくるのがおそらく本来のPBTのスタイル。そのためにジェネレーターの調整をするためにメトリクスの収集などが必要な時もあり紹介されている。GoでもそれはできるけどErlangと同じようなことをしようとするとテスト内のコード量が増え「テストのために用意した関数をテストする」といったことにもなりかねない。実際のPBTもどこまで処理を信用するか?といった話もされていた。
なんとか読み終えたけど半分くらい読み飛ばした。(Erlang特有の話してそうな気配もしたので)。それでも、上述したような「プロパティの書き方」を知れたので十分買ってよかった。せっかく読んだのでできればzennの記事にしたいけど心折れるかもしれない。
全然関係ないけどラムダノートの本初めて買ったけど他に気になる本けっこうあった。難しいかな。余裕があればあと一冊くらい読んでみたい。