『単体テストの考え方/使い方』を読んだ。
感想
「なぜ単体テストを作るのか」「どのような単体テストを書くべきか」「何を単体テストで確認するべきか」という知りたかったことがたくさん書いてある本。章ごとに「まとめ」が書いてあるので、1つの章を読み終わった後で復習できるのも良かった。
この本の好きなところは、構成が目的からのトップダウンになっているところだ。
まず、そもそも単体テストの目的は何か?
→ それは「プロジェクトの成長を止めないこと」だ(第1章)
では、その目的にかなう単体テストはどのような性質をもつべきか?
→ 以下の4つの性質をもつ必要がある(第4章)
- 退行に対する保護
- リファクタリングへの耐性
- 迅速なフィードバック
- 保守のしやすさ
これらの性質をもつために何をテスト対象とするべきか?
→
- [リファクタリングへの耐性]をもつために、実装の詳細ではなく観測可能な振る舞いをテストする(第5章)
- [退行に対する保護]をもつために、複雑なコード・ドメインにおける重要性が高いコードをテストする(第6章)
さらに、このようなテストを書くために、どのようなプロダクションコードを書くべきか?
→
クリーンアーキテクチャ・関数型アーキテクチャを採用する(第6章)
各章のトピック
個人的に刺さった箇所をまとめる。
第1章 なぜ単体テストを行うのか
単体テストの現状
- ほとんどのプロジェクトで単体テストを導入するのは当然と思われている
- 単体テストの作成に膨大な労力を費やしたにもかかわらず、必ずしも望ましい結果が得られるわけではない
→労力に見合った単体テストを作成できるように
なぜ単体テストを書くのか
なぜ単体テスト(を含めた自動テスト)を書くのか。ソフトウェア開発の成長を持続可能にするためである。コードに変更を加えたときに、テストがあることで、コードの変更によって生じる多くの退行を検出するセーフティーネットが備わる。
第2章 単体テストとは何か
単体テストとは、以下の3つの性質をすべて満たすテストである:
- 1単位の振る舞いを検証する
- 実行時間が短い
- 他のテストケースから隔離されている
1と3の考え方については、「ロンドン学派」と「古典学派」で異なる。
ロンドン学派の場合、1単位はクラスのことを指しており、他のテストケースから隔離されているとは、不変依存以外の全ての依存関係がないことを示している。
古典学派の場合、1単位は1つのテストケースのことであり、他のテストケースから隔離されているとは、テストケース間で影響を与えるような共有依存だけがテスト・ダブルに置き換えられることを示している。
第3章 単体テストの構造的解析
AAAパターン
準備(Arrange)、実行(Act)、確認(Assert)のフェーズで記載する。
準備フェーズが最も大きく、実行フェーズは1行、確認フェーズはテスト対象の振る舞い次第。
単体テストの名前の付け方
-
メソッド名をテストの名前にするなど、実装の詳細に注目した命名は行わず、振る舞いに注目した名前とする
✕:Sum_TwoNumbers_ReturnSum()(メソッド名をそのまま)
〇:2つの数値の合計を返す() -
正常系を1パターン、異常系は1パターンにテストデータを全て書くことで読みやすくなる
// 正常系
[Fact]
public void 最短のは配達日は当日から2日後である(int daysFromNow)
{
}
// 異常系(テストデータの全パターンを1ケースに記載)
[InlineData(-1)]
[InlineData(0)]
[InlineData(1)]
[Theory]
public void 不正な配達日を検出する(int daysFromNow)
{
}
第4章 良い単体テストを構成する4つの柱
- 退行に対する保護
- リファクタリングへの耐性
- 迅速なフィードバック
- 保守のしやすさ
退行に対する保護
コードは負債。コードベースが大きくなると多くの潜在的バグを抱えることになり、最終的にプロジェクトの成長を維持できなくなる。新たにコードを追加した際、退行が発生していないかバグを見つけ出すテストでなければならない。
リファクタリングへの耐性
テストが失敗することなく、リファクタリングを行えるか。リファクタリングを行った結果、テストが失敗を示すケース(偽陽性)が続くと、開発者がテスト結果を重要視しなくなる。単体テストは偽陽性がうまれづらいものでなければならない。
迅速なフィードバック
改善のプロセスが早くなるため、テストは速やかに行えるようにするべき。
保守のしやすさ
テストケースは理解しやすく、実施しやすいものであるべき。
第5章
テストケースが偽陽性を生み出してしまう最大の原因は、テストケースが実装の詳細と結びついてしまうこと。これを回避する方法は、検証する対象をテスト対象のコードが生み出す最終的な結果(観察可能な振る舞い)だけにすること。
第6章 単体テストの3つの手法
- 出力値ベース・テスト(戻り値を確認するテスト)
- 状態ベース・テスト(状態を確認するテスト)
- コミュニケーション・ベーステスト(オブジェクト間のやり取りを確認するテスト)
- 出力値ベーステストは実装の詳細と結びつきづらいため、最も質の高いテストを作成できる
- 出力値ベーステストを作るために関数型アーキテクチャを使用する