Fake (féjk) — egy működő egyszerűsített függőség-implementáció, amely úgy viselkedik, mint egy valódi komponens, de in-memory tárolást vagy más könnyű mechanizmusokat használ éles infrastruktúra helyett. A stub-bal ellentétben a fake valódi üzleti logikát tartalmaz — rendezést, szűrést, aggregálást — csak külső hatások nélkül. In-memory adatbázis Room helyett vagy HashMap SharedPreferences helyett — klasszikus példák. Bővebben Martin Fowler test doubles osztályozásában.
Főbb pontok
Fake — egy teljes, de könnyű interfész-implementáció, amely alkalmas tesztelésre. A kifejezést Gerard Meszaros (2007) vezette be az «xUnit Test Patterns» című könyvben. A stub-bal ellentétben, amely szigorúan előre meghatározott válaszokat ad vissza, a fake végrehajtható kódot tartalmaz: képes rendezni egy listát, feltétel szerint szűrni, megszámolni a rekordokat. Az egyetlen különbség az éles implementációtól — a fake in-memory adatokkal dolgozik és nem végez valódi I/O-műveleteket.
Fő előny — sebesség. A fake-kel végzett tesztek milliszekundumok alatt futnak le, mert nincs hozzáférés a lemezhez, hálózathoz vagy adatbázishoz. Az in-memory HashMap 100-1000-szer gyorsabban működik, mint a Room vagy a CoreData. Ugyanakkor a fake ellenőrzi a valódi üzleti logikát: rendezést, szűrést, aggregálást — mindazt, amit a stub nem tud ellenőrizni, mert a stub csak azt adja vissza, amit neki mondtak. A fake biztosítja, hogy a kód helyesen dolgozza fel az adatokat, nem csak egy előre meghatározott választ kap.
A Fake jobb, mint a Stub — ha a tesztelt komponens több műveletet végez az adatokon (kiolvasott, szűrt, rendezett, mentett), a stub minden hívást külön konfigurálást igényel. A fake a logikát tartalmazza — a teszt egyszerűen meghívja a metódusokat és ellenőrzi az eredményt. Az IT Sectr-ben minden adattárhoz fake-et használunk az egységtesztekben: a HashMap-es fake-adattár a forgatókönyvek 90%-át lefedi Mockito vagy MockK konfiguráció nélkül.
Választási szempont — határozza meg, mit ellenőriz a teszt: állapotot vagy interakciót. Ha a teszt állapotot (a munka eredményét) ellenőrzi és logikát használ — fake szükséges. Ha a tesztnek csak bemeneti adatokra van szüksége logika nélkül — stub is elegendő. Ha a teszt egy metódus meghívásának tényét ellenőrzi — mock szükséges. A test doubles típusainak keverése egy tesztben megnehezíti a megértést és növeli a törékenységet.
| Szempont | Fake | Stub | Mock |
|---|---|---|---|
| Logika megléte | Igen (egyszerűsített) | Nem | Nem |
| Sebesség | Magas | Maximális | Magas |
| Viselkedés ellenőrzése | Közvetett | Nem | Igen (verify) |
| Karbantartás | Egy osztály interfészenként | Konfiguráció tesztenként | Konfiguráció tesztenként |
| Realizmus | Magas (kód működik) | Alacsony (rögzített adatok) | Közepes |
| Hamis riasztások kockázata | Alacsony | Közepes | Magas (törékeny tesztek) |
Antiminta: Fake, ami nem fake — gyakori hiba, amikor a fejlesztő fake-nek nevez egy objektumot, ami valójában stub vagy mock. Ha az InMemoryUserRepository nem tartalmaz logikát (szűrést, rendezést) — ez nem fake, hanem stub in-memory tárolással. A fake éppen a végrehajtható logika jelenlétében különbözik a stub-tól. Ha a fake-adattár csak visszaadja, amit belehelyeztek, és nem dolgozza fel az adatokat — használjon mock-ot vagy stub-ot.
Gyakorlati javaslat — kezdje fake-kel minden adattár vagy szolgáltatás esetén. Ha a fake 50 sornál összetettebb — ossza fel több osztályra. Ha a fake egyáltalán nem szükséges (a teszt csak egy forgatókönyvet ellenőriz rögzített adatokkal) — használjon stub-ot. Ha a teszt ellenőrzi, hogy egy metódust meghívtak-e bizonyos paraméterekkel — használjon mock-ot. Ne optimalizálja előre a választást: írjon fake-et, és ha túlzónak bizonyul, cserélje le stub-ra az adott tesztben.
Fake-adattár Room számára — tipikus példa fake-re Androidon. Az éles UserRepository implementáció Room DAO-t használ SQLite lekérdezésekkel. A fake verzió az adatokat MutableList-ben vagy HashMap-ben tárolja, és ugyanazokat a metódusokat implementálja: getUser(id), saveUser(user), deleteUser(id). A fake tartalmazza a keresési, szűrési és rendezési logikát — ugyanazt, mint az éles adattárban, de SQL nélkül. Ez lehetővé teszi a ViewModel és UseCase tesztelését a Room adatbázis konfigurálása nélkül.
class FakeUserRepository : UserRepository {
private val users = mutableListOf<User>()
override suspend fun getUser(id: String): User? {
return users.find { it.id == id }
}
override suspend fun saveUser(user: User) {
val index = users.indexOfFirst { it.id == user.id }
if (index >= 0) users[index] = user
else users.add(user)
}
override suspend fun search(query: String): List<User> {
return users.filter {
it.name.contains(query, ignoreCase = true)
}
}
}
Fake Retrofit API-hoz — a MockWebServer (ami stub, nem fake) helyett létrehozható egy ApiService implementáció, amely in-memory gyűjteményből ad vissza adatokat. Különbség: a MockWebServer elfogja a HTTP-t és JSON-t ad vissza, míg a fake-ApiService a Kotlin interfész szintjén dolgozik szerializálás nélkül. A fake gyorsabb (nincs JSON-elemzés) és egyszerűbb hibakeresés (ugyanabban a folyamatban dolgozik, típusos). Alkalmas olyan tesztekhez, ahol a HTTP szemantika (státuszkódok, fejlécek) nem számít.
Fake Swift-ben — protokollokon keresztül épül fel. Az éles osztály valódi logikával (CoreData, URLSession) implementálja a protokollt. A fake struktúra ugyanazt a protokollt implementálja in-memory tárolóval és egyszerűsített logikával. A Swift értékszemantikájú nyelv, ezért a fake struktúrák megváltoztathatatlanok és biztonságosak többszálú tesztekben. Ez előnyt jelent az Android-analógokkal szemben: nem kell szinkronizálni az in-memory adatokhoz való hozzáférést.
protocol UserRepositoryProtocol {
func getUser(id: String) async -> User?
func saveUser(user: User) async
}
final class FakeUserRepository: UserRepositoryProtocol {
private var storage: [String: User] = [:]
func getUser(id: String) async -> User? {
return storage[id]
}
func saveUser(user: User) async {
storage[user.id] = user
}
}
final class UserViewModelTests: XCTestCase {
func test_save_and_load() async {
let fake = FakeUserRepository()
let vm = UserViewModel(repository: fake)
let user = User(id: "1", name: "Alice")
await vm.saveUser(user)
let loaded = await vm.getUser(id: "1")
XCTAssertEqual(loaded?.name, "Alice")
}
}
Fake CoreData-hoz — iOS-projektekben létrehozható egy in-memory NSPersistentContainer a description.type = NSInMemoryStoreType beállításával. Ez egy teljes CoreData stack, amely memóriában dolgozik. Az ilyen fake lehetővé teszi NSFetchRequest, predikátumok és rendezések tesztelését SQLite fájl létrehozása nélkül. Sebesség: az in-memory CoreData tesztek 5-10-szer gyorsabban futnak, mint a lemezes analóg. Hátrány: az NSManagedObjectModel-t minden alkalommal konfigurálni kell.
FakeURLProtocol — az URLProtocol alosztálya hálózati kérések elfogására iOS-en. Az URLProtocol.registerClass(fakeProtocol) segítségével regisztrálható. Belül egy in-memory URL -> Data szótárt tartalmaz, és adatokat ad vissza valódi kérés nélkül. Különbség a stub-tól: a FakeURLProtocol ellenőrizheti a kérés törzsét, fejléceket, és különböző válaszokat adhat vissza a bemeneti adatoktól függően. Ez fake, mert kérés-útvonalválasztó logikát tartalmaz.
Fake mint Test Fixture — helyezze át a fake osztályokat egy megosztott tesztmodulba (androidTest/sharedTest vagy TestSupport). A projekt összes tesztje ugyanazt az InMemoryUserRepository-t használja. Ez kiküszöböli a mock objektumok konfigurálásának megkettőzését minden tesztben és egységes viselkedést garantál. A fake logikájának változása az összes tesztet egyszerre frissíti. Az IT Sectr-ben a fake osztályokat a sharedTest/java/com/itSectr/fake/ könyvtárban tároljuk és az implementation project(:sharedTest) segítségével csatlakoztatjuk.
Fake előre beállított adatokkal — gyakran a teszteknek olyan adattárra van szükségük, amely már tartalmaz néhány rekordot. Megoldás: fakeWithData(vararg items) gyári metódus vagy beépített addDefaultData() metódus. A gyár létrehozza a fake-et, feltölti tipikus adatokkal és visszaadja a használatra kész objektumot. Ez csökkenti a boilerplate-et a tesztekben: a mock hívások konfigurálása helyett a teszt egyszerűen meghívja a FakeUserRepository.withUsers(alice, bob) függvényt.
Fake hívásszámlálással — néha nem csak az állapotot, hanem a hívások számát is ellenőrizni kell. A fake számlálókat tartalmazhat: saveCallCount, getUserCallCount. A teszt a számlálót ellenőrzi a végrehajtás után. Ez kompromisszum a tiszta fake (állapot ellenőrzése) és a mock (interakció ellenőrzése) között. A számlálók nem ellenőrzik az argumentumokat és a hívások sorrendjét — csak a számot. Az argumentumok ellenőrzéséhez használjon mock-ot.
Fake Callback-kel — aszinkron forgatókönyvek teszteléséhez a fake minden híváskor fogadhat egy callback-et: beforeGetUser, afterSaveUser. Ez lehetővé teszi késleltetések, hibák szimulálását vagy köztes állapotok ellenőrzését. Ez a megközelítés hasznos az UI betöltési állapotainak teszteléséhez: a fake 100 ms szünetet tart, és a teszt ellenőrzi, hogy a képernyő betöltőt mutat. Éles környezetben a callback nem létezik — ez tisztán tesztfunkcionalitás.
Gyakran ismételt kérdések
Fake működő logikát tartalmaz — szűr, rendez, számol. A Stub csak előre meghatározott válaszokat ad vissza logika nélkül. Ha az objektumnak elágazásai vannak (if/else, when) — ez fake. Ha csak return values-t tartalmaz — ez stub. A fake drágább karbantartani, de realisztikusabb teszteket ad.
Amikor a fake logikája nem egyezik az éles logikával. Például a FakeUserRepository case-sensitive keresést használ, míg az éles case-insensitive-t. A teszt átmegy, de a valóságban hiba van. Megoldás: tesztelje a fake logikáját külön, vagy használjon fake-eket csak egyszerű logikájú interfészekhez (CRUD-műveletek). Összetett logikához írjon integrációs teszteket valódi adatbázissal.
In-memory database — a fake egyik változata. A Room.inMemoryDatabaseBuilder() egy in-memory SQLite-ot hoz létre, amely úgy viselkedik, mint egy éles adatbázis. Ez egy teljes fake. De a fake lehet adattár szinten (SQL nélkül) és hálózati szinten (FakeApiService) is. Az in-memory adatbázis a fake speciális esete, ahol a logika maximálisan közelít a valódihoz.
Igen, de óvatosan. Fake az adattárhoz (adatok), Mock az AnalyticsTracker-hez (események ellenőrzése). Rétegenkénti szétválasztás: fake az adatréteghez, mock az analitikai/naplózási réteghez. Ne tegyen egy objektumot egyszerre fake-é és mock-ká — ez sérti az egységes felelősség elvét és összezavarja a tesztet.
Tesztelje a fake-et ugyanazokkal a tesztekkel, mint az éles implementációt. Ha van UserRepositoryTest, ami ellenőrzi a save, get, delete függvényeket — futtassa kétszer: FakeUserRepository-val és RealUserRepository-val. Ez garantálja, hogy a fake megismétli az éles osztály viselkedését. Ha a fake máshogy kezd viselkedni — a teszt mindkét implementáción megbukik.
Ö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