Spy — mi ez, Mockito.spy() és hívások ellenőrzése

Szerző: IT Sectr Megjelenés: 2026-04-10 Olvasási idő: 9 perc

Spy (kém) — egy tesztobjektum, amely becsomagol egy valós példányt és rögzíti az információkat minden hívásról: mely metódusok kerültek meghívásra, milyen argumentumokkal, hányszor. Ellentétben a mock-kal, a spy a becsomagolt objektum valós implementációját használja — a hívások a valódi kódon mennek keresztül, a spy pedig csak rögzíti a tényeket. A teszt végrehajtása után a fejlesztő ellenőrzi a kém feljegyzéseit: „Háromszor hívták meg a sendAnalytics metódust?”. Bővebben — az Android tesztelési útmutatóban.

Főbb pontok

  • Spy — objektum, amely rögzíti a valós implementáció metódushívásait anélkül, hogy lecserélné azt
  • Ellenőrzés — teszt után a spy lehetővé teszi annak ellenőrzését, hogy hányszor és milyen argumentumokkal hívták meg a metódust
  • Mockito.spy() — kémet hoz létre Androidon valós Java/Kotlin objektumokhoz
  • Részleges mocking — a spy kombinálható stub-bal: egyes metódusokat elfogni, másokat átengedni
  • iOS — OCMock (Objective-C) és manuális kémek protokollokon keresztül Swiftben

Mi az Spy és miben különbözik a Mock-tól?

Spy — egy burkolat egy valós objektum körül, amely elfogja az összes metódushívást és rögzíti azokat. Az objektum valós logikája végrehajtódik: ha a metódus adatokat ment, értéket számol ki vagy kérést intéz — minden a szokásos módon történik. Ezenkívül a spy metaadatokat rögzít: a metódus nevét, argumentumokat, hívások számát, végrehajtási időt. A kifejezés Meszaros (2007) osztályozásának része, és Martin Fowler „Mocks Aren't Stubs” című cikkében részletesen le van írva.

Fő különbség a Mock-tól — a mock teljesen helyettesíti az objektumot egy teszt-stub-bal, minden metódus alapértelmezés szerint nem csinál semmit. A spy egy meglévő objektumot csomagol be: minden metódus alapértelmezés szerint a szokásos módon működik, de közben rögzítésre kerül. Ez a különbség alapvető: a mock elkülöníti a tesztelt kódot a valóságtól, a spy megőrzi a valóságot és lehetővé teszi annak megfigyelését. A választás attól függ, hogy mit tesztelünk.

Mikor a Spy a helyes választás

Spy — a helyes választás — ha a tesztelt kód módosítja egy valós objektum állapotát, és a tesztnek egyaránt ellenőriznie kell az eredményt (állapotot) és meg kell győződnie arról, hogy a hívások a helyes sorrendben történtek. A mock nem megfelelő, mert nem hajtja végre a valós implementációt. A stub nem megfelelő, mert nem rögzíti a hívásokat. A spy az egyetlen tesztduplum, amely egyszerre őrzi meg a valós logikát és nyújt információt a hívásokról.

Spy vs Mock: mikor használjunk kémet

Mock — teljes elkülönítés. Ha a teszt nem függhet a valós objektum implementációjától (például adatbázis vagy hálózati kliens), használjon mock-ot. A mock garantálja, hogy egyetlen hívás sem éri el a valós komponenst. Ez biztonságos és kiszámítható. Hátrány: a mock nem hajt végre valós logikát, így ha a tesztelt kód a visszatérési értékre támaszkodik — azt explicit módon kell konfigurálni when/stub segítségével.

Spy — valós logika + megfigyelés. Ha a tesztelt kód olyan objektummal lép kölcsönhatásba, amelynek logikája fontos a teszt szempontjából, nem csak az adatok — használjon spy-t. Például AnalyticsTracker, amely eseményeket gyűjt és időszakonként elküldi azokat. A teszt ellenőrzi, hogy az események hozzáadásra kerültek-e a pufferhez, és hogy a küldés után a puffer kiürült-e. A mock nem tudja ezt ellenőrizni, mert nem hajtja végre a tracker valós logikáját.

ForgatókönyvSpyMock
Valós logika szükségesIgenNem (stub)
Hívások ellenőrzéseIgen (szám, argumentumok)Igen (szám, argumentumok)
Részleges stubbólásIgen (egyes metódusok — spy, mások — stub)Nem (minden metódus — stub)
Mellékhatások kockázataMagas (valós kód)Nulla
SebességAlacsonyabb (valós logika)Magasabb (stub-ok)
OlvashatóságAlacsonyabb (nehezebb megérteni, mi valós)Magasabb (minden explicit)

Antiminta: spy mindenhez

Spy mindenhez — a spy használata mock helyett minden tesztben hiba. A spy valós kódot hajt végre, amelynek mellékhatásai lehetnek: fájlba írás, HTTP küldés, globális állapot megváltoztatása. Ha a tesztelt modul meghív egy spy-objektum metódusát, amely HTTP-kérést intéz, a teszt integrációs tesztté válik, nem pedig egységtesztté. Szabály: ha a spy I/O műveletekkel rendelkező objektumot csomagol be — ez már nem egységteszt. Használjon mock-ot az I/O elkülönítésére, spy-t — csak memóriabeli objektumokhoz külső hatások nélkül.

Mockito.spy() és spy a MockK-ban Androidon

Mockito.spy() — a klasszikus módja a kém létrehozásának Java/Kotlin projektekben. A spy() átvesz egy valós objektumot és visszaad egy burkolatot. Minden hívás alapértelmezés szerint a valós objektumra delegálódik, az eredmények pedig rögzítésre kerülnek. A teszt végrehajtása után a verify() segítségével ellenőrizhető a hívások száma és az argumentumok. Azoknál a metódusoknál, amelyeknek tesztadatokat kell visszaadniuk, a doReturn/when használatos — ezt nevezzük „részleges stubbólásnak” (partial mocking).

kotlin
class AnalyticsReporterTest {

    private val realTracker = AnalyticsTracker()
    private val spyTracker = Mockito.spy(realTracker)

    fun test_event_tracked() {
        val event = AnalyticsEvent("login")
        spyTracker.track(event)

        Mockito.verify(spyTracker).track(event)
        assertEquals(1, spyTracker.getBufferedCount())
    }

    fun test_track_with_exception() {
        Mockito.doThrow(RuntimeException("network"))
            .when(spyTracker).flush()

        spyTracker.track(AnalyticsEvent("login"))
        assertTrue(spyTracker.hasPendingEvents())
    }
}

MockK.spyk() — alternatíva Kotlin projektekhez, jobb támogatással a korutinokhoz és sealed osztályokhoz. A MockK.spyk() kémet hoz létre, a Mockito.spy() analógja. Támogatja a coVerify-t a suspend függvényekhez és az every-t a részleges stubbóláshoz. Ellentétben a Mockito-val, a MockK nem támogatja a spy-t final osztályokhoz (Kotlinban minden osztály alapértelmezés szerint final) — az osztályt meg kell nyitni (open) vagy interfészt kell használni.

kotlin
class LoginUseCaseTest {

    private val realRepo = UserRepository()
    private val spyRepo = spyk(realRepo)

    private val useCase = LoginUseCase(spyRepo)

    fun test_login_calls_save() = runTest {
        every { spyRepo.getUser(any()) } returns User("test")

        val result = useCase.login("test", "pass")

        coVerify { spyRepo.saveLoginTime(any()) }
        assertTrue(result.isSuccess)
    }
}

Részleges stubbólás spy segítségével

Részleges stubbólás — egy erőteljes, de veszélyes technika. Létrehozhat egy objektum spy-ját, és csak néhány metódust írhat felül (stub), a többit valósként hagyva. Példa: egy spy-adattár, amelynek getUser() tesztadatokat ad vissza, a saveUser() pedig ténylegesen elment egy memóriabeli listába. Ez lehetővé teszi a stub-ok (ellenőrzött adatok) és a spy (valós logika) előnyeinek kombinálását. Hátrány: a teszt olvasásának bonyolultsága — nem nyilvánvaló, mely metódusok valósak és melyek stub-ok.

Spy megvalósítása iOS-en OCMock-kal és protokollokkal

OCMock Objective-C-hez — könyvtár, amely támogatja spy-objektumok létrehozását niceMock segítségével. Az OCMock elfogja a metódushívásokat az Objective-C runtime segítségével és rögzíti azokat. A teszt végrehajtása után a verify meghívásra kerül. Az OCMock támogatja a spy-t bármely objektumhoz (Objective-C-ben minden metódus dinamikus), ami előnyt jelent Swift-hez képest, ahol a spy csak protokollokon keresztül lehetséges.

objective-c
// Spy létrehozása a valós objektumhoz
AnalyticsTracker *realTracker = [[AnalyticsTracker alloc] init];
AnalyticsTracker *spy = [OCMockObject partialMockForObject:realTracker];

// Teszt végrehajtása
[spy trackEvent:@"login"];

// Ellenőrzés
[[spy verify] trackEvent:@"login"];
XCTAssertEqual([realTracker eventCount], 1);

Swift protocol-based spy — Swift-ben nincs Objective-C runtime-reflexió, ezért a spy manuálisan jön létre. A tesztstruktúra implementálja a protokollt és belül meghívja a valós objektumot, miközben rögzíti a hívásokat. Ez több kód, de teljesen kontrollálható és típusbiztos. A manuális kémek nem igényelnek külső könyvtárakat és nem használnak runtime-ot — minden a fordítási fázisban ellenőrzésre kerül.

swift
protocol AnalyticsProtocol {
    func trackEvent(name: String)
}

final class SpyAnalytics: AnalyticsProtocol {
    private let real: AnalyticsProtocol
    private var events: [String] = []

    init(real: AnalyticsProtocol) {
        self.real = real
    }

    func trackEvent(name: String) {
        events.append(name)
        real.trackEvent(name: name)
    }

    func verifyTracked(name: String) -> Bool {
        return events.contains(name)
    }
}

Mikor használjunk OCMock-ot vs manuális spy-t — Objective-C kódhoz használjon OCMock-ot (kevesebb boilerplate). Swift-hez — a manuális kémek protokollokon keresztül előnyösebbek. A manuális spy teljes ellenőrzést biztosít a hívások rögzítése felett, nem igényel reflexiót, és értéktípusokkal (struct) működik. Az egyetlen hátrány: a spy-osztály kódját szinkronban kell tartani a protokollal új metódusok hozzáadásakor.

A Spy alkalmazásának tipikus forgatókönyvei

Analitika ellenőrzése — a spy használatának leggyakoribb forgatókönyve. A termelési kódban az analitikai hívások szétszórva vannak az egész alkalmazásban: login, logout, purchase, error. A teszt létrehoz egy spy-burkolatot az AnalyticsTracker számára, végrehajtja a forgatókönyvet (bejelentkezés, termék megtekintése, kosárba helyezés, vásárlás) és ellenőrzi, hogy az összes szükséges esemény a helyes sorrendben került-e elküldésre. A mock nem megfelelő, mert az AnalyticsTracker pufferelési és küldési logikát tartalmaz.

Időzítők és ütemezők — a Handler-t (Android) vagy Timer-t (iOS) használó kód tesztelése a valós idő miatt nehéz. A Scheduler spy-burkolata rögzíti, hogy mely feladatok lettek ütemezve és milyen késleltetéssel. A teszt létrehozza a valós Handler spy-ját, végrehajtja a műveletet és ellenőrzi, hogy a Handler.postDelayed(runnable, delay) a megfelelő késleltetéssel lett-e meghívva. A valós feladat ezalatt nem hajtódik végre — a spy elfogja és rögzíti a hívást.

Naplózás és hibakeresési információ — termelésben a naplók lehetnek kikapcsolva vagy fájlba írva. A Logger spy-burkolata az összes üzenetet egy memóriabeli listába rögzíti, amelyet a teszt a végrehajtás után ellenőriz. Ez lehetővé teszi annak ellenőrzését, hogy hiba esetén a megfelelő üzenet kerül-e kiírásra anélkül, hogy a konzol eltömődne. A manuális kémek a Logger számára különösen hasznosak iOS-en, ahol az OSLog-nak nincs teszt API-ja.

Hívások sorrendjének ellenőrzése — egyes forgatókönyvek szigorú műveleti sorrendet igényelnek: kapcsolat megnyitása, adatok küldése, kapcsolat bezárása. A Mockito lehetővé teszi a sorrend ellenőrzését az InOrder.verify() segítségével. A spy ugyanezt teszi, de megőrzi a valós végrehajtást. Ha nem csak a sorrend, hanem minden lépés eredménye is fontos (a kapcsolat ténylegesen megnyílt) — használjon spy-t, ne mock-ot.

Gyakran Ismételt Kérdések

Spy vs Mock: mi a fő különbség?

Spy becsomagol egy valós objektumot és végrehajtja a logikáját, ezenkívül rögzíti a hívásokat. A mock teljesen helyettesíti az objektumot egy stub-bal — semmilyen valós logika nem hajtódik végre. A spy megőrzi a viselkedést, a mock nem. Válassza a spy-t, ha az objektum valós működése fontos; válassza a mock-ot, ha a tesztet el kell különíteni egy külső függőségtől.

Mikor rossz választás a Spy?

Amikor a spy-burkolat valós I/O műveletekhez vezet. Ha a spy olyan objektumot csomagol be, amely fájlba ír, HTTP-t küld vagy lemezről olvas — a teszt megszűnik egységteszt lenni. Második eset: a teszt csak a visszatérési értéket ellenőrzi a hívások iránti érdeklődés nélkül — itt a stub elegendő, a spy felesleges. Harmadik: a kód a spy belső állapotára támaszkodik — ez egy törékeny teszt.

Támogatja a MockK a spy-t?

Igen, a spyk() segítségével — a Mockito.spy() analógja. A MockK.spyk() kémet hoz létre egy valós objektum körül, támogatja az every-t a részleges stubbóláshoz és a coVerify/coroutinesVerify-t a suspend függvényekhez. Korlátozás: nem működik final osztályokkal (open vagy interfész szükséges). Java osztályokhoz a MockK szintén támogatja a spyk()-t, de megköveteli a @MockKJvmInline annotációt.

Lehet-e Spy-t csinálni egy Mock-ból?

Technikailag — nem. A Mock egy stub, amely nem tartalmaz valós implementációt. A spy definíció szerint egy valós objektumot csomagol be. A Mockito-ban nem lehet a mock-ot spy-vá alakítani. De fordítva lehetséges: hozzon létre egy spy-t, és írja felül a metódusok egy részét a doReturn/when (partial mocking) segítségével. Ez a spy-objektum kiválasztott metódusaihoz a mock-hoz hasonló viselkedést biztosít.

Spy Swift-ben — kötelező protokollon keresztül?

Kötelező. A Swift-ben nincs dinamikus proxyzás, mint Java/Kotlin esetében. A spy létrehozásához egy protokollra van szükség, amelyet mind a termelési osztály, mind a spy osztály implementál. A Swift-protocol-based spy egy manuális implementáció, amely átvesz egy valós objektumot, delegálja neki a hívásokat és rögzíti a metaadatokat. Alternatíva: a Cuckoo könyvtár, amely SourceKit segítségével generál spy osztályokat.

Összefoglalás

  • Spy — burkolat egy valós objektum körül, amely rögzíti az összes metódushívást a logika lecserélése nélkül
  • Különbség a Mock-tól — a spy valós kódot hajt végre, a mock stub-bal helyettesíti
  • Mockito.spy() — Java/Kotlinhez, valós objektumot csomagol be verify és partial mock lehetőséggel
  • MockK.spyk() — Kotlin analóg korutin, sealed osztály és coVerify támogatással
  • iOS — OCMock Objective-C-hez, manuális protocol-based spy Swift-hez
  • Fő forgatókönyv — analitika, időzítők, naplók és hívási sorrend ellenőrzése
  • Vigyázat — a spy I/O műveletekkel egységtesztből integrációs tesztet csinál

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.

Projekt megbeszélése

Olvassa el is