Stub: какво е, видове и приложение в тестването

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

Stub (стаб, заместител) — тестов обект, който връща предварително определени отговори на извиквания на методи вместо реална имплементация. В мобилното разработване стабовете изолират тествания модул от мрежови заявки, база данни и файлова система, позволявайки проверка на логиката без настройка на средата. За разлика от mock, stub не проверява поведение — той само предоставя данни. Повече в статията на Martin Fowler за test doubles.

Основни точки

  • Stub — заместител, връщащ зададени стойности на извиквания на методи без логика
  • Изолация — стабовете изключват реални зависимости: API, база данни, файлове, сензори
  • Разлика от Mock — stub не проверява извиквания, той само замества отговора
  • Android — MockWebServer (OkHttp) като stub за HTTP, MockK.constantAnswer за Kotlin
  • iOS — OCMock и Swift протоколи с тестови имплементации като стабове

Какво е Stub и как се различава от други test doubles?

Stub — е заместващ обект, който заменя реалната зависимост в теста и връща предварително определени стойности на конкретни извиквания. Терминът е въведен в класификацията на Gerard Meszaros (2007) в книгата „xUnit Test Patterns". Stub принадлежи към категорията test doubles — обекти, заместващи реални компоненти по време на тестване. Основната цел на стаба е да осигури на тествания блок предвидими данни, премахвайки несигурността на външните системи.

Принцип на работа — тестът конфигурира стаба преди изпълнение: „когато методът getUsers() бъде извикан, върни този списък с потребители". Stub не съдържа бизнес логика, не проверява последователността на извикванията и не записва история на достъпа. Той просто стои на мястото на реалния компонент и връща това, което му е казано. В контекста на Android тестването това означава, че OkHttp клиентът не прави реална заявка към сървъра, а получава отговор от MockWebServer, конфигуриран като stub.

  • Stub — връща данни, не проверява извиквания
  • Mock — връща данни и проверява поведение (verify)
  • Fake — работеща опростена имплементация с реална логика
  • Spy — обвива реалния обект, записвайки извиквания
  • Dummy — предава се, но не се използва (null, празен обект)

Кога да използвате — стабовете са оптимални за тестване на UI слоя (ViewModel, Presenter) и бизнес логиката (UseCase, Interactor), където трябва да се провери реакцията на конкретни данни: празен списък, сървърът върна грешка 500, изтекъл токен. Всеки случай, в който тестът изисква конкретно входно състояние, е задача за stub. За всеки тестов сценарий се създава собствена конфигурация на стаба, което прави тестовете четливи и предвидими.

Класификация на test doubles според Meszaros

Gerard Meszaros (2007) в книгата „xUnit Test Patterns" идентифицира пет типа test doubles: dummy, stub, spy, mock, fake. Всеки тип решава своя задача. Dummy — предава се, но не се използва. Stub — връща данни. Spy — записва извиквания. Mock — проверява поведение. Fake — съдържа опростена логика. Разбирането на тази класификация помага на разработчика да избере правилния инструмент за всеки тестов сценарий.

Къде се използват стабовете в тестването на мобилни приложения

Стабове за мрежови заявки

Мрежови заявки — най-честият сценарий за използване на стабове. Приложението прави HTTP извиквания към API и в теста трябва да се провери реакцията на различни отговори: успешен JSON, грешка 401 (неоторизиран), таймаут, празен масив. MockWebServer (OkHttp) на Android и URLProtocol (iOS) действат като стабове, връщайки предварително определени HTTP отговори без реална връзка със сървъра. Това ускорява тестовете от секунди до милисекунди.

База данни — Room (Android) и CoreData (iOS) имат in-memory варианти, но тяхната настройка все пак отнема време. Stub вместо хранилище връща предварително подготвени списъци с Entity, без да докосва базата данни. Това е особено ефективно за тестване на ViewModel, където трябва да се провери сортиране, филтриране или трансформация на данни. Тестът се изпълнява за милисекунди, независимо от обема на данните.

Системни услуги — LocationManager, SensorManager, SharedPreferences изискват реално устройство или емулатор. Stub за LocationProvider връща зададени координати, за SensorManager — фиксирани стойности на акселерометъра. На iOS аналогът е CLLocationManager с тестова имплементация на делегата. Без стабове такива тестове изискват физическо устройство с определени условия.

Файлова система и кеш — зареждане на изображения, кеширане на отговори, работа с конфигурационни файлове — всички тези операции зависят от състоянието на диска. Stub за FileManager или ImageCache връща успех/грешка без четене на реални файлове. Това елиминира фалшивите грешки на тестове поради несъответствие на пътища или права на различни машини на разработчиците.

Stub vs Mock vs Fake: ключови разлики

Разделение на отговорностите — три типа test doubles решават различни задачи. Stub: „дай ми данни". Mock: „провери дали са ме извикали". Fake: „работя като истински, само по-просто". Разликата е критична за четливостта на тестовете: ако тестът използва mock там, където е необходим stub, той е претоварен с verify извиквания, несвързани с тествания сценарий.

ХарактеристикаStubMockFake
ПредназначениеПредоставяне на данниПроверка на взаимодействиеОпростена имплементация
ЛогикаНеНеДа (но опростена)
ВерификацияНеДа (verify)Косвена (чрез състояние)
ГъвкавостНиска — твърди отговориСреднаВисока — логиката се адаптира
СкоростМаксималнаВисокаСредна
ПримерMockWebServer връща JSONMockito.verify(repository).save()InMemoryRepository с HashMap

Практическо правило — ако тестът проверява какви данни е получил тестваният компонент — използвайте stub. Ако тестът проверява дали компонентът е извикал метода на зависимостта с правилни аргументи — използвайте mock. Ако просто искате да замените базата данни с хеш таблица — това е fake. Смесването на типове в един тест го прави крехък: при промяна на имплементацията ще трябва да пренапишете както stub, така и verify логиката.

Анти-модел: Stub с verify

Stub с verify — често срещана грешка, когато разработчикът конфигурира stub и след това добавя verify(stub).method(). Stub по дефиниция не трябва да бъде верифициран — за верификация има mock. Ако трябва да проверите, че методът е бил извикан с конкретни аргументи, използвайте Mockito.mock() вместо Mockito.stub(). Това разделение поддържа намерението на теста ясно за други разработчици.

Имплементация на стабове в Android с MockWebServer и MockK

MockWebServer — библиотека OkHttp за създаване на HTTP стабове в Android и JVM. Тя стартира локален HTTP сървър на определен порт, който прихваща заявките на OkHttp клиента и връща предварително определени отговори. Настройката отнема три реда: създаване на сървър, поставяне на отговор в опашка (enqueue), стартиране. Тестът може последователно да постави няколко отговора в опашка за сценарии с пагинация или повторни опити.

kotlin
class UserRepositoryTest {

    private val server = MockWebServer()

    fun setup() {
        server.start(8080)
        val client = OkHttpClient.Builder()
            .readTimeout(1, TimeUnit.SECONDS)
            .build()
    }

    fun test_user_list_success() {
        val json = "[{\"id\":1,\"name\":\"Alice\"}]"
        server.enqueue(MockResponse()
            .setBody(json)
            .setResponseCode(200)
        )
        val result = repository.getUsers()
        assertEquals(1, result.size)
    }

    fun teardown() {
        server.shutdown()
    }
}

MockK — алтернатива на Mockito за Kotlin с first-class поддръжка за корутини, разширяващи функции и sealed класове. Стабовете в MockK се създават чрез coEvery (за suspend функции) и every (за обикновени функции). За разлика от MockWebServer, MockK замества отделни методи на зависимости, а не целия HTTP слой. Това е удобно за unit тестове на UseCase или Interactor, където зависимостите са абстракции на хранилища.

kotlin
interface UserRepository {
    suspend fun getUsers(): List<User>
}

class GetUsersUseCaseTest {

    private val repo = mockk<UserRepository>()

    private val useCase = GetUsersUseCase(repo)

    fun test_empty_list() = runTest {
        coEvery { repo.getUsers() } returns emptyList()

        val result = useCase.invoke()

        assertTrue(result.isEmpty())
        coVerify(exactly = 1) { repo.getUsers() }
    }
}

Best practice — за интеграционни тестове използвайте MockWebServer (прихваща реален HTTP), за unit тестове — MockK (замества интерфейси). Не замествайте това, което не тествате: ако тестът проверява Repository, не замествайте OkHttp клиента вътре в него — използвайте реален MockWebServer на HTTP ниво. Това правило поддържа тестовете релевантни и намалява крехкостта при рефакториране.

Имплементация на стабове в iOS с OCMock и протоколи

Swift протоколи като стабове — в iOS-native подхода stub се имплементира чрез поставяне на тестова структура, съответстваща на протокола на зависимостта. Вместо реален NetworkService, тестът получава StubNetworkService, който връща фиксирани данни. Swift е език със статично типизиране, така че stub трябва да съответства на същия протокол като реалната услуга. Компилаторът гарантира, че stub имплементира всички необходими методи.

swift
protocol NetworkServiceProtocol {
    func fetchUsers() async throws -> [User]
}

struct StubNetworkService: NetworkServiceProtocol {
    let result: Result<[User], Error>

    func fetchUsers() async throws -> [User] {
        try result.get()
    }
}

final class UsersViewModelTests: XCTestCase {
    func test_success_state() async {
        let stub = StubNetworkService(
            result: .success([User(name: "Alice")])
        )
        let vm = UsersViewModel(service: stub)
        await vm.load()
        XCTAssertEqual(vm.users.count, 1)
    }
}

OCMock за Objective-C — библиотека за създаване на стабове и мокове в наследени iOS проекти. OCMock поддържа stub методи с аргументи и връщани стойности. Съвременните проекти в Swift предпочитат протокол-базиран подход с ръчни стабове — това дава контрол над всеки метод и не изисква външни зависимости. OCMock остава опция за проекти, където протоколизирането на всички зависимости не е икономически оправдано.

URLProtocol за HTTP стабове — системен механизъм на iOS за прихващане на мрежови заявки чрез подклас на URLProtocol. Тестът регистрира персонализиран URLProtocol, който прихваща URLSession и връща stub отговори. Предимство пред ръчните стабове: не е необходимо да променяте архитектурата на приложението — URLSession остава реален, но данните се заместват на протоколно ниво. Недостатък: по-трудно за дебъгване от изрична stub услуга.

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

Как се различава Stub от Mock?

Stub връща предварително определени данни и не проверява факта на извикване. Mock допълнително проверява (verify), че методът е бил извикан с правилни аргументи. Stub отговаря на въпроса „какво да върна", Mock — на въпроса „имало ли е извикване". Използвайте stub за проверка на състояние, mock — за проверка на взаимодействие.

Кога да използвам Fake вместо Stub?

Fake е необходим, когато тестът изисква работеща (макар и опростена) имплементация — например in-memory база данни вместо Room. Stub е подходящ за единични сценарии с предварително определени данни. Ако повтаряте същия stub в 10 теста — вероятно имате нужда от Fake. Fake намалява дублирането, защото логиката живее в един клас.

Могат ли статичните методи да бъдат стабвани?

На Android — MockK за Kotlin обекти (object) поддържа mockkObject(), включително статични методи на Java класове чрез mockkStatic(). На iOS — статичните методи на Swift не могат да бъдат стабвани директно; използвайте протоколи и DI, за да замените static извикването с инстанционен метод на протокол. Статичните стабове са технически дълг и трябва да се избягват в нов код.

Как да стабвам мрежови заявки на Android?

Използвайте MockWebServer (OkHttp) — работи като локален HTTP сървър, който поставя отговори в опашка (enqueue). За Retrofit е достатъчно да промените базовия URL на localhost:8080. За Ktor използвайте MockEngine — вградения механизъм за заместване на HttpStatement. И двата подхода работят без реален интернет и дават пълен контрол над кода на състоянието, тялото и заглавките на отговора.

Stub vs Spy — каква е разликата?

Spy обвива реалния обект и записва извиквания, докато Stub напълно замества обекта с фиксирани отговори. Spy позволява частично използване на реалната имплементация (останалите методи работят както преди), а stub — не. Ако трябва да проверите, че метод е бил извикан, но част от логиката трябва да се изпълни — използвайте spy, а не stub.

Резюме

  • Stub — заместващ обект, който връща предварително определени отговори на извиквания на методи по време на тестване
  • Изолация на зависимости — стабовете заместват мрежови заявки, бази данни, системни услуги и файлова система
  • Разлика от Mock — stub не проверява извиквания, той само връща данни без проверка на поведение
  • Android инструменти — MockWebServer за HTTP, MockK за Kotlin интерфейси с поддръжка на корутини
  • iOS инструменти — протокол-базирани стабове в Swift, URLProtocol за HTTP, OCMock за Objective-C
  • Не смесвайте роли — не добавяйте verify към stub, използвайте mock за проверка на извиквания
  • Stub + MockWebServer — стандартен подход за интеграционни тестове без реален сървър

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

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

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

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