前置き
ユニットテスト(≒テストコード)は開発のペースや品質に安定をもたらします。
しかし、テストコードを書くこと自体にもコストがかかり、また下手なテストコードは保守性や開発ペースを下げてしまいます。ここではテストコードのメリットがデメリットを上回る様、うまくテストを書くための心得を3つにまとめてみましたので紹介します。
さっそく心得
ひとつ、良いテストを知るべし。
ひとつ、書くべきテスト、書くべきでないテストを見極めるべし。
ひとつ、テストを起点にコードを書くべし。
良いテストの状態とは
次のような状態を目指す、維持することが良いとされています。
- すぐにテスト結果を得られること(Fast)
- わかりやすいこと(Clean)
- 頼れる、信用できること(Confidence)
- 実装がテストに縛られないこと(Freedom)
すぐにテスト結果を得られること(Fast)
- 実行時間が長いと開発ペースが遅くなる
- テストダブルを適切に利用することで不要な処理が走るのを防ぐことができる
- 実行しやすいことも大事
わかりやすいこと(Clean)
- 何のためのテストで、何を保証しているのかがわかりやすいことが大事
- わかりにくいと
- 意味のないテストや無駄に重いテストなどが混ざりやすくなる
- 保守性が下がるだけでなく他の3項目(Fast, Confidence, Freedom)に悪い影響が出る
頼れる、信用できること(Confidence)
- 頼れない、信用できない状態では、別途手動テストをやる羽目になるなど、テストコードの価値が低くなる
- 信頼性を高めるには
- テストの意図に沿って抜け漏れがないこと
- 複雑な部分や不安になりやすい部分がしっかりテストされていること
実装がテストに縛られないこと(Freedom)
- コード間の結合度が高くなるほど変更が大変になるが、テストコードも実装と結合している点を見逃しがち
- 特にUIコンポーネントのテストコードは緩さがないとメンテが回らずすぐに破綻する
- BDDと狭義のTDD(※1)を使い分け、適度な緩さを保つことが大事
- 緩さの例
- クラスのpublicメソッドのみをテストし、privateメソッドはpublicメソッドを通して担保する
※1 誤解を恐れずに書いた個人的なTDDとBDDの理解の表
項目 | 内容 |
---|---|
広義のTDD | 純粋にテストコードを起点として開発を進めること。 BDDと狭義のTDD両方を含む。 |
狭義のTDD | 結果としてボトムアップ型で、ホワイトボックステストに似た 一つ一つの分岐を確認するテストが書かれる。 |
BDD | 結果としてトップダウン型で、ブラックボックステストに似た 振る舞いにおける一連の処理をまとめて確認するテストが書かれる。 |
書くべき、書くべきでないテストとは
書くべきでないテスト
個人の経験に依存したテスト
- テストの粒度がばらつく
- 抜け漏れもに気づきにくい
- 結果的に**「良いテストの状態」**を保てなくなる
信頼性 < メンテコスト となるテスト
- コスパが悪い状態
- 経験上の例
- 「configファイルの設定が正しく渡せているかのテスト」
- 解消できる不安がほとんどないのに設定が変わるたびにテストの修正を必要とした(低いFreedom性)
- ユニットテスト以外の保証の仕方を検討する
- 例)振る舞いのテストの中で結合的に担保する
- 例)上記の例の様な場合で変更の頻度が低いなら、手動テストで担保するのもあり
手段を確認するテスト
- 例)引数を整形する関数が、整形に必要なサブ関数を何回呼べているか
- 上記で「サブ関数を3回呼べています」というテストを見ても正しいのか自信を持てない
- returnしない関数やメソッドにありがち
- テストコードをだけを見て、対象が "どんなものなのか" がわかるか見返してみる
カバレッジ100%を目指したテスト
- 一定の基準に沿って網羅したテストを書くこととカバレッジ100%は全くの別物
- 書くべきでないテストが量産される
- カバレッジは飽くまで目安にとどめたほうが良い
書くべきテスト
目的を確認するテスト
- 手段を確認するテストの逆
- 例)引数を整形する関数が、引数を正しく整形できているか
チームの方針、基準に沿ったテスト
- 複数人で開発する場合は事前にチームで話し合い、テストの方針、基準を決めてそれに沿って書くと良い
- 抜け漏れが出にくくなる
- テストコード全体の一貫性が保ちやすくなる
- 結果として、わかりやすさと信頼性が増す
方針、基準はどう決めるのか
- 銀の弾丸は無さそう(あればぜひコメントで教えてください!!)
- 言語や利用フレームワーク、プロダクトの規模や性質によって最適解が変わるため
- 4つの**「良いテストの状態」**は変わらないので大方針としてはそれを目指す
- 具体策はチームで随時話し合い、自分たちの開発環境に合った基準を探す
- どこを保証すれば効果的か
- どこのテストが 信頼性 < メンテコスト となりやすいのか
- などなど
- チームで相談して基準を変更するなど、継続的なメンテも大事
- 例)決めた規準ではテスト不要となるが、実装に不安な部分がでた場合
テストを起点にコードを書く
- テストコードの恩恵を受けやすくするため
- 要素としてテストファースト、テストドリブンについて紹介します。
テストファースト
- テストコードを意識せずに書いたコードは、非常にテストコードが書きにくいものになりがち
- 後からテストを書こうとして挫折した経験がある人も多いはず
- テストファーストはテストコードを書くコストを低く保つために必要
テストドリブン
- テストを起点に実装することにより、
- テストコードが実装の根拠となる
- 実装の目的や内容をテストコードが表現することになる
- 結果的にテストは実装を要約したドキュメントの様な役割を果たす
- 後から参加したメンバーもテストを通してコードを理解できる
まとめ
- 3つの心得を元に、良いユニットテストの書き方を紹介
- テストコードはただ書けば良いものでは無い
- 具体的にどの様なテストを書いていくかはチームで探求し続けよう!