テストダブル完全分類ガイド — 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 への戻り値(間接入力)を制御します。「この関数を呼んだら、この値を返す」を定義するものです。

1
2
3
4
5
6
7
# 例: 時刻関数を固定値にする
def test_greeting_morning():
    # Stub: current_time() が常に 8:00 を返す
    time_stub = TimeStub(fixed_time=datetime(2026, 3, 2, 8, 0))
    greeter = Greeter(time_provider=time_stub)

    assert greeter.greet() == "おはようございます"

使いどころ: テスト結果を安定させたいとき。時刻、乱数、外部 API のレスポンスなど。

Mock(モック): 間接出力を検証する

Mock は SUT から DOC への呼び出し(間接出力)を事前に期待値として定義し、正しく呼ばれたかを検証します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 例: メール送信が正しく呼ばれたか検証する
def test_order_sends_confirmation_email():
    # Mock: send_email が特定の引数で1回呼ばれることを期待
    email_mock = Mock()
    email_mock.expect_call("send_email", args=["user@example.com", "注文確認"])

    order_service = OrderService(email_sender=email_mock)
    order_service.place_order(user_id=1)

    email_mock.verify()  # 期待通りに呼ばれたか検証

使いどころ: SUT が DOC に対して正しい操作を行ったかを検証したいとき。

Spy(スパイ): 間接出力を記録する

Spy は Mock と似ていますが、呼び出しを事前に定義するのではなく記録し、テスト後に検証します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 例: ログ出力を記録して後から検証する
def test_error_is_logged():
    # Spy: 呼び出しを記録する
    logger_spy = LoggerSpy()

    processor = DataProcessor(logger=logger_spy)
    processor.process(invalid_data)

    # 後から検証
    assert logger_spy.was_called_with("error", "Invalid data format")
    assert logger_spy.call_count == 1

Mock との違い: Mock は事前に期待値を定義する(事前検証)。Spy は呼び出しを記録して後から検証する(事後検証)。Spy の方が柔軟で、テストが実装に密結合しにくい利点があります。

Fake(フェイク): 実装を置換する

Fake は DOC の実際の実装を、テスト用の軽量な代替実装に置き換えます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 例: インメモリデータベース
class FakeUserRepository:
    def __init__(self):
        self.users = {}

    def save(self, user):
        self.users[user.id] = user

    def find_by_id(self, user_id):
        return self.users.get(user_id)

def test_user_registration():
    # Fake: 本物のDBの代わりにインメモリ実装を使う
    repo = FakeUserRepository()
    service = UserService(repository=repo)

    service.register("Alice", "alice@example.com")

    user = repo.find_by_id(1)
    assert user.name == "Alice"

他のテストダブルとの違い: Stub や Mock は特定の呼び出しに対する応答を定義しますが、Fake は動作する実装を持っています。インメモリ DB、ローカルファイルシステム、クラウドサービスのエミュレータなどが典型例です。

Dummy(ダミー): 引数を埋めるだけ

Dummy は実際にはテストで使用されませんが、API の引数として渡す必要があるときに使います。

1
2
3
4
5
6
# 例: テストに無関係だが引数として必要
def test_calculate_total():
    dummy_logger = None  # テスト対象はロガーを使わない
    calculator = PriceCalculator(logger=dummy_logger)

    assert calculator.total([100, 200, 300]) == 600

注意: Dummy は厳密にはテストダブルというより「値パターン」に近く、NTT の記事でも分類図からは除外されています。

状態検証と振る舞い検証

Martin Fowler が Mocks Aren’t Stubs で指摘した、テストダブルを理解するうえで最も重要な区別です。

検証方式対象確認すること主に使うテストダブル
状態検証直接出力処理結果の値が正しいかStub, Fake
振る舞い検証間接出力正しい操作が行われたかMock, Spy

状態検証は「結果が正しいか」を確認します。Stub で間接入力を制御し、SUT の出力値を検証します。

振る舞い検証は「正しく呼ばれたか」を確認します。Mock や Spy で間接出力を監視し、DOC への呼び出しパターンを検証します。

忠実性と決定性のトレードオフ

gihyo.jp の連載記事が指摘するように、テストダブルには本質的なトレードオフがあります。

忠実性(本物に近い)  ←→  決定性(安定して再現できる)
     Fake                     Stub
   本物のDB               固定値を返す
指標高忠実性(本物に近い)高決定性(安定した再現)
利点本番環境の問題を検出しやすいテストが速く、安定する
欠点テストが遅く、不安定になりがち本番との乖離を見逃しがち
代表例実際の DB、外部 APIStub、Mock

実践的な使い分けの指針

t_wada 氏の連載や講演から導かれる実践的なアドバイスです。

  1. 同一プロセス・マシン内で安定している依存は本物を使う: テストダブルを使わなくても決定性が高い場合は、本物の方が忠実性が高い
  2. 外部サービス、ネットワーク越しの依存はテストダブルを使う: 不安定さを排除するために Stub や Fake を使う
  3. 再現困難な例外条件にはテストダブルを使う: ネットワーク障害、ディスクエラーなどは Stub で再現する
  4. テストダブルの過剰使用を避ける: テストが実装の細部に密結合し、リファクタリングのたびにテストが壊れる原因になる

よくある混乱と正しい理解

「全部モック」問題

現場では 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 のような軽量な代替実装
  • 状態検証と振る舞い検証の区別が本質: 「結果が正しいか」と「正しく呼ばれたか」は別の検証
  • 忠実性と決定性はトレードオフ: 本物に近いほど忠実だが不安定、テストダブルは安定だが乖離のリスクがある
  • テストダブルは必要な場面でのみ使う: 過剰使用はテストの脆さにつながる

参考