Test Doubles — то су објекти-замене, коришћени у јединичном тестирању уместо стварних зависности. Термин је увео Gerard Meszaros у књизи „xUnit Test Patterns" (2007) као заједнички појам за Mock, Stub, Fake, Spy и Dummy. По подацима Martin Fowler (2024), Test Doubles омогућавају изоловање тестираног компонента од његовог окружења, чинећи тестове детерминистичким, брзим и независним од спољних сервиса.
Главно
Test Doubles — то је термин из аутомобилске индустрије (каскадери, „double" за глумце), пренет у развој софтвера. Као што каскадер замењује глумца у опасној сцени, Test Double замењује стварни компонент у тестном сценарију. То је потребно када стварна зависност није доступна, спора је, недетерминистичка или има нуспојаве.
Појам Test Double обухвата пет конкретних типова, од којих сваки решава свој задатак. Типологија Gerard Meszaros-а је канонска и користи се у свим савременим водичима за тестирање. Разлика између типова је у степену контроле и верификације: од једноставног попуњавања параметара (Dummy) до потпуне провере редоследа позива (Mock).
Основна сврха Test Doubles — изолација тестираног модула. У развоју мобилних апликација стварне зависности су API сервери, базе података, систем датотека, сензори уређаја, системски сервиси (LocationManager, Camera, Bluetooth). Директно коришћење ових компоненти чини тестове спорим, крхким и зависним од окружења. По подацима Google Testing Blog (2023), добро изоловани unit тестови се извршавају за милисекунде, а интеграциони — за секунде и минуте.
Класификација Gerard Meszaros-а обухвата пет типова Test Doubles, који се разликују по понашању и сврси коришћења. Разумевање разлике између њих — основа исправног јединичног тестирања.
Dummy — то је објекат који се прослеђује тестираном методу, али се никада не користи. Dummy је потребан само да би се задовољио потпис метода. У Kotlin-у је то често null, emptyList() или објекат са заглушкама. Dummy не сме да садржи никакву логику — ако се позове, тест мора да падне.
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 не проверава да ли је позван — једноставно пружа податке. У Mockito-у се Stub креира помоћу when(method).thenReturn(value). Stub је идеалан за тестирање када је потребно да зависност врати одређену вредност, али сам чин позива није важан.
Spy — то је омот око стварног објекта, који бележи све позиве за каснију верификацију. За разлику од Mock-а, Spy прослеђује позиве стварном објекту, али омогућава проверу да су се догодили. У Mockito-у се Spy креира помоћу spy(realObject). Spy је користан за делимично mocking, када се жели користити стварни објекат, али проверити неке позиве.
Mock — то је објекат са унапред одређеним очекивањима позива. Mock проверава да су одређени методи позвани са одређеним аргументима и у одређеном редоследу. За разлику од Stub-а, Mock се фокусира на верификацију понашања, а не на враћање података. Mock — најмоћнији и најчешће коришћени тип Test Double-а у развоју мобилних апликација.
Разлика између Mock-а и Stub-а често изазива забуну чак и код искусних програмера. Основна разлика је у сврси: Stub проверава стање (state verification), Mock проверава понашање (behavior verification).
Stub одговара на питање: „да ли је код вратио исправан резултат?". Mock одговара на питање: „да ли је код позвао исправне методе са исправним аргументима?". У развоју мобилних апликација Stub се користи када је важан резултат (на пример, подаци из репозиторијума), а Mock — када су важне нуспојаве (на пример, слање email-а, упис у базу података).
// 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-у уз коришћење MockK-а — најпопуларније библиотеке за mocking Android пројеката.
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]
}
}
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())
}
}
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-а користите Mock за зависности које производе нуспојаве (репозиторијуми, analytics, навигација), а Stub за зависности које враћају податке (API клијенти, ContentProvider). То омогућава проверу да ViewModel исправно обрађује и успешне и оне са грешком сценарије.
На нивоу Repository-а пожељни су Fake (in-memory имплементације базе података) и Stub (фиксни API одговори). Fake омогућава проверу логике кеширања и офлајн режима без подешавања SQLite-а. Stub имитира различите HTTP статусе: 200, 404, 500, timeout.
Неправилно коришћење Test Doubles — један од најчешћих узрока крхких тестова, који се ломе при сваком рефакторингу.
Најчешћа грешка — mocking свега и свачега. Ако је свака зависност у тесту замењена Mock-ом, тест престаје да проверава стварно понашање. Mock треба да буде само за спољне зависности (мрежа, БД, систем датотека, системски сервиси). Унутрашњи компоненти апликације (Value Object, data class, једноставне утилите) не треба да се замењују.
Друга грешка — креирање Mock-а без дефинисаних очекивања. Ако се метод позове без every / when, Mock враћа подразумевану вредност (null, 0, false). То може довести до лажно позитивних тестова, када Mock тихо враћа null, а тест то тумачи као исправно понашање.
Трећа грешка — провера сваког позива сваког Mock-а. Verify треба користити само за позиве који су критични са становишта пословне логике. Прекомерна верификација чини тестове крхким: промена редоследа позива у production коду ломи тестове без промене понашања.
Често постављана питања
Stub враћа податке и проверава стање (шта је враћено), а Mock проверава понашање (који методи су позвани). Stub = „врати X", Mock = „провери да су позвали Y са аргументом Z". У стварним тестовима један објекат често истовремено делује и као Stub и као Mock.
Fake је пожељнији од Mock-а када се тестира логика зависна од стања: кеширање, офлајн режим, трансакције. Fake (in-memory имплементација) омогућава проверу тих сценарија без крхких verify позива. Mock је бољи за проверу слања података: analytics, push, email.
За Android пројекте на Kotlin-у препоручује се MockK. Она подржава корутине, suspend функције, sealed class и extension функције без додатних подешавања. За пројекте на Java-и стандард је и даље Mockito — најпопуларнија библиотека са опсежном документацијом.
За тестирање Kotlin Flow-а користите библиотеку Turbine у пару са MockK-ом. Turbine поједностављује проверу емисије Flow-а: могуће је проверити редослед вредности, завршетак тока и изузетке. Stub за Flow враћа flowOf(value), Mock проверава да је Flow сакупљен.
Да, али на нивоу API одговора, а не UI компоненти. Библиотеке MockWebServer (OkHttp) и WireMock омогућавају замену HTTP одговора у UI тестовима. Саме UI компоненте (Compose, SwiftUI Views) не треба замењивати — њихово понашање се тестира кроз screenshot тестове и Espresso.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође