Spy — ano ito, Mockito.spy() at pagpapatunay ng mga tawag

May-akda: IT Sectr Nai-publish: 2026-04-10 Oras ng pagbabasa: 9 min

Spy (espiya) — isang test object na bumabalot sa tunay na instance at nagtatala ng impormasyon tungkol sa bawat tawag: anong mga method ang tinawag, sa anong mga argumento, ilang beses. Hindi tulad ng mock, ang spy ay gumagamit ng tunay na implementasyon ng binalot na object — ang mga tawag ay dumadaan sa tunay na code, at ang spy ay nagtatala lamang ng mga katotohanan. Pagkatapos ng pagpapatakbo ng test, sinusuri ng developer ang mga tala ng espiya: „Ang method bang sendAnalytics ay tinawag nang tatlong beses?”. Higit pa — sa gabay sa pagsubok ng Android.

Mga pangunahing punto

  • Spy — object na nagtatala ng mga tawag sa method ng tunay na implementasyon nang hindi ito pinapalitan
  • Pagpapatunay — pagkatapos ng test, pinapayagan ng spy na suriin kung ilang beses at sa anong mga argumento tinawag ang method
  • Mockito.spy() — gumagawa ng espiya sa Android para sa tunay na Java/Kotlin object
  • Partial mocking — maaaring pagsamahin ang spy sa stub: ang ilang method ay harangin, ang iba ay hayaang dumaan
  • iOS — OCMock (Objective-C) at manu-manong espiya sa pamamagitan ng protocol sa Swift

Ano ang Spy at ano ang pagkakaiba nito sa Mock?

Spy — ay isang balot sa paligid ng isang tunay na object na humaharang sa lahat ng tawag sa method at nagtatala ng mga ito. Ang tunay na lohika ng object ay isinasagawa: kung ang method ay nag-save ng data, nagkalkula ng halaga, o gumagawa ng kahilingan — lahat ay nangyayari gaya ng dati. Dagdag pa, nagtatala ang spy ng metadata: pangalan ng method, argumento, bilang ng mga tawag, oras ng pagpapatakbo. Ang termino ay bahagi ng klasipikasyon ng Meszaros (2007) at detalyadong inilarawan sa artikulo ni Martin Fowler na „Mocks Aren't Stubs”.

Pangunahing pagkakaiba sa Mock — ganap na pinapalitan ng mock ang object ng isang test stub, lahat ng method ay walang ginagawa bilang default. Binabalot ng spy ang isang umiiral na object: lahat ng method ay gumagana gaya ng dati, ngunit sa parehong oras ay naitatala. Ang pagkakaibang ito ay pundamental: iniisa-isa ng mock ang nasubok na code mula sa katotohanan, pinapanatili ng spy ang katotohanan at pinapayagan ang pagmamasid dito. Ang pagpili sa pagitan nila ay depende sa kung ano ang sinusubok.

Kailan ang Spy ay tamang pagpili

Spy — tamang pagpili — kung ang nasubok na code ay nagbabago ng estado ng tunay na object, at ang test ay dapat parehong suriin ang resulta (estado) at tiyakin na ang mga tawag ay ginawa sa tamang pagkakasunod-sunod. Hindi angkop ang mock dahil hindi nito isinasagawa ang tunay na implementasyon. Hindi angkop ang stub dahil hindi nito itinatala ang mga tawag. Ang spy ay ang tanging test double na sabay na pinapanatili ang tunay na lohika at nagbibigay ng impormasyon tungkol sa mga tawag.

Spy vs Mock: kailan gagamit ng espiya

Mock — ganap na paghihiwalay. Kung ang test ay hindi dapat umasa sa implementasyon ng tunay na object (halimbawa, database o network client), gumamit ng mock. Ginagarantiya ng mock na walang tawag na makakarating sa tunay na component. Ito ay ligtas at mahuhulaan. Kakulangan: hindi isinasagawa ng mock ang tunay na lohika, kaya kung ang nasubok na code ay umaasa sa ibinalik na halaga — kailangan itong i-configure nang tahasan sa pamamagitan ng when/stub.

Spy — tunay na lohika + obserbasyon. Kung ang nasubok na code ay nakikipag-ugnayan sa isang object na ang lohika ay mahalaga para sa test, hindi lamang ang data — gumamit ng spy. Halimbawa, AnalyticsTracker na nangongolekta ng mga event at pana-panahong nagpapadala ng mga ito. Sinusuri ng test kung ang mga event ay naidagdag sa buffer at pagkatapos ng pagpapadala ay na-clear ang buffer. Hindi ito masusuri ng mock dahil hindi nito isinasagawa ang tunay na lohika ng tracker.

SenaryoSpyMock
Kailangan ang tunay na lohikaOoHindi (stub)
Pagpapatunay ng mga tawagOo (bilang, argumento)Oo (bilang, argumento)
Bahagyang stubbingOo (ilang method — spy, iba — stub)Hindi (lahat ng method — stub)
Panganib ng side effectMataas (tunay na code)Wala
BilisMas mababa (tunay na lohika)Mas mataas (stub)
PagkabasaMas mababa (mas mahirap maintindihan kung ano ang tunay)Mas mataas (lahat ay tahasan)

Anti-pattern: spy para sa lahat

Spy para sa lahat — ang paggamit ng spy sa halip na mock para sa lahat ng test ay isang pagkakamali. Isinasagawa ng spy ang tunay na code na maaaring magkaroon ng mga side effect: pagsulat sa file, pagpapadala ng HTTP, pagbabago ng pandaigdigang estado. Kung ang nasubok na module ay tumatawag sa method ng spy-object na gumagawa ng HTTP request, ang test ay nagiging integration test, hindi unit test. Panuntunan: kung binabalot ng spy ang isang object na may I/O operations — hindi na ito unit test. Gumamit ng mock para sa I/O isolation, spy — para lamang sa in-memory object na walang panlabas na epekto.

Mockito.spy() at spy sa MockK sa Android

Mockito.spy() — ang klasikong paraan ng paggawa ng espiya sa Java/Kotlin projects. Ang spy() ay tumatanggap ng tunay na object at nagbabalik ng balot. Lahat ng tawag ay bilang default ay inidelegate sa tunay na object, at ang mga resulta ay naitatala. Pagkatapos ng pagpapatakbo ng test, sa pamamagitan ng verify() ay masusuri ang bilang ng mga tawag at argumento. Para sa mga method na dapat magbalik ng test data, ginagamit ang doReturn/when — ito ay tinatawag na „bahagyang stubbing” (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() — alternatibo para sa Kotlin projects na may mas mahusay na suporta para sa coroutine at sealed classes. Ang MockK.spyk() ay gumagawa ng espiya, kahalintulad ng Mockito.spy(). Sinusuportahan ang coVerify para sa suspend functions at every para sa bahagyang stubbing. Hindi tulad ng Mockito, hindi sinusuportahan ng MockK ang spy para sa final classes (lahat ng class sa Kotlin ay final bilang default) — kailangang buksan ang class (open) o gumamit ng interface.

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)
    }
}

Bahagyang stubbing sa pamamagitan ng spy

Bahagyang stubbing — isang makapangyarihan ngunit mapanganib na pamamaraan. Maaari kang gumawa ng spy ng isang object at i-override (stub) lamang ang ilang method, na iiwan ang iba na tunay. Halimbawa: isang spy-repository na ang getUser() ay nagbabalik ng test data, at ang saveUser() ay talagang nagse-save sa in-memory list. Ito ay nagpapahintulot na pagsamahin ang mga bentahe ng stub (kontroladong data) at spy (tunay na lohika). Kakulangan: pagiging kumplikado ng pagbasa ng test — hindi halata kung aling method ang tunay at alin ang stub.

Implementasyon ng Spy sa iOS gamit ang OCMock at protocol

OCMock para sa Objective-C — library na sumusuporta sa paggawa ng spy-object sa pamamagitan ng niceMock. Ang OCMock ay humaharang ng mga tawag sa method gamit ang Objective-C runtime at nagtatala ng mga ito. Pagkatapos ng pagpapatakbo ng test, tinatawag ang verify. Sinusuportahan ng OCMock ang spy para sa anumang object (sa Objective-C lahat ng method ay dinamiko), na nagbibigay ng kalamangan kumpara sa Swift, kung saan ang spy ay posible lamang sa pamamagitan ng protocol.

objective-c
// Paggawa ng spy para sa tunay na object
AnalyticsTracker *realTracker = [[AnalyticsTracker alloc] init];
AnalyticsTracker *spy = [OCMockObject partialMockForObject:realTracker];

// Pagpapatakbo ng test
[spy trackEvent:@"login"];

// Pagpapatunay
[[spy verify] trackEvent:@"login"];
XCTAssertEqual([realTracker eventCount], 1);

Swift protocol-based spy — sa Swift ay walang Objective-C runtime reflection, kaya ang spy ay ginagawa nang manu-mano. Ang test structure ay nag-iimplementa ng protocol at sa loob ay tumatawag sa tunay na object, sabay na nagtatala ng mga tawag. Ito ay mas maraming code, ngunit ganap na kontrolado at type-safe. Ang manu-manong espiya ay hindi nangangailangan ng panlabas na library at hindi gumagamit ng runtime — lahat ay sinusuri sa yugto ng compilation.

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)
    }
}

Kailan gagamit ng OCMock vs manu-manong spy — para sa Objective-C code, gamitin ang OCMock (mas kaunting boilerplate). Para sa Swift — mas gusto ang manu-manong espiya sa pamamagitan ng protocol. Ang manu-manong spy ay nagbibigay ng ganap na kontrol sa pagtatala ng mga tawag, hindi nangangailangan ng reflection, at gumagana sa value types (struct). Ang tanging kakulangan: kailangan panatilihing naka-sync ang spy class code sa protocol kapag nagdaragdag ng mga bagong method.

Mga karaniwang senaryo ng paggamit ng Spy

Pagsusuri ng analytics — ang pinakakaraniwang senaryo ng paggamit ng spy. Sa production code, ang mga tawag sa analytics ay nakakalat sa buong application: login, logout, purchase, error. Gumagawa ang test ng spy-balot para sa AnalyticsTracker, isinasagawa ang senaryo (pag-login, pagtingin ng produkto, pagdagdag sa cart, pagbili) at sinusuri kung ang lahat ng kinakailangang event ay naipadala sa tamang pagkakasunod-sunod. Hindi angkop ang mock dahil ang AnalyticsTracker ay naglalaman ng lohika ng buffering at pagpapadala.

Timer at scheduler — ang pagsubok ng code na gumagamit ng Handler (Android) o Timer (iOS) ay mahirap dahil sa tunay na oras. Ang spy-balot para sa Scheduler ay nagtatala kung aling mga gawain ang naka-iskedyul at sa anong pagkaantala. Gumagawa ang test ng spy ng tunay na Handler, isinasagawa ang aksyon at sinusuri kung ang Handler.postDelayed(runnable, delay) ay tinawag na may tamang pagkaantala. Ang tunay na gawain ay hindi isinasagawa — hinaharang at itinatala ng spy ang tawag.

Pag-log at impormasyon ng debug — sa production, ang mga log ay maaaring naka-off o nakasulat sa file. Ang spy-balot para sa Logger ay nagtatala ng lahat ng mensahe sa isang in-memory list na sinusuri ng test pagkatapos ng pagpapatakbo. Ito ay nagpapahintulot na suriin kung ang tamang mensahe ay naisulat kapag may error, nang hindi nadudumihan ang console. Ang manu-manong espiya para sa Logger ay lalong kapaki-pakinabang sa iOS, kung saan ang OSLog ay walang test API.

Pagsusuri ng pagkakasunod-sunod ng mga tawag — ang ilang senaryo ay nangangailangan ng mahigpit na pagkakasunod-sunod ng mga operasyon: buksan ang koneksyon, magpadala ng data, isara ang koneksyon. Pinapayagan ng Mockito ang pagsusuri ng pagkakasunod-sunod sa pamamagitan ng InOrder.verify(). Ginagawa rin ito ng spy ngunit pinapanatili ang tunay na pagpapatakbo. Kung hindi lamang ang pagkakasunod-sunod kundi pati ang resulta ng bawat hakbang ay mahalaga (ang koneksyon ay talagang nabuksan) — gumamit ng spy, hindi mock.

Mga Madalas Itanong

Spy vs Mock: ano ang pangunahing pagkakaiba?

Spy ay bumabalot sa tunay na object at isinasagawa ang lohika nito, dagdag na nagtatala ng mga tawag. Ganap na pinapalitan ng mock ang object ng stub — walang tunay na lohika na isinasagawa. Pinapanatili ng Spy ang pag-uugali, hindi ang mock. Piliin ang spy kapag mahalaga ang tunay na gawain ng object; piliin ang mock kapag kailangan ihiwalay ang test mula sa panlabas na dependency.

Kailan ang Spy ay masamang pagpili?

Kapag ang spy-balot ay humantong sa tunay na I/O operations. Kung binabalot ng spy ang isang object na nagsusulat sa file, nagpapadala ng HTTP, o bumabasa mula sa disk — ang test ay tumitigil na maging unit test. Pangalawang kaso: ang test ay sumusuri lamang ng ibinalik na halaga nang walang interes sa mga tawag — dito sapat na ang stub, at ang spy ay kalabisan. Pangatlo: ang code ay umaasa sa panloob na estado ng spy — ito ay isang marupok na test.

Sinusuportahan ba ng MockK ang spy?

Oo, sa pamamagitan ng spyk() — kahalintulad ng Mockito.spy(). Ang MockK.spyk() ay gumagawa ng espiya sa paligid ng tunay na object, sinusuportahan ang every para sa bahagyang stubbing at coVerify/coroutinesVerify para sa suspend functions. Limitasyon: hindi gumagana sa final classes (kailangan ng open o interface). Para sa Java classes, sinusuportahan din ng MockK ang spyk(), ngunit nangangailangan ng @MockKJvmInline annotation.

Maaari bang gumawa ng Spy mula sa Mock?

Teknikal — hindi. Ang Mock ay isang stub na walang tunay na implementasyon. Ang spy ay sa pamamagitan ng depinisyon ay bumabalot sa tunay na object. Sa Mockito, hindi maaaring gawing spy ang mock. Ngunit ang kabaligtaran ay maaaring gawin: gumawa ng spy at i-override ang bahagi ng mga method sa pamamagitan ng doReturn/when (partial mocking). Ito ay nagbibigay ng pag-uugaling katulad ng mock para sa mga napiling method ng spy-object.

Spy sa Swift — obligado sa pamamagitan ng protocol?

Obligado. Sa Swift ay walang dinamikong proxy tulad ng sa Java/Kotlin. Para sa paggawa ng spy, kailangan ng protocol na ini-implementa ng parehong production class at spy class. Ang Swift-protocol-based spy ay isang manu-manong implementasyon na tumatanggap ng tunay na object, nagde-delegate ng mga tawag dito, at nagtatala ng metadata. Alternatibo: ang Cuckoo library, na bumubuo ng spy classes sa pamamagitan ng SourceKit.

Buod

  • Spy — balot sa paligid ng tunay na object na nagtatala ng lahat ng tawag sa method nang hindi pinapalitan ang lohika
  • Pagkakaiba sa Mock — spy ay nagsasagawa ng tunay na code, mock ay pinapalitan ito ng stub
  • Mockito.spy() — para sa Java/Kotlin, bumabalot sa tunay na object na may kakayahang verify at partial mock
  • MockK.spyk() — Kotlin analog na may suporta para sa coroutine, sealed classes, at coVerify
  • iOS — OCMock para sa Objective-C, manu-manong protocol-based spy para sa Swift
  • Pangunahing senaryo — pagsusuri ng analytics, timer, log, at pagkakasunod-sunod ng mga tawag
  • Pag-iingat — spy na may I/O operations ay ginagawang integration test ang unit test

Gagawa kami ng mobile application na turnkey

Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.

Pag-usapan ang proyekto

Basahin din