ユニットテスト概論
ユニットテストとは
- ユニット(Unit、単体)レベルの少量のコードを検証すること
ユニットテストの大目的
ソフトウェアの持続可能な成長の担保
- ソフトウェアが持続的に成長できるよう動作を検証し、不具合のない事を保証する
ユニットテストの小目的
期待通りに正しく動作することの検証
- 実装したコードが期待通りの動作をすることを実際に実行して確認する
- ユニットテストが行われない場合、結合テストで初めて動作確認が行われるので問題があった場合の手戻りが大きい
退行を防ぐ
- 十分なユニットテストがある場合、コードの修正時、意図しない動作や不具合の混入(退行)を防ぐことができる
関数/メソッドの説明書として
- 関数/メソッドがどのような使われ方を想定しているかをコードから読み解くのは中々難しい
- 実際の利用例が最も分かりやすい
- また、取り得る入力のエッジケースや出力の変化もテストコードが生きたドキュメントとして機能する
単体テストの技法
出力値ベースのテスト
- メソッドや関数の出力を検証する
- 最も基本的なテスト
状態ベースのテスト
- テスト対象のコードが実行された後の状態を検証する
- 例えばDBやファイルなど
コミュニケーションベースのテスト
- クラス間のコミュニケーションが想定通りか検証する
- メソッドの呼び出し回数・順序をキャプチャすることで検証する
テストダブル
テストダブルとは
- テスト対象が依存しているコンポーネントを置き換えてテストを容易にするテスト時のみ使用するコンポーネント
- ダブル(Double)とは代役、影武者を意味するらしい
- 映画のスタントマンのことをスタントダブルと呼んでいたのが由来とのこと
種類
カバレッジ
カバレッジとは
- テストの網羅率の指標
- テスト対象のコードを網羅できているか
WEBアプリケーションのテスト戦略
- ハンドラ層はビジネスロジック層をモック化してハンドラ層のみテスト
- ビジネスロジック層は永続化層をモック化してビジネスロジック層のみテスト
- ドメインモデルは基本的にテストは書かないが、ロジックが複雑なものに関しては書く
- 永続化層のコードのテストは書かない
- 永続化層の担保はE2Eテストで行う
ユニットテスト不要論
(1) ユニットテストのコストが利益を上回る
- ユニットテストの作成・保守・実行には時間と労力がかかり、そのコストが得られるメリット(バグの早期発見、コードの信頼性向上)を上回る
- 小規模プロジェクトや迅速なプロトタイピングでは、ユニットテストを書くよりもコードを直接本番で試す方が効率的
- テストのメンテナンス負荷が受け入れがたいレベルに達する
- 過剰なテストコードが作成されがち
(2) インテグレーションテストやE2Eテストで十分
- コンポーネント単位のテストより実際に統合されたアプリ(システム)の振る舞いの方が重要
- ユニットテストではなくインテグレーションテストやE2Eテストにリソースを割くべき
- 実際に動くシステムを使ってテストする方が低コストでバグを発見できる
(3) 本番環境でのモニタリングやカナリアリリースが代替
- 本番環境で素早く問題を検知・修正・リリースできる体制が整えられているなら、ユニットテストでバグを防ぐよりも積極的に本番環境にリリースした方がよい
- 特に市場の反応に迅速に対応する必要のあるサービスなら多少のバグよりも早く市場に公開できることの方がメリットが上回る
(4) ユニットテストがコードの硬直化を招く
- ユニットテストを書くと、テストコードの資産を活かそうとして設計変更より既存コードの修正を選んでしまいがち
- テストを通過させる為にコードを書くという逆転現象が起きてしまう
- 実装の詳細に踏み込んだテストコードがある場合、実装やインターフェイスが変化した場合、テストが大量に壊れてしまう
(5) 開発者のスキルやコンテキストに依存
- テストコードの品質は開発者のスキルやプロジェクトの性質に依存する面が大きい
- 小規模なプロトタイピングのプロジェクトではテストコードを書いても有効に機能しない上、工数が無駄になってしまう
(6) ユニットテストには限界がある
- ユニットテストでは全てのバグを防ぐことができない
- 境界条件や外部システムに依存する問題を防ぐには向いていない
- モックを多様するテストでは実際のコードの検証が漏れていて、本番環境までバグが残ってしまうことが見受けられる
- 複雑過ぎる構成のテストコードでも同様の問題が起きる
- 結局、インテグレーションテストやE2Eテストで検出するしかない問題もある
ユニットテストを書かない技術
- 型の制約でそもそも誤った振る舞いができないようにする戦略も有り得る
処理フローロジックのテストは必要か
- 処理フローロジックとは、サブモジュールの呼び出しと制御に特化したロジック
- 抽象度統一の原則におけるパブリックメソッドのイメージ
- 処理フローロジックではサブモジュールの呼び出しと制御に特化している為、本質的なドメインロジックは無い(そのように実装されるはず)
- すると、本質的なロジックのテストはサブモジュールのテストでカバーされ、処理フローロジックのテストはサブモジュールの呼び出しをテストするのと等しくなる
- サブモジュールのモック作成とメンテナンスの手間が掛かる割にあまり有効なテストにならない
- カバレッジを稼ぐことはできるが、あまりコスパが見合う話でもない
- したがって、処理フローロジックのユニットテストは省略してもよい
ユニットテストにおける派閥
ロンドン学派
- モックを積極的に使う派
- テスト対象の依存関係を全てモックに置き換える
- ユニットテストの単位はクラス
- コードを検証する
- 不変的な依存関係以外はモック(不変的な依存関係=Enumや定数)
- 単一クラスで完結しないクラスは結合テストとして扱う
古典派
- モックはあまり使わない
- テスト対象の共有依存関係のみモック(共有依存関係=DBなど)
- ユニットテストの単位は1つの振る舞い
- 振る舞いをテストする
- テスト同士は隔離される
- 2つ以上の動作単位を検証する場合は結合テスト