🧪
単体テストの考え方/使い方まとめ
業務で単体テストコードの導入を検討していたので、単体テストの考え方/使い方を読んでみました。全400ページのかなりボリュームのある書籍でしたが、重要に感じたポイントを列挙します。
第1章 単体(Unit)テストとは
なぜ、単体テストを行うのか?
- 単体テストと設計の関係
- 単体テストがしにくいことは、プロダクトコードに問題を抱えていると評価できる。異なるコード同士が密結合していることが原因で、コードを分離して個別にテストすることが難しくなっている。
ただし、単体テストを作成しやすいからといって、プロダクトコードの質が良いと言えるとは限らない。
- 単体テストがしにくいことは、プロダクトコードに問題を抱えていると評価できる。異なるコード同士が密結合していることが原因で、コードを分離して個別にテストすることが難しくなっている。
- テストは、ソフトウェアとプロジェクトの成長を持続可能なものにする。
- テストにも質はある。
テストの質が良ければソフトウェア全体の質を向上する。テストの質が悪ければ、テストが間違った理由で失敗するようになったり、退行(バグ)をきちんと検出できなくなったりする。また、テストに時間がかかって保守が難しくなると単に「単体テストを行った」という事実を作り上げるようになってしまう。- 単体テストの目標を実現するには、作成する単体テストの価値とその維持にかかるコストを意識する必要がある
何がテストスイートの質を良くするのか?
優れたテストスイートには、次の特徴がある
- テストすることが開発サイクルの中に組み込まれている。
- コードベースの特に重要な部分のみがテスト対象となっている。
- プロダクトコードの全てが単体テストをする価値を持っているわけではない。システムにとって重要な部分(ビジネスロジック)に労力をかける。
- 最小限のコストで最大限の価値を生み出すようになっている。これを実現にするには、次のことを行わなくてはならない。
- 価値のあるテスト、価値の低いテストを認識できるようになる
- 価値のあるテストを作成できるようになる
第2章 単体テストとは何か?
単体テストは次のように定義できる
- 「単体(Unit)」と呼ばれる少量のコードを検証する
- 実行時間が短い
- 隔離された状態で実行する
- これについて、古典学派とロンドン学派という大きく二つの考え方に分かれている。筆者は古典学派を支持する。
ロンドン学派
ロンドン学派では、「少量のコードを隔離してテストすること」を、テスト対象システムから協力者オブジェクトを隔離すること、と考えている。テスト対象となるクラスが他のクラスに依存しているのであれば、その依存を全てテスト・ダブルに置き換えなければならない。テスト対象の振る舞いは外部の影響から隔離され、そのクラスのことだけに専念できる。
メリット
- テスト失敗時に、コードベースのどこで問題が起こったのかを明確にできる。
- 1つのテストケースは1つのクラスしか検証しない、という指針になり、テストスイート全体がシンプルな構造になる。
古典学派との違い - 確認フェーズ
- 古典学派は、依存クラスの状態を確認する
- ロンドン学は、依存クラスとのやりとりを確認する
古典学派
コードを隔離するのではなく、テストケースを隔離すべき、と考える。プライベート依存であれば、テストダブルに置き換えずにそのまま使ってしまっても問題ない。
- 他のテストケースが状態が共有されてしまうものの例。次のような依存をプロセス外依存と呼ぶ
- データベース
- ファイルシステム
- 共有依存
- テストケース間で共有される依存。staticな可変フィールド、データベースなど。このような依存を扱う複数のテストを同時に実行されると、お互いの検証に影響を与える。
- プライベート依存
- 共有されない依存
- プロセス外依存
- アプリケーションを実行するプロセスの外で稼働する依存のこと。プロセス外依存は共有依存であることが多い。(データベースやファイルシステムなど)、、、が、必ずしもそうではないので注意。
- アプリケーションを実行するプロセスの外で稼働する依存のこと。プロセス外依存は共有依存であることが多い。(データベースやファイルシステムなど)、、、が、必ずしもそうではないので注意。
第3章 単体テストの構造的解析
AAAパターンの利用
単体テストにおいて回避すべきこと
- 同じフェーズを複数用意すること
- if文の使用
テストケースが読みづらくなり、テストに対して保守コストがかかる
各フェーズの適切なサイズ
- 通常準備フェーズがもっとも大きくなる
- 実行フェーズは1行。もしそれ以上になったら設計を見直す必要があるかも
- 確認フェーズで確認する項目は1つでなくてもよい。単体テストにおける「単体」とは、1単位のコードではなく、1単位の振る舞いである
テストフィクスチャ
テスト・フィクスチャをコンストラクタで利用することはコードの量を劇的に減らすことができるが、2つの重大な欠点がある。
コンストラクタで利用すると、テスト・ケース間の結びつきを強めてしまう
コンストラクタの利用はテスト・ケースの読みやすさを損なわせる
では、どうやってコードを共有すべきか?
プライベートなファクトリメソッドを導入する。ただし、全てのテストケースで同じテストフィクスチャを使う場合は、コンストラクタを使っても良い。
単体テストでの名前の付け方
よくある次のような命名規則は、実装の詳細に目を向けているため実用性がない
❌{テスト対象メソッド}_{事前準備}_{想定する結果}
テストメソッドの指針
- 厳密な命名規則に縛られないようにする
- 非開発者のドメインエキスパートに対して、どのような検証をするのかが伝わるような名前をつける
- テストクラス名は、
{クラス名}Test
とするが、これは1単位のクラスを示しているのではない。- 実際に呼び出されるクラスの中には、テストクラス名に使われていないクラスも含まれることもある。
- テストクラス名に使われているクラスは、1単位の振る舞いを検証するのにAPIとしての(起点)の役割のクラスが識別できるようにしている
第4章 良い単体テストを構成する4本の柱
良い単体テストを構成する4本の柱
- 退行に対する保護
- リファクタリングへの耐性
- 迅速なフィードバック
- 保守のしやすさ
退行に対する保護とリファクタリングへの耐性の関係
- 偽陰性(見つけられないバグ)
- 偽陽性(嘘の警告)
迅速なフィードバックと保守のしやすさ
理想的なテストの探求
最善の単体テストはリファクタリングへの耐性と保守のしやすさを最大限に備えたもの。退行に対する保護と迅速なフィードバックは、どちらを優先するのかバランスを調整する必要がある。
- テストコードは負債である
- 4本の柱のうち、「退行に対する保護」「リファクタリングへの耐性」「迅速なフィードバック」は互いに排反する性質をもつ。
- E2Eテストは、退行に対する保護とリファクタリングへの耐性を備えているが、テスト実行に時間がかかり、迅速なフィードバックは得られない
- 取るに足らないテストは、常に成功するようなあまり意味のないことを検証している。退行に対する保護が備わっていないといえる。
- 壊れやすいテストは、リファクタリングを妨げる。
- 壊れやすいテストとは、実行時間が短く、退行を見つけることに優れていても多くの偽陽性を持ち込んでしまうテストのこと
- テストケースが、何(What)ではなくどのように(how)に目を向けているため
ソフトウェアテストにおけるよく知られた概念
- 単体テストであっても、E2Eテストであってもリファクタリングは最大限にする必要がある。つまり、偽陽性を可能な限り排除する。
- ブラックボックステストとホワイトボックステスト
- ブラックボックステスト
システムの機能を内部構造を知ることなしに検証する手法。仕様や要求をもとに作成される。テスト対象が処理をどのように(how)ではなく、何(what)すべきか、ということが検証される - ホワイトボックステスト
ブラックボックステストとは逆の視点で行われるテスト手法。ホワイトボックステストは、仕様や要求からではなく、プロダクトコードから作成される。
- ブラックボックステスト
- ホワイトボックステストは偽陽性を持ち込みやすく、リファクタリングへの耐性が欠落する。ブラックボックステストを選択すべきである。
* ただし、テストを分析する際はホワイトボックスを採用することができる。たとえば、次のようにホワイトボックスとブラックボックスを組み合わせると、テストスイートの質をより高めることができる。
* 1. カバレッジ計測ツールを利用して未検証の経路を見つける。
* 2. あたかもそのことを知らなかったかのように未検証の経路を経由するテストケースを作成する
第5章 モックの利用とテストの壊れやすさ
モックの利用はテストの壊れやすさを招く。
モックとスタブの違い
- モックはテスト対象システムからその依存に向かって行われる外部に向かうコミュニケーションを模倣する。
- スタブは依存からテスト対象システムに向かって行われる内部に向かうコミュニケーションを模倣するのに使われる。
- モックとのコミュニケーションは検証する。これは最終的な結果に関係があるから。
- スタブとのコミュニケーションは検証しない。最終的な結果を生み出す一家庭に過ぎないから。
Discussion