テストダブル完全分類ガイド — Mock, Stub, Spy, Fake, Dummy の違いを図解で理解する
t_wada 氏(和田卓人氏)のポストが、テストダブルの分類について「混乱しがちなテストダブルの分類については、この図がおすすめです」と NTT の解説記事を紹介しています。テストダブルという用語は現場でよく使われますが、「全部モックと呼んでしまう」「Stub と Mock の違いが曖昧」という混乱が起きがちです。この記事では、xUnit Test Patterns に基づく正確な分類を整理します。
テストダブルとは
テストダブルは、映画のスタントダブル(代役)から名付けられた用語です。テスト対象のコード(SUT: System Under Test)が依存するコンポーネント(DOC: Depended-on Component)の「身代わり」として使うオブジェクトの総称です。
Gerard Meszaros が著書 xUnit Test Patterns で体系化し、Martin Fowler が “Mocks Aren’t Stubs” で広めました。
なぜテストダブルが必要か
- 外部依存の排除: データベース、外部 API、ファイルシステムなどの影響を受けずにテストできる
- 再現困難な条件のテスト: ネットワーク障害、ディスクエラーなどの例外条件を再現できる
- テスト速度の向上: メモリ上で動作するため高速に実行でき、並列動作も容易
- 決定性の確保: 外部環境に左右されない安定したテスト結果を得られる
SUT と DOC の関係
テストダブルの分類を理解するには、まず情報の流れを整理する必要があります。
テスト → [直接入力] → SUT → [直接出力] → テスト
↕
[間接入力/間接出力]
↕
DOC
| 用語 | 意味 | 例 |
|---|---|---|
| SUT (System Under Test) | テスト対象 | テストしたいクラス・関数 |
| DOC (Depended-on Component) | SUT が依存するもの | DB、外部 API、時刻関数 |
| 直接入力 | テストから SUT に渡す値 | 関数の引数 |
| 直接出力 | SUT からテストに返る値 | 関数の戻り値 |
| 間接入力 | DOC から SUT に渡される値 | DB のクエリ結果、API のレスポンス |
| 間接出力 | SUT から DOC に渡す値 | DB への書き込み、API へのリクエスト |
テストダブルは、間接入力と間接出力のどちらを制御するかで分類されます。
5 種類のテストダブル
NTT の解説記事の分類に沿って、4 つの主要なテストダブルと Dummy を整理します。
分類マトリクス
| テストダブル | 対象 | 目的 | 一言で言うと |
|---|---|---|---|
| Stub | 間接入力 | 操作する | DOC の戻り値を制御する |
| Mock | 間接出力 | 検証する | DOC への呼び出しを事前に期待値として定義する |
| Spy | 間接出力 | 記録する | DOC への呼び出しを記録し、後から検証する |
| Fake | 実装全体 | 置換する | DOC の軽量な代替実装を提供する |
| Dummy | なし | 存在する | 引数を埋めるためだけに渡す(使われない) |
Stub(スタブ): 間接入力を操作する
Stub は DOC から SUT への戻り値(間接入力)を制御します。「この関数を呼んだら、この値を返す」を定義するものです。
| |
使いどころ: テスト結果を安定させたいとき。時刻、乱数、外部 API のレスポンスなど。
Mock(モック): 間接出力を検証する
Mock は SUT から DOC への呼び出し(間接出力)を事前に期待値として定義し、正しく呼ばれたかを検証します。
| |
使いどころ: SUT が DOC に対して正しい操作を行ったかを検証したいとき。
Spy(スパイ): 間接出力を記録する
Spy は Mock と似ていますが、呼び出しを事前に定義するのではなく記録し、テスト後に検証します。
| |
Mock との違い: Mock は事前に期待値を定義する(事前検証)。Spy は呼び出しを記録して後から検証する(事後検証)。Spy の方が柔軟で、テストが実装に密結合しにくい利点があります。
Fake(フェイク): 実装を置換する
Fake は DOC の実際の実装を、テスト用の軽量な代替実装に置き換えます。
| |
他のテストダブルとの違い: Stub や Mock は特定の呼び出しに対する応答を定義しますが、Fake は動作する実装を持っています。インメモリ DB、ローカルファイルシステム、クラウドサービスのエミュレータなどが典型例です。
Dummy(ダミー): 引数を埋めるだけ
Dummy は実際にはテストで使用されませんが、API の引数として渡す必要があるときに使います。
| |
注意: Dummy は厳密にはテストダブルというより「値パターン」に近く、NTT の記事でも分類図からは除外されています。
状態検証と振る舞い検証
Martin Fowler が Mocks Aren’t Stubs で指摘した、テストダブルを理解するうえで最も重要な区別です。
| 検証方式 | 対象 | 確認すること | 主に使うテストダブル |
|---|---|---|---|
| 状態検証 | 直接出力 | 処理結果の値が正しいか | Stub, Fake |
| 振る舞い検証 | 間接出力 | 正しい操作が行われたか | Mock, Spy |
状態検証は「結果が正しいか」を確認します。Stub で間接入力を制御し、SUT の出力値を検証します。
振る舞い検証は「正しく呼ばれたか」を確認します。Mock や Spy で間接出力を監視し、DOC への呼び出しパターンを検証します。
忠実性と決定性のトレードオフ
gihyo.jp の連載記事が指摘するように、テストダブルには本質的なトレードオフがあります。
忠実性(本物に近い) ←→ 決定性(安定して再現できる)
Fake Stub
本物のDB 固定値を返す
| 指標 | 高忠実性(本物に近い) | 高決定性(安定した再現) |
|---|---|---|
| 利点 | 本番環境の問題を検出しやすい | テストが速く、安定する |
| 欠点 | テストが遅く、不安定になりがち | 本番との乖離を見逃しがち |
| 代表例 | 実際の DB、外部 API | Stub、Mock |
実践的な使い分けの指針
t_wada 氏の連載や講演から導かれる実践的なアドバイスです。
- 同一プロセス・マシン内で安定している依存は本物を使う: テストダブルを使わなくても決定性が高い場合は、本物の方が忠実性が高い
- 外部サービス、ネットワーク越しの依存はテストダブルを使う: 不安定さを排除するために Stub や Fake を使う
- 再現困難な例外条件にはテストダブルを使う: ネットワーク障害、ディスクエラーなどは Stub で再現する
- テストダブルの過剰使用を避ける: テストが実装の細部に密結合し、リファクタリングのたびにテストが壊れる原因になる
よくある混乱と正しい理解
「全部モック」問題
現場では Stub も Spy も Fake もすべて「モック」と呼ばれがちです。テスティングフレームワークの名前(Jest の jest.mock()、Python の unittest.mock)がこの混乱を助長しています。
しかし、正確には jest.mock() で作るオブジェクトの多くは Mock ではなく Stub です。戻り値を制御しているだけで、呼び出し方を検証しているわけではないからです。
Mock と Spy の混同
どちらも間接出力を扱いますが、タイミングが異なります。
- Mock: テスト実行前に期待値を定義する(失敗するとテスト中に即座にエラー)
- Spy: テスト実行後に記録を確認する(テスト全体が終わってから検証)
現代のフレームワークでは Spy 的な使い方(記録→後検証)が主流になっています。
Stub と Fake の混同
どちらも間接入力に関わりますが、複雑さが異なります。
- Stub: 単純な固定値を返すだけ。ロジックを持たない
- Fake: 動作する実装を持つ。インメモリ DB は Fake であって Stub ではない
まとめ
- テストダブルは 5 種類: Stub、Mock、Spy、Fake、Dummy に分類される
- 間接入力を制御するのが Stub: DOC の戻り値を固定する
- 間接出力を検証するのが Mock と Spy: Mock は事前定義、Spy は事後検証
- 実装を置換するのが Fake: インメモリ DB のような軽量な代替実装
- 状態検証と振る舞い検証の区別が本質: 「結果が正しいか」と「正しく呼ばれたか」は別の検証
- 忠実性と決定性はトレードオフ: 本物に近いほど忠実だが不安定、テストダブルは安定だが乖離のリスクがある
- テストダブルは必要な場面でのみ使う: 過剰使用はテストの脆さにつながる