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 — упрощённая рабочая реализация (например, in-memory база данных)
  • Spy записывает вызовы для последующей верификации, Dummy заполняет параметры

Что такое Test Doubles?

Test Doubles — это термин из автомобильной индустрии (каскадёры, «double» для актёров), перенесённый в разработку программного обеспечения. Как каскадёр заменяет актёра в опасной сцене, Test Double заменяет реальный компонент в тестовом сценарии. Это необходимо, когда реальная зависимость недоступна, медленна, недетерминирована или имеет побочные эффекты.

Понятие Test Double обобщает пять конкретных типов, каждый из которых решает свою задачу. Типология Meszaros является канонической и используется во всех современных руководствах по тестированию. Различие между типами — в степени контроля и верификации: от простого заполнения параметров (Dummy) до полной проверки последовательности вызовов (Mock).

Зачем нужны Test Doubles

Основная цель Test Doubles — изоляция тестируемого модуля. В мобильной разработке реальные зависимости — это API-серверы, базы данных, файловая система, сенсоры устройства, системные сервисы (LocationManager, Camera, Bluetooth). Использование этих компонентов напрямую делает тесты медленными, хрупкими и зависимыми от окружения. По данным Google Testing Blog (2023), хорошо изолированные unit-тесты выполняются за миллисекунды, а интеграционные — за секунды и минуты.

Пять типов Test Doubles

Классификация Gerard Meszaros включает пять типов Test Doubles, различающихся по поведению и цели использования. Понимание разницы между ними — основа грамотного модульного тестирования.

Dummy

Dummy — это объект, который передаётся в тестируемый метод, но никогда не используется. Dummy нужен только для удовлетворения сигнатуры метода. В Kotlin это часто null, emptyList() или объект с заглушками. 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 полезен для частичного mocking, когда хочется использовать реальный объект, но проверить некоторые вызовы.

Mock

Mock — это объект с предопределёнными ожиданиями вызовов. Mock проверяет, что определённые методы были вызваны с определёнными аргументами и в определённой последовательности. В отличие от Stub, Mock фокусируется на верификации поведения, а не на возврате данных. Mock — самый мощный и самый часто используемый тип Test Double в мобильной разработке.

Mock и Stub: ключевые различия

Различие между Mock и Stub часто вызывает путаницу даже у опытных разработчиков. Основная разница — в цели: Stub проверяет состояние (state verification), Mock проверяет поведение (behavior verification).

Stub отвечает на вопрос: «вернул ли код правильный результат?». Mock отвечает на вопрос: «вызвал ли код правильные методы с правильными аргументами?». В мобильной разработке Stub используется, когда важен результат (например, данные из репозитория), а Mock — когда важны побочные эффекты (например, отправка email, запись в базу данных).

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") }

Примеры Test Doubles на Kotlin

Практические примеры всех пяти типов Test Doubles на Kotlin с использованием MockK — наиболее популярной библиотеки mocking для 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 для зависимостей, которые производят побочные эффекты (репозитории, analytics, навигация), и Stub для зависимостей, которые возвращают данные (API-клиенты, ContentProvider). Это позволяет проверить, что ViewModel правильно обрабатывает как успешные, так и ошибочные сценарии.

Для Repository и Data Layer

На уровне Repository предпочтительны Fake (in-memory реализации базы данных) и Stub (фиксированные ответы API). Fake позволяет проверить логику кэширования и офлайн-режима без настройки SQLite. Stub имитирует различные HTTP-статусы: 200, 404, 500, timeout.

  • Unit-тесты бизнес-логики — Mock для всех внешних зависимостей, Dummy для неиспользуемых параметров
  • Интеграционные тесты — Fake вместо Mock (проверяем, что компоненты работают вместе)
  • UI-тесты — Stub для API-ответов (через MockWebServer или WireMock)
  • Тесты кэширования — Fake для базы данных (in-memory вместо Room/SQLite)
  • Тесты асинхронности — Mock с поддержкой корутин (MockK + Turbine для Flow)

Типовые ошибки при использовании заменителей

Неправильное использование Test Doubles — одна из самых частых причин хрупких тестов, которые ломаются при каждом рефакторинге.

Over-mocking: избыточное использование Mock

Самая распространённая ошибка — mocking всего подряд. Если каждая зависимость в тесте заменена на Mock, тест перестаёт проверять реальное поведение. Mock должен быть только для внешних зависимостей (сеть, БД, файловая система, системные сервисы). Внутренние компоненты приложения (Value Object, data class, простые утилиты) не должны заменяться.

Under-specification: недостаточная спецификация

Вторая ошибка — создание Mock без определения ожиданий. Если метод вызывается без every / when, Mock возвращает значение по умолчанию (null, 0, false). Это может привести к ложно-положительным тестам, когда Mock молча возвращает null, а тест интерпретирует это как корректное поведение.

Over-verification: избыточная верификация

Третья ошибка — проверка каждого вызова каждого Mock. Verify должен использоваться только для вызовов, критически важных с точки зрения бизнес-логики. Избыточная верификация делает тесты хрупкими: изменение порядка вызовов в production-коде ломает тесты без изменения поведения.

Часто задаваемые вопросы

В чём разница между Mock и Stub?

Stub возвращает данные и проверяет состояние (что вернулось), а Mock проверяет поведение (какие методы вызывались). Stub = «верни X», Mock = «проверь, что вызвали Y с аргументом Z». В реальных тестах один объект часто выступает и Stub, и Mock одновременно.

Когда использовать Fake вместо Mock?

Fake предпочтительнее Mock, когда тестируется логика, зависящая от состояния: кэширование, офлайн-режим, транзакции. Fake (in-memory реализация) позволяет проверить эти сценарии без хрупких verify-вызовов. Mock лучше подходит для проверки отправки данных: analytics, push, email.

Какая библиотека Test Doubles лучше для Android?

Для Android-проектов на Kotlin рекомендуется MockK. Она поддерживает корутины, suspend-функции, sealed class и extension-функции без дополнительных настроек. Для проектов на Java по-прежнему стандартом является Mockito — самая популярная библиотека с обширной документацией.

Как тестировать Kotlin Flow с Test Doubles?

Для тестирования Kotlin Flow используйте библиотеку Turbine в паре с MockK. Turbine упрощает проверку эмиссии Flow: можно проверить порядок значений, завершение потока и исключения. Stub для Flow возвращает flowOf(value), Mock проверяет, что Flow был собран.

Допустимо ли использовать Test Doubles в UI-тестах?

Да, но на уровне API-ответов, а не UI-компонентов. Библиотеки MockWebServer (OkHttp) и WireMock позволяют подменять HTTP-ответы в UI-тестах. Сами UI-компоненты (Compose, SwiftUI Views) не должны заменяться — их поведение тестируется через screenshot-тесты и Espresso.

Итоги

  • Test Doubles — общий термин для пяти типов объектов-заменителей: Mock, Stub, Fake, Spy, Dummy
  • Mock проверяет поведение (verify), Stub возвращает данные (thenReturn), Fake — работает как упрощённая реальная реализация
  • Spy оборачивает реальный объект и записывает вызовы, Dummy заполняет неиспользуемые параметры
  • Типология Gerard Meszaros — каноническая классификация, используемая во всех современных фреймворках mocking
  • Для Kotlin-проектов рекомендуется MockK, для Java — Mockito, для iOS — Cuckoo или OHHTTPStubs
  • Типовые ошибки: over-mocking (замена всего подряд), under-specification (неопределённые ожидания), over-verification (избыточные verify)
  • Fake предпочтительнее Mock при тестировании логики с состоянием — кэширования, офлайн-режима и транзакций

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также