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 — е термин от автомобилната индустрия (каскадьор, „dубл“ за актьори), пренесен в разработването на софтуер. Както каскадьорът замества актьор в опасна сцена, Test Double замества реален компонент в тестов сценарий. Това е необходимо, когато реалната зависимост е недостъпна, бавна, недетерминистична или има странични ефекти.

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

Защо са нужни Test Doubles

Основната цел на Test Doubles е изолирането на тествания модул. В мобилното разработване реалните зависимости са API сървъри, бази от данни, файлова система, сензори на устройството, системни услуги (LocationManager, Camera, Bluetooth). Прякото използване на тези компоненти прави тестовете бавни, крехки и зависими от средата. Според Google Testing Blog (2023), добре изолираните единични тестове се изпълняват за милисекунди, а интеграционните тестове за секунди и минути.

Пет вида 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 за зависимостите, които произвеждат странични ефекти (репозиториуми, аналитика, навигация) и Stub за зависимостите, които връщат данни (API клиенти, ContentProvider). Това позволява да се провери, че ViewModel обработва правилно както успешните, така и грешните сценарии.

За Repository и Данновия слой

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

  • Единични тестове на бизнес логиката — 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 трябва да се използва само за извиквания, които са критични от гледна точка на бизнес логиката. Прекалената верификация прави тестовете крехки: промяната на реда на извикванията в производствения код чупи тестовете без промяна на поведението.

Често задавани въпроси

Каква е разликата между Mock и Stub?

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

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

Fake се предпочита пред Mock, когато се тества логика, зависеща от състояние: кеширане, офлайн режим, транзакции. Fake (in-memory имплементация) позволява да се тестват тези сценарии без крехки verify извиквания. Mock е по-подходящ за проверка на изпращане на данни: аналитика, 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 (прекалена верификация)
  • Fake се предпочита пред Mock при тестване на логика с състояние — кеширане, офлайн режим и транзакции

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

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също