Fake — какво е, предназначение и как да се използва в тестването

Автор: IT Sectr Публикувано: 2026-04-10 Време за четене: 9 мин

Fake (фейк) — работеща опростена имплементация на зависимост, която се държи като истински компонент, но използва in-memory хранилище или други леки механизми вместо продукционна инфраструктура. За разлика от stub, fake съдържа реална бизнес логика — сортиране, филтриране, агрегиране — просто без външни ефекти. In-memory база данни вместо Room или HashMap вместо SharedPreferences — класически примери. Повече в класификацията на test doubles от Мартин Фаулър.

Основни неща

  • Fake — опростена работеща имплементация с реална логика, но без външни зависимости
  • In-memory съхранение — fake-репозиториумът съхранява данни в HashMap, а не в база данни
  • Разлика от Stub — stub връща фиксирани данни, fake съдържа изпълнима логика
  • Android — InMemoryUserRepository като Fake за тестване на ViewModel и UseCase
  • iOS — FakeNetworkSession с URLProtocol и тестови данни вместо реален сървър

Какво е Fake и защо е необходим в тестването?

Fake — пълна, но лека имплементация на интерфейс, подходяща за тестване. Терминът е въведен от Gerard Meszaros (2007) в книгата «xUnit Test Patterns». За разлика от stub, който връща строго предварително определени отговори, fake съдържа изпълним код: може да сортира списък, да филтрира по условие, да брои записи. Единствената разлика от продукционната имплементация — fake работи с in-memory данни и не извършва реални I/O операции.

Основно предимство — скорост. Тестовете с fake се изпълняват за милисекунди, защото няма достъп до диск, мрежа или база данни. In-memory HashMap работи 100-1000 пъти по-бързо от Room или CoreData. В същото време fake проверява реалната бизнес логика: сортиране, филтриране, агрегиране — всичко, което stub не може да провери, защото stub връща само това, което му е казано. Fake дава увереност, че кодът обработва правилно данните, а не просто получава предварително определен отговор.

Кога Fake е за предпочитане пред Stub

Fake е за предпочитане пред Stub — ако тестваният компонент извършва няколко операции върху данни (извлече, филтрира, сортира, запази), stub ще изисква конфигуриране на всяко извикване поотделно. Fake съдържа логиката вътре в себе си — тестът просто извиква методи и проверява резултата. В IT Sectr използваме fake за всички репозиториуми в unit тестовете: fake-репозиториум с HashMap покрива 90% от сценариите без конфигуриране на Mockito или MockK.

Fake vs Stub vs Mock: кога какво да изберем

Критерий за избор — определете какво проверява тестът: състояние или взаимодействие. Ако тестът проверява състояние (резултат от работа) и използва логика — нужен е fake. Ако тестът се нуждае само от входни данни без логика — достатъчен е stub. Ако тестът проверява факта на извикване на метод — нужен е mock. Смесването на типове test doubles в един тест затруднява разбирането и увеличава крехкостта.

КритерийFakeStubMock
Наличие на логикаДа (опростена)НеНе
СкоростВисокаМаксималнаВисока
Проверка на поведениеНепрякаНеДа (verify)
ПоддръжкаЕдин клас на интерфейсКонфигурация за всеки тестКонфигурация за всеки тест
РеализъмВисок (кодът работи)Нисък (фиксирани данни)Среден
Риск от фалшиви алармиНисъкСреденВисок (крехки тестове)

Антимодел: Fake, който не е fake — често срещана грешка, когато разработчикът нарича fake обект, който всъщност е stub или mock. Ако вашият InMemoryUserRepository не съдържа логика (филтриране, сортиране) — това не е fake, а stub с in-memory съхранение. Fake се различава от stub именно по наличието на изпълнима логика. Ако fake-репозиториумът просто връща това, което е поставено в него, и не обработва данни — използвайте mock или stub.

Практическо правило за избор на test double

Практическа препоръка — започнете с fake за всеки репозиториум или услуга. Ако fake е по-сложен от 50 реда — разделете на няколко класа. Ако fake изобщо не е необходим (тестът проверява само един сценарий с фиксирани данни) — използвайте stub. Ако тестът проверява дали метод е бил извикан с определени параметри — използвайте mock. Не оптимизирайте избора предварително: напишете fake и ако се окаже излишен, го заменете с stub в конкретния тест.

Създаване на Fake-обекти на Android за Room и Retrofit

Fake-репозиториум за Room — типичен пример за fake на Android. Продукционната имплементация на UserRepository използва Room DAO с SQLite заявки. Версията fake съхранява данни в MutableList или HashMap и имплементира същите методи: getUser(id), saveUser(user), deleteUser(id). Fake съдържа логика за търсене, филтриране и сортиране — същата като в продукционния репозиториум, но без SQL. Това позволява тестване на ViewModel и UseCase без конфигуриране на Room база данни.

kotlin
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 — вместо MockWebServer (който е stub, не fake) може да се създаде имплементация на ApiService, която връща данни от in-memory колекция. Разлика: MockWebServer прихваща HTTP и връща JSON, докато fake-ApiService работи на ниво Kotlin интерфейс без сериализация. Fake е по-бърз (няма парсване на JSON) и по-лесен за отстраняване на грешки (работи в същия процес, типизиран). Подходящ за тестове, където HTTP семантиката (кодове за състояние, хедъри) не е важна.

FakeSharedPreferences за бързи тестове

— друг често срещан сценарий. Продукционните SharedPreferences записват на диск чрез commit/apply. Версията fake съхранява двойки ключ-стойност в HashMap и незабавно връща данни. Поддържа същите методи: getString, putString, getInt, putInt, clear. За Jetpack DataStore аналогът е FakeDataStore с in-memory хранилище. Такива fake ускоряват тестовете десетки пъти, тъй като няма операции за запис на диск.

Fake имплементации на iOS с in-memory хранилища

Fake в Swift — изгражда се чрез протоколи. Продукционният клас имплементира протокола с реална логика (CoreData, URLSession). Структурата fake имплементира същия протокол с in-memory хранилище и опростена логика. Swift е език със стойностна семантика, така че структурите fake са неизменяеми и безопасни в многонишкови тестове. Това дава предимство пред Android аналозите: достъпът до in-memory данни не трябва да се синхронизира.

swift
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 — в iOS проекти може да се създаде in-memory NSPersistentContainer чрез задаване на description.type = NSInMemoryStoreType. Това е пълноценен CoreData стек, работещ в паметта. Такъв fake позволява тестване на NSFetchRequest, предикати и сортирания без създаване на SQLite файл. Скорост: тестовете на in-memory CoreData се изпълняват 5-10 пъти по-бързо от дисков аналог. Недостатък: NSManagedObjectModel трябва да се конфигурира всеки път.

FakeURLProtocol — подклас на URLProtocol за прихващане на мрежови заявки на iOS. Регистрира се чрез URLProtocol.registerClass(fakeProtocol). Вътре съдържа in-memory речник URL -> Data и връща данни без реална заявка. Разлика от stub: FakeURLProtocol може да проверява тялото на заявката, хедърите и да връща различни отговори в зависимост от входните данни. Това е fake, защото съдържа логика за маршрутизиране на заявки.

Модели за използване на Fake в мобилни проекти

Fake като Test Fixture — изнесете fake класовете в общ тестов модул (androidTest/sharedTest или TestSupport). Всички тестове в проекта използват един и същ InMemoryUserRepository. Това елиминира дублирането на конфигурацията на mock обекти във всеки тест и гарантира еднообразно поведение. Промяната на fake логиката актуализира всички тестове едновременно. В IT Sectr съхраняваме fake класовете в sharedTest/java/com/itSectr/fake/ и ги свързваме чрез implementation project(:sharedTest).

Fake с предварително зададени данни — често тестовете се нуждаят от репозиториум, който вече съдържа някои записи. Решение: фабричен метод fakeWithData(vararg items) или вграден метод addDefaultData(). Фабриката създава fake, попълва го с типични данни и връща обект, готов за използване. Това намалява boilerplate в тестовете: вместо да конфигурирате mock извиквания, тестът просто извиква FakeUserRepository.withUsers(alice, bob).

Fake с броене на извиквания — понякога трябва да се провери не само състоянието, но и броят на обръщенията. Fake може да съдържа броячи: saveCallCount, getUserCallCount. Тестът проверява брояча след изпълнение. Това е компромис между чист fake (проверка на състояние) и mock (проверка на взаимодействие). Броячите не проверяват аргументи и ред на извикванията — само броя. За проверка на аргументи използвайте mock.

Fake с Callback — за тестване на асинхронни сценарии, fake може да приема callback при всяко извикване: beforeGetUser, afterSaveUser. Това позволява симулиране на закъснения, грешки или проверка на междинни състояния. Такъв подход е полезен за тестване на състояния на зареждане на UI: fake прави пауза от 100 ms, а тестът проверява дали екранът показва лоудър. В продукционна среда callback не съществува — това е чисто тестова функционалност.

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

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

Fake съдържа работеща логика — филтрира, сортира, брои. Stub връща само предварително зададени отговори без логика. Ако обектът има разклонения (if/else, when) — това е fake. Ако съдържа само return values — това е stub. Fake е по-скъп за поддръжка, но дава по-реалистични тестове.

Кога fake може да бъде вреден?

Когато fake логиката не съвпада с продукционната логика. Например FakeUserRepository използва case-sensitive търсене, а продукцията — case-insensitive. Тестът минава, но в действителност има грешка. Решение: тествайте fake логиката отделно или използвайте fakes само за интерфейси с проста логика (CRUD операции). За сложна логика пишете интеграционни тестове с реална база данни.

Fake е същото като in-memory database?

In-memory database — един от вариантите на fake. Room.inMemoryDatabaseBuilder() създава in-memory SQLite, който се държи като продукционна база данни. Това е пълноценен fake. Но fake може да бъде и на ниво репозиториум (без SQL) и на мрежово ниво (FakeApiService). In-memory базата данни е частен случай на fake, когато логиката е максимално близка до реалната.

Могат ли Fake и Mock да се комбинират в един тест?

Да, но внимателно. Fake за репозиториум (данни), Mock за AnalyticsTracker (верификация на събития). Разделение по слоеве: fake за слоя данни, mock за слоя аналитика/логиране. Не правете един обект едновременно fake и mock — това нарушава принципа на единната отговорност и обърква теста.

Как да тестваме самия Fake?

Тествайте fake със същите тестове като продукционната имплементация. Ако имате UserRepositoryTest, който проверява save, get, delete — пуснете го два пъти: с FakeUserRepository и с RealUserRepository. Това гарантира, че fake повтаря поведението на продукционния клас. Ако fake започне да се държи различно — тестът ще се провали и при двете имплементации.

Резюме

  • Fake — работеща опростена имплементация на зависимост с реална бизнес логика и in-memory съхранение
  • Разлика от Stub — fake съдържа логика (филтриране, сортиране), stub само връща данни
  • Скорост — fake работи 100-1000 пъти по-бързо от продукционната имплементация без I/O операции
  • Android — InMemoryUserRepository, FakeDataStore, in-memory Room чрез Room.inMemoryDatabaseBuilder
  • iOS — fake на базата на протоколи, in-memory CoreData, FakeURLProtocol за HTTP прихващане
  • Най-добра практика — изнесете fake в общ тестов модул и използвайте във всички тестове на проекта
  • Тествайте fake — пуснете същите тестове на fake и продукционната имплементация за проверка на консистентност

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

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

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

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