Fake (feik) — o implementare simplificată funcțională a dependenței care se comportă ca un component real, dar folosește stocarea in-memory sau alte mecanisme ușoare în locul infrastructurii de producție. Spre deosebire de stub, fake conține logică de afaceri reală — sortare, filtrare, agregare — doar fără efecte externe. Baza de date in-memory în loc de Room sau HashMap în loc de SharedPreferences — exemple clasice. Mai multe detalii în clasificarea test doubles de Martin Fowler.
Principalele
Fake — este o implementare completă, dar ușoară a unei interfețe, potrivită pentru testare. Termenul a fost introdus de Gerard Meszaros (2007) în cartea «xUnit Test Patterns». Spre deosebire de stub, care returnează răspunsuri strict prestabilite, fake conține cod executabil: poate sorta o listă, filtra după o condiție, număra înregistrările. Singura diferență față de implementarea de producție — fake lucrează cu date in-memory și nu efectuează operații I/O reale.
Avantajul principal — viteza. Testele cu fake se execută în milisecunde, deoarece nu există acces la disc, rețea sau bază de date. HashMap in-memory funcționează de 100-1000 de ori mai rapid decât Room sau CoreData. În același timp, fake verifică logica reală de afaceri: sortare, filtrare, agregare — tot ceea ce stub nu poate verifica, deoarece stub returnează doar ceea ce i s-a spus. Fake oferă încredere că codul procesează corect datele, nu doar primește un răspuns prestabilit.
Fake este preferabil Stub — dacă componentul testat efectuează mai multe operații asupra datelor (a extras, a filtrat, a sortat, a salvat), stub va necesita configurarea fiecărei apelări separat. Fake conține logica în interior — testul doar apelează metodele și verifică rezultatul. În IT Sectr folosim fake pentru toate depozitele în testele unitare: depozitul fake cu HashMap acoperă 90% din scenarii fără configurare Mockito sau MockK.
Criteriul de alegere — determinați ce verifică testul: starea sau interacțiunea. Dacă testul verifică starea (rezultatul lucrului) și folosește logică — aveți nevoie de fake. Dacă testul are nevoie doar de date de intrare fără logică — este suficient stub. Dacă testul verifică faptul apelării unei metode — aveți nevoie de mock. Amestecarea tipurilor de test doubles într-un singur test complică înțelegerea și crește fragilitatea.
| Criteriu | Fake | Stub | Mock |
|---|---|---|---|
| Prezența logicii | Da (simplificată) | Nu | Nu |
| Viteză | Ridicată | Maximă | Ridicată |
| Verificare comportament | Indirectă | Nu | Da (verify) |
| Întreținere | O clasă per interfață | Configurare pentru fiecare test | Configurare pentru fiecare test |
| Realism | Ridicat (codul funcționează) | Scăzut (date fixe) | Mediu |
| Risc de alarme false | Scăzut | Mediu | Ridicat (teste fragile) |
Antipattern: Fake care nu este fake — o greșeală frecventă când dezvoltatorul numește fake un obiect care de fapt este stub sau mock. Dacă InMemoryUserRepository nu conține logică (filtrare, sortare) — acesta nu este fake, ci stub cu stocare in-memory. Fake se deosebește de stub tocmai prin prezența logicii executabile. Dacă depozitul fake doar returnează ceea ce i s-a pus și nu procesează date — folosiți mock sau stub.
Recomandare practică — începeți cu fake pentru fiecare depozit sau serviciu. Dacă fake este mai complex de 50 de linii — împărțiți în mai multe clase. Dacă fake nu este deloc necesar (testul verifică doar un scenariu cu date fixe) — folosiți stub. Dacă testul verifică că o metodă a fost apelată cu anumiți parametri — folosiți mock. Nu optimizați alegerea dinainte: scrieți fake, iar dacă se dovedește redundant, înlocuiți-l cu stub în testul respectiv.
Depozit Fake pentru Room — exemplu tipic de fake pe Android. Implementarea de producție UserRepository folosește Room DAO cu interogări SQLite. Versiunea fake stochează datele în MutableList sau HashMap și implementează aceleași metode: getUser(id), saveUser(user), deleteUser(id). Fake conține logică de căutare, filtrare și sortare — aceeași ca în depozitul de producție, dar fără SQL. Acest lucru permite testarea ViewModel și UseCase fără configurarea bazei Room.
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 pentru Retrofit API — în loc de MockWebServer (care este stub, nu fake) se poate crea o implementare ApiService care returnează date dintr-o colecție in-memory. Diferența: MockWebServer interceptează HTTP și returnează JSON, iar fake-ApiService funcționează la nivelul interfeței Kotlin fără serializare. Fake este mai rapid (fără parsare JSON) și mai simplu de depanat (funcționează în același proces, tipizat). Potrivit pentru teste unde semantica HTTP (coduri de stare, antete) nu este importantă.
Fake în Swift — se construiește prin protocoale. Clasa de producție implementează protocolul cu logică reală (CoreData, URLSession). Structura fake implementează același protocol cu stocare in-memory și logică simplificată. Swift este un limbaj cu semantică de valoare, deci structurile fake sunt imutabile și sigure în teste multi-thread. Acest lucru oferă un avantaj față de analogii pe Android: nu este nevoie să sincronizați accesul la datele in-memory.
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 pentru CoreData — în proiectele iOS se poate crea un NSPersistentContainer in-memory, setând description.type = NSInMemoryStoreType. Acesta este un stack CoreData complet care funcționează în memorie. Un astfel de fake permite testarea NSFetchRequest, predicatelor și sortărilor fără a crea un fișier SQLite. Viteză: testele pe CoreData in-memory se execută de 5-10 ori mai rapid decât pe analogul pe disc. Dezavantaj: trebuie configurat NSManagedObjectModel de fiecare dată.
FakeURLProtocol — o subclasă a URLProtocol pentru interceptarea cererilor de rețea pe iOS. Se înregistrează prin URLProtocol.registerClass(fakeProtocol). În interior conține un dicționar in-memory URL -> Data și returnează date fără cerere reală. Diferența de stub: FakeURLProtocol poate verifica corpul cererii, antetele și poate returna răspunsuri diferite în funcție de datele de intrare. Acesta este fake deoarece conține logică de rutare a cererilor.
Fake ca Test Fixture — mutați clasele fake într-un modul de test comun (androidTest/sharedTest sau TestSupport). Toate testele din proiect folosesc același InMemoryUserRepository. Acest lucru elimină duplicarea configurării obiectelor mock în fiecare test și garantează un comportament uniform. Modificarea logicii fake actualizează toate testele simultan. În IT Sectr păstrăm clasele fake în sharedTest/java/com/itSectr/fake/ și le conectăm prin implementation project(:sharedTest).
Fake cu date prestabilite — adesea testele au nevoie de un depozit care conține deja unele înregistrări. Soluție: metoda fabrică fakeWithData(vararg items) sau metoda incorporată addDefaultData(). Fabrica creează fake, îl umple cu date tipice și returnează obiectul gata de utilizare. Acest lucru reduce boilerplate-ul în teste: în loc să configurați apelări mock, testul doar apelează FakeUserRepository.withUsers(alice, bob).
Fake cu numărare de apeluri — uneori trebuie verificat nu doar starea, ci și numărul de accesări. Fake poate conține contoare: saveCallCount, getUserCallCount. Testul verifică contorul după execuție. Acesta este un compromis între fake pur (verificarea stării) și mock (verificarea interacțiunii). Contoarele nu verifică argumentele și ordinea apelurilor — doar numărul. Pentru verificarea argumentelor folosiți mock.
Fake cu Callback — pentru testarea scenariilor asincrone, fake poate accepta un callback la fiecare apel: beforeGetUser, afterSaveUser. Acest lucru permite simularea întârzierilor, erorilor sau verificarea stărilor intermediare. O astfel de abordare este utilă pentru testarea stărilor de încărcare UI: fake face o pauză de 100 ms, iar testul verifică dacă ecranul arată un loader. În producție callback-ul lipsește — este o funcționalitate pur de test.
Întrebări frecvente
Fake conține logică funcțională — filtrează, sortează, numără. Stub returnează doar răspunsuri prestabilite fără logică. Dacă obiectul are ramificări (if/else, when) — acesta este fake. Dacă conține doar return values — este stub. Fake este mai scump de întreținut, dar oferă teste mai realiste.
Când logica fake nu coincide cu logica de producție. De exemplu, FakeUserRepository folosește căutare case-sensitive, iar producția — case-insensitive. Testul trece, dar în realitate există o eroare. Soluție: testați logica fake separat sau folosiți fakes doar pentru interfețe cu logică simplă (operații CRUD). Pentru logică complexă scrieți teste de integrare cu baza de date reală.
In-memory database — una dintre variantele de fake. Room.inMemoryDatabaseBuilder() creează un SQLite in-memory care se comportă ca o bază de date de producție. Acesta este un fake complet. Dar fake poate fi și la nivel de depozit (fără SQL) și la nivel de rețea (FakeApiService). Baza de date in-memory este un caz particular de fake, când logica este cât mai aproape de cea reală.
Da, dar cu prudență. Fake pentru depozit (date), Mock pentru AnalyticsTracker (verificare evenimente). Separare pe straturi: fake pentru stratul de date, mock pentru stratul de analitică/logare. Nu faceți un obiect simultan fake și mock — aceasta încalcă principiul responsabilității unice și complică înțelegerea testului.
Testați fake cu aceleași teste ca și implementarea de producție. Dacă aveți UserRepositoryTest care verifică save, get, delete — rulați-l de două ori: cu FakeUserRepository și cu RealUserRepository. Aceasta garantează că fake reproduce comportamentul clasei de producție. Dacă fake începe să se comporte diferit — testul va eșua pe ambele implementări.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și