Test Doubles — 代替オブジェクトの種類と活用方法

著者: IT Sectr 公開日: 2026-04-10 読了時間: 9 分

Test Doublesとは、実際の依存関係の代わりに単体テストで使用される代替オブジェクトです。この用語は、Gerard Meszarosが著書「xUnit Test Patterns」(2007年)でMock、Stub、Fake、Spy、Dummyの総称として導入しました。Martin Fowler(2024年)によると、Test Doublesはテスト対象のコンポーネントをその環境から分離し、テストを決定論的で高速かつ外部サービスに依存しないものにします。

重要ポイント

  • Test Doubles — テストにおけるすべての種類の代替オブジェクトの総称
  • Mockは相互作用を検証:どのメソッドがどの引数で呼ばれたかを確認
  • Stubは呼び出しを検証せずに事前定義された値を返す
  • Fake — 簡略化された動作する実装(例:インメモリデータベース)
  • Spyは後で検証するために呼び出しを記録し、Dummyはパラメータを埋める

Test Doublesとは?

Test Doublesは自動車業界(スタントダブル)からソフトウェア開発に取り入れられた用語です。スタントダブルが危険なシーンで俳優の代わりをするように、Test Doubleはテストシナリオで実際のコンポーネントを置き換えます。これは、実際の依存関係が利用不可、低速、非決定的、または副作用がある場合に必要です。

Test Doubleの概念は5つの特定のタイプを包含し、それぞれが独自の役割を解決します。Meszarosの分類は標準的であり、すべての現代的なテストガイドで使用されています。タイプ間の違いは、制御と検証の度合いにあります:単純なパラメータ埋め(Dummy)から完全な呼び出しシーケンス検証(Mock)まで。

Test Doublesが必要な理由

Test Doublesの主な目的は、テスト対象モジュールの分離です。モバイル開発では、実際の依存関係にはAPIサーバー、データベース、ファイルシステム、デバイスセンサー、システムサービス(LocationManager、Camera、Bluetooth)が含まれます。これらのコンポーネントを直接使用すると、テストが低速で壊れやすく、環境に依存するものになります。Google Testing Blog(2023年)によると、適切に分離された単体テストはミリ秒で実行され、統合テストは秒や分で実行されます。

Test Doublesの5つのタイプ

Gerard Meszarosの分類には5つのタイプのTest Doublesが含まれ、動作と目的が異なります。それらの違いを理解することが、適切な単体テストの基礎です。

Dummy

Dummyは、テスト対象メソッドに渡されるが決して使用されないオブジェクトです。Dummyはメソッドシグネチャを満たすためだけに必要です。Kotlinでは、これは多くの場合nullemptyList()、またはスタブ付きのオブジェクトです。Dummyにはロジックを含めるべきではありません — 呼び出された場合、テストは失敗する必要があります。

Fake

Fakeはインターフェースの簡略化されたしかし動作する実装です。MockやStubとは異なり、Fakeには実際のビジネスロジックが含まれますが、簡略化された形です。典型的な例はInMemoryUserRepositoryで、データベースの代わりにHashMapにデータを保存します。Fakeは、実際のインフラストラクチャのオーバーヘッドなしに、状態に依存するロジックをテストする必要がある場合に使用されます。

タイプ目的
Dummyパラメータを埋めるnull、空オブジェクト
Fake動作する簡略化された実装InMemoryRepository
Stub固定値を返すwhen(api.getUser()).thenReturn(user)
Spy検証のために呼び出しを記録verify(spy).save(user)
Mock相互作用を検証verify(mock).sendEmail(email)

Stub

Stubは特定の呼び出しに対して事前定義された値を返します。Stubは呼び出されたかどうかを確認しません — 単にデータを提供します。Mockitoでは、Stubはwhen(method).thenReturn(value)で作成されます。Stubは、依存関係が特定の値を返す必要があるが、呼び出し自体は重要でない場合のテストに理想的です。

Spy

Spyは実際のオブジェクトのラッパーで、後で検証するためにすべての呼び出しを記録します。Mockとは異なり、Spyは実際のオブジェクトに呼び出しを委譲しますが、それらが発生したことを確認できます。Mockitoでは、Spyはspy(realObject)で作成されます。Spyは部分的なモッキングに便利で、実際のオブジェクトを使用したいが一部の呼び出しを検証したい場合に使用します。

Mock

Mockは事前定義された呼び出し期待値を持つオブジェクトです。Mockは、特定のメソッドが特定の引数と特定の順序で呼び出されたことを検証します。Stubとは異なり、Mockはデータの返却ではなく動作の検証に焦点を当てています。Mockはモバイル開発で最も強力で最も頻繁に使用されるTest Doubleのタイプです。

Mock vs Stub:主な違い

MockStubの違いは、経験豊富な開発者の間でも混乱を引き起こすことがよくあります。主な違いは目的にあります:Stubは状態検証(state verification)を行い、Mockは動作検証(behavior verification)を行います。

Stubは次の質問に答えます:「コードは正しい結果を返したか?」。Mockは次の質問に答えます:「コードは正しい引数で正しいメソッドを呼び出したか?」。モバイル開発では、結果が重要な場合(例:リポジトリからのデータ)にStubが使用され、副作用が重要な場合(例:メール送信、データベースへの書き込み)にMockが使用されます。

kotlin
// Stub: 状態検証
every { repository.getUsers() } returns listOf(user)
val result = useCase.getUsers()
assertEquals(1, result.size)

// Mock: 動作検証
every { analytics.logEvent("purchase") } returns Unit
useCase.purchase(item)
verify { analytics.logEvent("purchase") }

KotlinでのTest Doublesの例

MockKを使用したKotlinでの5つのタイプすべてのTest Doublesの実践的な例 — Androidプロジェクトで最も人気のあるモッキングライブラリです。

Fake:InMemoryUserRepository

kotlin
class InMemoryUserRepository : UserRepository {
    private val store = mutableMapOf<String, User>()

    override fun save(user: User) {
        store[user.email] = user
    }

    override fun findByEmail(email: String): User? {
        return store[email]
    }
}

Stub + Mock:UseCaseのテスト

kotlin
class RegisterUseCaseTest {
    private val api = mockk<AuthApi>()
    private val repo = spyk(InMemoryUserRepository())
    private val useCase = RegisterUseCase(api, repo)

    fun `register user successfully`() = runTest {
        // Stub: 固定APIレスポンスを返す
        coEvery { api.register("test@test.com") } returns AuthResult.Success("token123")

        val result = useCase.execute("test@test.com")

        // Verify: ユーザーが保存されたことを確認
        verify { repo.save(any()) }
        assertTrue(result.isSuccess())
    }
}

Dummy:未使用パラメータを使ったテスト

kotlin
data class Logger(val appContext: Context, val format: FormatType)

fun `test logger with dummy context`() {
    // Dummy: ContextはLogger内で使用されない
    val dummyContext = mockk<Context>()
    val logger = Logger(dummyContext, FormatType.JSON)
    assertEquals(FormatType.JSON, logger.format)
}

モバイル開発で各タイプをいつ使うか

Test Doubleのタイプ選択は、何をテストしているかに依存します:状態、動作、または統合。AndroidおよびiOSのモバイル開発では、以下の推奨事項が確立されています。

ViewModelとUseCaseの場合

ViewModelをテストする際は、副作用を生成する依存関係(リポジトリ、アナリティクス、ナビゲーション)にはMockを使用し、データを返す依存関係(APIクライアント、ContentProvider)にはStubを使用します。これにより、ViewModelが成功シナリオとエラーシナリオの両方を正しく処理することを検証できます。

Repositoryとデータレイヤーの場合

Repositoryレベルでは、Fake(インメモリデータベース実装)とStub(固定APIレスポンス)を優先します。Fakeを使用すると、SQLiteを設定せずにキャッシュロジックとオフラインモードをテストできます。StubはさまざまなHTTPステータス(200、404、500、timeout)をシミュレートします。

  • ビジネスロジックの単体テスト — すべての外部依存関係にMock、未使用パラメータにDummy
  • 統合テスト — Mockの代わりにFake(コンポーネントが連携することを検証)
  • UIテスト — APIレスポンスにStub(MockWebServerまたはWireMock経由)
  • キャッシュテスト — データベースにFake(Room/SQLiteの代わりにインメモリ)
  • 非同期テスト — コルーチン対応のMock(Flow用にMockK + Turbine)

代替オブジェクト使用時のよくある間違い

Test Doublesの誤った使用は、リファクタリングのたびに壊れる脆弱なテストの最も一般的な原因の一つです。

過剰なモッキング:Mockの過剰使用

最も一般的な間違いは、すべてをモックすることです。テスト内のすべての依存関係がMockに置き換えられると、テストは実際の動作を検証しなくなります。Mockは外部依存関係に対してのみ使用すべきです(ネットワーク、データベース、ファイルシステム、システムサービス)。内部アプリケーションコンポーネント(Value Object、data class、単純なユーティリティ)は置き換えるべきではありません。

仕様不足:不十分な指定

2つ目の間違いは、期待値を定義せずにMockを作成することです。every / whenなしでメソッドが呼び出されると、Mockはデフォルト値(null0false)を返します。これにより、Mockが静かにnullを返し、テストがそれを正しい動作として解釈する偽陽性テストにつながる可能性があります。

過剰な検証:verifyの過剰使用

3つ目の間違いは、すべてのMockのすべての呼び出しを検証することです。Verifyは、ビジネスロジックの観点から重要で重要な呼び出しにのみ使用する必要があります。過剰な検証はテストを脆弱にします:プロダクションコードでの呼び出し順序の変更が、動作を変更せずにテストを壊します。

よくある質問

MockとStubの違いは何ですか?

Stubはデータを返し、状態(何が返されたか)を検証します。一方Mockは動作(どのメソッドが呼び出されたか)を検証します。Stub = 「Xを返せ」、Mock = 「Yが引数Zで呼び出されたことを確認せよ」。実際のテストでは、1つのオブジェクトがStubとMockの両方として同時に機能することがよくあります。

Mockの代わりにFakeを使うのはいつですか?

Fakeは、状態に依存するロジック(キャッシング、オフラインモード、トランザクション)をテストする場合にMockより推奨されます。Fake(インメモリ実装)は、脆弱なverify呼び出しなしでこれらのシナリオをテストできます。Mockはデータ送信(アナリティクス、プッシュ、メール)の確認に適しています。

Androidに最適なTest Doublesライブラリは?

KotlinのAndroidプロジェクトにはMockKをお勧めします。追加設定なしでコルーチン、サスペンド関数、シールドクラス、拡張関数をサポートしています。Javaプロジェクトでは、Mockitoが引き続き標準です — 広範なドキュメントを持つ最も人気のあるライブラリです。

Test DoublesでKotlin Flowをテストするには?

Kotlin Flowをテストするには、MockKと一緒にTurbineライブラリを使用します。TurbineはFlowの出力検証を簡素化します:値の順序、ストリームの完了、例外を確認できます。Flow用のStubはflowOf(value)を返し、MockはFlowが収集されたことを検証します。

UIテストでTest Doublesを使用しても問題ありませんか?

はい、ただしAPIレスポンスのレベルであり、UIコンポーネントではありません。MockWebServer(OkHttp)やWireMockなどのライブラリを使用すると、UIテストでHTTPレスポンスをモックできます。UIコンポーネント自体(Compose、SwiftUI Views)は置き換えるべきではありません — それらの動作はスクリーンショットテストとEspressoを通じてテストされます。

まとめ

  • Test Doubles — 5種類の代替オブジェクトの総称:Mock、Stub、Fake、Spy、Dummy
  • Mockは動作を検証(verify)、Stubはデータを返す(thenReturn)、Fakeは簡略化された実際の実装として機能
  • Spyは実際のオブジェクトをラップして呼び出しを記録、Dummyは未使用パラメータを埋める
  • Gerard Meszarosの分類は、すべての最新モッキングフレームワークで使用される標準的な分類です
  • KotlinプロジェクトにはMockK、JavaにはMockito、iOSにはCuckooまたはOHHTTPStubsが推奨
  • よくある間違い:過剰なモッキング(すべてを置き換え)、仕様不足(未定義の期待値)、過剰な検証(verifyの多用)
  • Fakeは状態を持つロジック(キャッシング、オフラインモード、トランザクション)のテストでMockより推奨

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください