技術書読書ログ「単体テストの考え方/使い方」
個人的に重要だと思ったところ
サマリ
単体テストの考え方:以下の4つの柱が良いテストの基盤
- リグレッションからの保護
- 言い換えると、プロダクションコードの変更によるバグ見つけやすさ
- リファクタリングへの耐性
- 言い換えると、テストが失敗することなくプロダクションコードのリファクタリングをどの程度できるか
- 迅速なフィードバック
- 保守のしやすさ
- 言い換えると、「テストケースの理解のしやすさ」と「テストの実行のしやすさ」
この4つの柱を満たすための主な単体テストの使い方は以下の3つ
- 単体テストの単位は、「コード(クラスや関数)」ではなく、「振る舞い」
- すべてのプロダクションコード対象にするのではなく、複雑だったり重要なコードのみを対象にして、他はインテグレーションテストでカバーする
- モックを適用するのは他のシステムに依存しているところに限定し、単体テストでは利用しないのが理想
単体テストの単位は「ふるまい」
古典学派の考え方で、単体テストの「単体」は1単位の振る舞い(a unit of befavior)のことを指している。1つのテストケースで複数のコード(クラスや関数)をまとめてテストすることもある。
他のテストケースから隔離された状態でテストを実行できるようなっていればよく、他のテストケースの実行に影響を与える依存にだけテストダブルを用いる。
各テストケースはブラックボックステストになっていて、テスト名にはメソッドや関数名を使わず、非開発者でもドメインに精通していれば理解可能な名前になっているべき。
複雑だったり重要なコードのみ単体テストの対象
(テストも含めて)コードは資産でははなく負債であり、少なければ少ないほど良い。なので単体テストも最小限のコストで、最大限の価値を生み出せるようにするべき。
そのためには、プロダクションコードは「複雑(重要)かつ依存が多いコード」をできる限り避ける。テストのコストが大きくなるため。そうなりそうな(そうなっている)コードは「複雑(重要)なコード」と「依存が多いだけのコード」とに分割する。
「複雑(重要)なコード」とはドメインモデルであったり複雑なアルゴリズムを持つコードであり、このコードを単体テストの対象にするのが最も効果が高い。
「依存が多いだけのコード」とはHumbleObjectやMVCのController、MVPのPresenter、アプリケーション・サービス層などのコードこと。このコードを単体テストの対象にしてもあまり効果は高くならない。代わりにインテグレーションテストでカバーする。
モックを使わないのが理想
前提として「モックを適用するのはシステムの管理下にない依存のみに限定すべき」というプラックティスがある。なぜなら、システム内のコミュニケーションの確認にもモックを使うと、テストがプロダクションコードの詳細を知ることになり、壊れやすくリファクタリング耐性がなくなってしまうから。
そして1つ前の項目に書いてあるように、プロダクションコードでは「複雑(重要)かつ依存が多いコード」は「複雑(重要)なコード」と「依存が多いだけのコード」とに分割し、「依存が多いだけのコード」にのみ「システムの管理下にない依存」が含まれるべき。
「依存が多いだけのコード」はインテグレーションテストでカバーするものであり、単体テストの対象にはしないので、モックも単体テストでは利用しないのが理想的な状態。
モックのベストプラクティスについての補足
- モックを適用するのはシステムの管理下にない依存のみ
- モックの利用はインテグレーションテストに限定
- モックの置き換え対象は以下に限定
- システムの境界に位置するもの
- 自身のプロジェクトが所有するクラス(型)
- モックに対して行われた呼び出しの回数は常に確認する
- 利用箇所を限定しているため
- 「想定している呼び出しが行われていること」と「想定していない呼び出しが行われていないこと」の両方を確認する
テストダブルの種類の補足
- モック(モック、スパイ)
- 「テスト対象システム→その依存」の外部に向かうコミュニケーション(出力)を模倣
- コマンドクエリ分離原則における「コマンド」を模倣
- スタブ(スタブ、ダミー、フェイク)
- 「依存→テスト対象システム」の内部に向かうコミュニケーション(入力)を模倣
- コマンドクエリ分離原則における「クエリ」を模倣
アンチパターン
- 単体テストの単位がコード(クラスや関数)の単位
- ロンドン学派
- ホワイトボックステストなテストケース
- モックを含めたテストダブルを多用して、テスト対象を他のコードから隔離している
- 1つのテストケースでテストするのは1つのコードのみ
- すべてのプロダクションコードを単体テストの対象にする
アンチパターンが良く無い一番の理由としては、単体テストの検証内容が詳細になりすぎてテストが壊れやすくなり、リファクタリングへの耐性がなくなること。
リグレッションに対する保護に関しても、振る舞い単位のテストと比較して優位なわけではない。
感想
私が仕事で書いている単体テストが、この本でいうところのまさにアンチパターンでしたw 実際に仕事の単体テストのコードは脆くて、ちょっとしたプロダクションコードの変更だったりリファクタリングでも単体テストでセットで修正しないといけなくなっていて、結構負担にはなっているなあと感じています。
本書に書いてある内容の考え方が存在していることは、他の技術書等からの情報で知ってはいたものの、具体的にどうやってテストケースを書いていけばいいかまでは分かっていなかったので、本書の中で具体的にどういうテストを書いて、プロダクションコード側の設計まで解説してくれているのが良かったです。
この内容を適用するのに難しそうだなと思ったのが、振る舞いの単位でテストを書いていく部分。クラスやメソッド毎に単体テストを書いていくのに慣れすぎていて、振る舞いの単位というのが理解はできても実際に書いていくのがパッとできなさそう。しばらく個人で書くコードはこの考え方を意識して書くようにして、振る舞いの単位でテストを書くということにも慣れていきたい。
Discussion