どういったテストが良いテストかを議論することが社内であったので、普段自分がテストを書くときに心がけていることを言語化しました。
One assertion per test
「テストメソッド毎にアサーションがひとつ」という状態が理想です。アサーションが多いテストは、「何を担保しているのか」がはっきりせず、テストの責務が曖昧になります。またひとつのテストコード量が増えて、他の人からすれば読むのに苦労して保守がしづらくもなります。
ひとつのことだけをテストするテストメソッドが沢山あるほうが望ましいです。
とはいえ現実的には、ひとつのアサーションだけで書けないようなテストケースも存在します。特にviewに関するテストは、チェックすべきことが多くなります。「one assertion per test」はあくまで基本として、こだわりすぎないことも重要です。
DRYにしすぎない
DRY(Don’t Repeat Yourself)はエンジニアにとって尊い行動ですが、テストに関していえばDRYをあまり意識しないほうが良いです。
テスト用のhelper関数を別ファイルに作った場合、テストを読むのにあちこち飛ばないといけなくなり他の人からは理解しづらいものになってしまいがちです。
テストコードは意図が汲み取りやすいシンプルなコードであることが重要です。
setupは必要最低限に
setupはテストメソッドのなかで共通化できる部分を書くことで、テストの可読性をあげることができます。
ただこのsetupに、ある特定のテストメソッドにのみ必要な処理が書かれてあることも見受けられます。他のテストメソッドへ副作用がなければ問題ないように思えますが、setupの処理が長くなるだけ読みづらさは増しています。
setupにはほとんどのテストメソッドに共通しているコードのみを記述して、他に必要なものは各テストメソッドのなかで用意するのがよいでしょう。
繰り返しになりますが、テストコードは情報が集約していてシンプルであることが望ましいです。
テスト間の依存関係をなくす
特定の順番でないと通らないテストは、開発中に気軽にテストを流せなくなるので必ず避けましょう。
境界値を意識する
条件分岐の境界となる値については、バグが発生しやすいのでテストを書くようにします。
例えば「3人以上なら追加料金が発生」といったロジックをテストする場合は、「2人」と「3人」のふたつが境界値となるので、正常系に加えてこれらをテストケースに加えます。
ランダムなテストデータを用いる
テストで必要となるデータを"hoge"のように固定値で用意しているのをよく見かけますが、たまたまシンプルなデータだから問題が起きてない可能性があります。例えばHTML特殊文字の場合や文字数が長い場合に不具合が発生するコードでも、この"hoge"というテストデータでは検知できません。
Fakerのようなライブラリを用いて、
- 本番のユースケースに即した意味のあるデータ
- 毎回異なるランダムなデータ
を生成してテストを実行することで、コードの堅牢性に繋がります。
テストをすべき箇所に集中する
テストは闇雲に書いてカバレッジを上げれば、品質が上がるというものではありません。例えば、ライブラリ側が担保していてこちらはテストしなくてもいいような箇所までテストを書くのは効果的とは言えません。(もちろん全面的にサードパーティ製のライブラリを信用することは問題ですが)
テストは大事ですが、テストを書く時間/保守する時間もコストになります。プロダクトのコアバリューの箇所や、仕様変更が多くて壊れやすい箇所など、テストすべき箇所にまずは集中するようにしましょう。
hotfixの後にテストを追加する
いくらテストで気をつけても本番環境でバグは起こるものです。そしてバグが発生した場合のhotfixでは、テストを用意する時間的猶予がないことも少なくありません。
しかしこのまま放置してしまうと同じ箇所でバグが再発するリスクが残っているので、hotfixリリースの後でもいいので忘れず新たなテストケースを追加するようにしましょう。