Test Doubles — olyan helyettesítő objektumok, amelyeket egységtesztekben használnak valós függőségek helyett. A kifejezést Gerard Meszaros vezette be „xUnit Test Patterns“ (2007) című könyvében a Mock, Stub, Fake, Spy és Dummy átfogó fogalmaként. Martin Fowler (2024) szerint a Test Doubles lehetővé teszi a tesztelt komponens elkülönítését a környezetétől, ami determinisztikussá, gyorssá és külső szolgáltatásoktól függetlenné teszi a teszteket.
FŐbb pontok
Test Doubles — az autóiparból származó kifejezés (kaszkadőr, „dublőr“ a színészek számára), átültetve a szoftverfejlesztésbe. Ahogy a kaszkadőr helyettesíti a színészt egy veszélyes jelenetben, a Test Double helyettesíti a valós komponenst egy tesztforgatókönyvben. Ez akkor szükséges, amikor a valós függőség nem érhető el, lassú, nem determinisztikus, vagy mellékhatásai vannak.
A Test Double fogalma öt konkrét típust foglal magában, amelyek mindegyike saját feladatát oldja meg. Meszaros tipológiája kanonikus, és minden modern tesztelési útmutatóban használatos. A típusok közötti különbség az ellenőrzés és verifikáció mértékében rejlik: az egyszerű paraméterkitöltéstől (Dummy) a hívási sorrend teljes ellenőrzéséig (Mock).
A Test Doubles fő célja a tesztelt modul elkülönítése. A mobil fejlesztésben a valós függőségek API szerverek, adatbázisok, fájlrendszer, eszköz szenzorok, rendszerszolgáltatások (LocationManager, Camera, Bluetooth). Ezek közvetlen használata lassú, törékeny és környezetfüggő teszteket eredményez. A Google Testing Blog (2023) szerint a jól elkülönített egységtesztek ezredmásodpercek alatt futnak, az integrációs tesztek pedig másodpercek és percek alatt.
Gerard Meszaros osztályozása öt típusú Test Doubles-t foglal magában, amelyek viselkedésük és használati céljuk alapján különböznek. A köztük lévő különbségek megértése a helyes egységtesztelés alapja.
Dummy — egy objektum, amelyet a tesztelt metódusnak adunk át, de soha nem használunk. A Dummy csak a metódus aláírásának kielégítéséhez szükséges. Kotlinban ez gyakran null, emptyList() vagy egy objektum helyettesítőkkel. A Dummy nem tartalmazhat semmilyen logikát — ha meghívásra kerül, a tesztnek meg kell buknia.
Fake — egy interfész egyszerűsített, de működő implementációja. A Mock-tól és Stub-tól eltérően a Fake valós üzleti logikát tartalmaz, de egyszerűsített formában. A klasszikus példa — InMemoryUserRepository, amely adatokat HashMap-ben tárolja adatbázis helyett. A Fake akkor használatos, amikor az állapotfüggő logikát kell tesztelni, de a valós infrastruktúra túlterhelése nélkül.
| Típus | Cél | Példa |
|---|---|---|
| Dummy | Paraméter kitöltése | null, üres objektum |
| Fake | Működő egyszerűsített implementáció | InMemoryRepository |
| Stub | Rögzített érték visszaadása | when(api.getUser()).thenReturn(user) |
| Spy | Hívások rögzítése ellenőrzéshez | verify(spy).save(user) |
| Mock | Interakció ellenőrzése | verify(mock).sendEmail(email) |
Stub előre meghatározott értékeket ad vissza bizonyos hívásokra. A Stub nem ellenőrzi, hogy meghívták-e — csak adatokat szolgáltat. Mockito-ban a Stub a when(method).thenReturn(value) segítségével jön létre. A Stub ideális tesztelésre, amikor a függőségnek egy adott értéket kell visszaadnia, de maga a hívás ténye nem számít.
Spy — egy burkoló egy valós objektum körül, amely rögzíti az összes hívást későbbi verifikációhoz. A Mock-tól eltérően a Spy átirányítja a hívásokat a valós objektumhoz, de lehetővé teszi annak ellenőrzését, hogy azok megtörténtek. Mockito-ban a Spy a spy(realObject) segítségével jön létre. A Spy részleges mocking-hoz hasznos, amikor egy valós objektumot szeretnénk használni, de néhány hívást ellenőrizni szeretnénk.
Mock — egy objektum előre meghatározott hívási elvárásokkal. A Mock ellenőrzi, hogy bizonyos metódusokat bizonyos argumentumokkal és bizonyos sorrendben hívtak-e meg. A Stub-tól eltérően a Mock a viselkedés verifikációjára összpontosít, nem az adatok visszaadására. A Mock a legerősebb és leggyakrabban használt Test Double típus a mobil fejlesztésben.
A Mock és Stub közötti különbség gyakran okoz zavart még tapasztalt fejlesztők körében is. A fő különbség a célban van: a Stub állapotot ellenőriz (state verification), a Mock viselkedést ellenőriz (behavior verification).
A Stub arra a kérdésre válaszol: „a kód a megfelelő eredményt adta vissza?“. A Mock arra a kérdésre válaszol: „a kód a megfelelő metódusokat hívta meg a megfelelő argumentumokkal?“. A mobil fejlesztésben a Stub akkor használatos, amikor az eredmény számít (pl. adatok a repozitóriumból), a Mock pedig amikor a mellékhatások számítanak (pl. email küldése, adatbázisba írás).
// Stub: állapot ellenőrzése
every { repository.getUsers() } returns listOf(user)
val result = useCase.getUsers()
assertEquals(1, result.size)
// Mock: viselkedés ellenőrzése
every { analytics.logEvent("purchase") } returns Unit
useCase.purchase(item)
verify { analytics.logEvent("purchase") }
Gyakorlati példák mind az öt típusú Test Doubles-re Kotlinban a MockK — a legnépszerűbb mocking könyvtár Android projektekhez — használatával.
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: fix API válasz visszaadása
coEvery { api.register("test@test.com") } returns AuthResult.Success("token123")
val result = useCase.execute("test@test.com")
// Verify: annak ellenőrzése, hogy a felhasználó el lett mentve
verify { repo.save(any()) }
assertTrue(result.isSuccess())
}
}
data class Logger(val appContext: Context, val format: FormatType)
fun `test logger with dummy context`() {
// Dummy: Context nem használatos a Logger-en belül
val dummyContext = mockk<Context>()
val logger = Logger(dummyContext, FormatType.JSON)
assertEquals(FormatType.JSON, logger.format)
}
A Test Double típus kiválasztása attól függ, hogy pontosan mit tesztelünk: állapotot, viselkedést vagy integrációt. Android és iOS mobil fejlesztésben az alábbi ajánlások alakultak ki.
ViewModel tesztelésekor használjon Mock-ot a mellékhatásokat okozó függőségekhez (repozitóriumok, analitika, navigáció) és Stub-ot az adatokat visszaadó függőségekhez (API kliensek, ContentProvider). Ez lehetővé teszi annak ellenőrzését, hogy a ViewModel helyesen kezeli mind a sikeres, mind a hibás forgatókönyveket.
A Repository szintjén a Fake (in-memory adatbázis implementációk) és a Stub (fix API válaszok) előnyben részesülnek. A Fake lehetővé teszi a gyorsítótár logika és offline mód tesztelését SQLite konfiguráció nélkül. A Stub különböző HTTP állapotokat szimulál: 200, 404, 500, timeout.
A Test Doubles helytelen használata — az egyik leggyakoribb oka a törékeny teszteknek, amelyek minden átalakításnál elromlanak.
A leggyakoribb hiba — mindennek a mockolása. Ha egy tesztben minden függőséget Mock helyettesít, a teszt megszűnik ellenőrizni a valós viselkedést. Mock csak külső függőségekhez használatos (hálózat, adatbázis, fájlrendszer, rendszerszolgáltatások). Az alkalmazás belső komponenseit (Value Object, data class, egyszerű segédprogramok) nem szabad helyettesíteni.
A második hiba — Mock létrehozása az elvárások meghatározása nélkül. Ha egy metódust every / when nélkül hívunk meg, a Mock alapértelmezett értéket ad vissza (null, 0, false). Ez hamis pozitív tesztekhez vezethet, amikor a Mock néma null-t ad vissza, és a teszt ezt helyes viselkedésként értelmezi.
A harmadik hiba — minden Mock minden hívásának ellenőrzése. A Verify-t csak az üzleti logika szempontjából kritikus hívásokhoz szabad használni. A túlzott verifikáció törékennyé teszi a teszteket: a hívások sorrendjének megváltozása a termelési kódban viselkedésváltozás nélkül töri el a teszteket.
Gyakran ismételt kérdések
Stub adatokat ad vissza és állapotot ellenőriz (mi lett visszaadva), míg Mock viselkedést ellenőriz (mely metódusok lettek meghívva). Stub = „ad vissza X“, Mock = „ellenőrizd, hogy Y meghívásra került Z argumentummal“. Valós tesztekben egy objektum gyakran egyszerre tölt be Stub és Mock szerepet.
A Fake előnyben részesítendő a Mock-kal szemben, amikor állapotfüggő logikát tesztelünk: gyorsítótár, offline mód, tranzakciók. A Fake (in-memory implementáció) lehetővé teszi e forgatókönyvek tesztelését törékeny verify hívások nélkül. A Mock alkalmasabb az adatküldés ellenőrzésére: analitika, push értesítések, email.
Kotlin Android projektekhez a MockK ajánlott. Támogatja a korutinokat, suspend függvényeket, sealed class-okat és extension függvényeket külön konfiguráció nélkül. Java projektekhez továbbra is a Mockito a szabvány — a legnépszerűbb könyvtár kiterjedt dokumentációval.
A Kotlin Flow teszteléséhez használja a Turbine könyvtárat a MockK-val együtt. A Turbine leegyszerűsíti a Flow emissziójának ellenőrzését: ellenőrizhető az értékek sorrendje, az áramlás befejeződése és a kivételek. A Stub Flow esetén flowOf(value) értéket ad vissza, a Mock ellenőrzi, hogy a Flow összegyűjtésre került.
Igen, de az API válaszok szintjén, nem a UI komponensekén. A MockWebServer (OkHttp) és WireMock könyvtárak lehetővé teszik a HTTP válaszok helyettesítését UI tesztekben. Maguk a UI komponensek (Compose, SwiftUI Views) nem helyettesítendők — viselkedésük screenshot tesztekkel és Espresso-val tesztelendő.
Összefoglaló
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is