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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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