Spy — nedir, Mockito.spy() ve çağrı doğrulama

Yazar: IT Sectr Yayınlanma: 2026-04-10 Okuma süresi: 9 dk

Spy (casus) — gerçek bir örneği saran ve her çağrı hakkında bilgi kaydeden bir test nesnesidir: hangi metotların çağrıldığı, hangi argümanlarla ve kaç kez. Mock'tan farklı olarak, spy sarılı nesnenin gerçek uygulamasını kullanır — çağrılar gerçek koddan geçer ve spy yalnızca gerçekleri kaydeder. Test yürütüldükten sonra geliştirici casusun kayıtlarını kontrol eder: “sendAnalytics metodu üç kez çağrıldı mı?”. Daha fazla bilgi için Android test kılavuzuna bakın.

Önemli noktalar

  • Spy — gerçek bir uygulamanın metot çağrılarını değiştirmeden kaydeden nesne
  • Doğrulama — testten sonra spy, bir metodun kaç kez ve hangi argümanlarla çağrıldığını kontrol etmenizi sağlar
  • Mockito.spy() — Android'de gerçek Java/Kotlin nesneleri için casus oluşturur
  • Kısmi mocking — spy, stub'larla birleştirilebilir: bazı metotları engellemek, diğerlerini geçirmek
  • iOS — OCMock (Objective-C) ve Swift'te protokoller aracılığıyla manuel casuslar

Spy nedir ve Mock'tan farkı nedir?

Spy — gerçek bir nesnenin etrafında, tüm metot çağrılarını engelleyen ve kaydeden bir sarmalayıcıdır. Nesnenin gerçek mantığı yürütülür: metot veri kaydediyorsa, bir değer hesaplıyorsa veya bir istek yapıyorsa — her şey normal şekilde gerçekleşir. Ek olarak, spy meta verileri kaydeder: metot adı, argümanlar, çağrı sayısı, yürütme süresi. Terim Meszaros (2007) sınıflandırmasının bir parçasıdır ve Martin Fowler'ın “Mocks Aren’t Stubs” makalesinde ayrıntılı olarak açıklanmıştır.

Mock'tan temel fark — mock, nesneyi tamamen bir test stub'ıyla değiştirir; tüm metotlar varsayılan olarak hiçbir şey yapmaz. Spy, mevcut bir nesneyi sarar: tüm metotlar varsayılan olarak normal şekilde çalışır ancak ayrıca kaydedilir. Bu ayrım temeldir: mock, test edilen kodu gerçeklikten izole ederken, spy gerçekliği korur ve onu gözlemlemeye olanak tanır. Aralarındaki seçim, neyin test edildiğine bağlıdır.

Spy ne zaman doğru seçimdir

Spy doğru seçimdir — test edilen kod gerçek bir nesnenin durumunu değiştiriyorsa ve test hem sonucu (durumu) doğrulamalı hem de çağrıların doğru sırada yapıldığından emin olmalıysa. mock uygun değildir çünkü gerçek uygulamayı yürütmez. stub uygun değildir çünkü çağrıları kaydetmez. Spy, aynı anda hem gerçek mantığı koruyan hem de çağrılar hakkında bilgi sağlayan tek test dublesidir.

Spy vs Mock: casus ne zaman kullanılır

Mock — tam izolasyon. Test, gerçek nesnenin uygulamasına bağlı olmamalıysa (örneğin, bir veritabanı veya ağ istemcisi), mock kullanın. mock, hiçbir çağrının gerçek bileşene ulaşmamasını sağlar. Bu güvenli ve öngörülebilirdir. Dezavantajı: mock gerçek mantığı yürütmez, bu nedenle test edilen kod bir dönüş değerine bağlıysa, when/stub aracılığıyla açıkça yapılandırılmalıdır.

Spy — gerçek mantık + gözlem. Test edilen kod, yalnızca veri değil, mantığı test için önemli olan bir nesneyle etkileşime giriyorsa — spy kullanın. Örneğin, olayları toplayan ve periyodik olarak gönderen bir AnalyticsTracker. Test, olayların arabelleğe eklendiğini ve gönderimden sonra ara belleğin temizlendiğini doğrular. mock bunu doğrulayamaz çünkü izleyicinin gerçek mantığını yürütmez.

SenaryoSpyMock
Gerçek mantık gerekliEvetHayır (stub)
Çağrı doğrulamaEvet (sayı, argümanlar)Evet (sayı, argümanlar)
Kısmi stubbingEvet (bazı metotlar spy, diğerleri stub)Hayır (tüm metotlar stub)
Yan etki riskiYüksek (gerçek kod)Sıfır
HızDaha düşük (gerçek mantık)Daha yüksek (stub)
OkunabilirlikDaha düşük (neyin gerçek olduğunu anlamak zor)Daha yüksek (her şey açık)

Anti-desen: her şey için spy

Her şey için spy — tüm testlerde mock yerine spy kullanmak bir hatadır. spy, yan etkileri olabilecek gerçek kodu yürütür: dosyaya yazma, HTTP gönderme, genel durumu değiştirme. Test edilen modül, HTTP isteği yapan bir spy nesnesinin metodunu çağırırsa, test birim test değil, entegrasyon testi haline gelir. Kural: spy, G/Ç işlemleri olan bir nesneyi sarıyorsa — bu artık bir birim test değildir. G/Ç'yi izole etmek için mock kullanın ve spy'yı yalnızca harici etkileri olmayan bellek içi nesneler için kullanın.

Android'de Mockito.spy() ve MockK'da spy

Mockito.spy() — Java/Kotlin projelerinde casus oluşturmanın klasik yoludur. spy() gerçek bir nesne alır ve bir sarmalayıcı döndürür. Tüm çağrılar varsayılan olarak gerçek nesneye devredilir ve sonuçlar kaydedilir. Testten sonra, çağrı sayısını ve argümanları kontrol etmek için verify() kullanabilirsiniz. Test verisi döndürmesi gereken metotlar için doReturn/when kullanılır — buna “kısmi mocking” denir.

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() — coroutine ve sealed sınıflar için daha iyi destek sağlayan Kotlin projeleri için bir alternatiftir. MockK.spyk(), Mockito.spy()'ye benzer bir casus oluşturur. Suspend fonksiyonlar için coVerify ve kısmi stubbing için every'i destekler. Mockito'nun aksine MockK, final sınıflar için spy'yı desteklemez (Kotlin'de tüm sınıflar varsayılan olarak final'dır) — sınıfı açmanız (open) veya bir arayüz kullanmanız gerekir.

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

Spy aracılığıyla kısmi stubbing

Kısmi stubbing — güçlü ancak tehlikeli bir tekniktir. Bir nesnenin spy'sını oluşturabilir ve yalnızca bazı metotları geçersiz kılabilir (stub), diğerlerini gerçek bırakabilirsiniz. Örnek: getUser()'un test verisi döndürdüğü, saveUser()'un ise gerçekten bellekteki bir listeye kaydettiği bir spy deposu. Bu, stub (kontrollü veri) ve spy (gerçek mantık) avantajlarını birleştirmenize olanak tanır. Dezavantajı: testin okunabilirliği zarar görür — hangi metotların gerçek, hangilerinin stub olduğu açık değildir.

iOS'ta OCMock ve protokollerle Spy uygulaması

Objective-C için OCMock — niceMock aracılığıyla spy nesneleri oluşturmayı destekleyen bir kütüphanedir. OCMock, Objective-C çalışma zamanını kullanarak metot çağrılarını engeller ve kaydeder. Testten sonra verify çağrılır. OCMock, herhangi bir nesne için spy'yı destekler (Objective-C'de tüm metotlar dinamiktir), bu da ona spy'nin yalnızca protokoller aracılığıyla mümkün olduğu Swift'e karşı bir avantaj sağlar.

objective-c
// Gerçek bir nesne için spy oluşturma
AnalyticsTracker *realTracker = [[AnalyticsTracker alloc] init];
AnalyticsTracker *spy = [OCMockObject partialMockForObject:realTracker];

// Testi çalıştırma
[spy trackEvent:@"login"];

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

Swift protokol tabanlı spy — Swift, Objective-C gibi çalışma zamanı yansımasına sahip değildir, bu nedenle spy'lar manuel olarak oluşturulur. Bir test yapısı bir protokol uygular ve dahili olarak gerçek nesneyi çağırırken aynı anda çağrıları kaydeder. Bu daha fazla kod gerektirir, ancak tamamen kontrol edilebilir ve tür güvenlidir. Manuel casuslar harici kütüphane gerektirmez ve çalışma zamanı kullanmaz — her şey derleme zamanında kontrol edilir.

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

OCMock vs manuel spy ne zaman kullanılır — Objective-C kodu için OCMock kullanın (daha az kalıp kodu). Swift için protokoller aracılığıyla manuel casuslar tercih edilir. Manuel bir spy, çağrı kaydı üzerinde tam kontrol sağlar, yansıma gerektirmez ve değer türleriyle (struct) çalışır. Tek dezavantajı, yeni metotlar eklerken spy sınıfı kodunu protokolle senkronize tutmanız gerektiğidir.

Spy'nin tipik kullanım senaryoları

Analitik doğrulama — casusların en yaygın kullanım durumudur. Üretim kodunda, analitik çağrıları uygulama geneline yayılmıştır: giriş, çıkış, satın alma, hata. Test, AnalyticsTracker için bir spy sarmalayıcı oluşturur, bir senaryo yürütür (giriş, ürün görüntüleme, sepete ekleme, satın alma) ve gerekli tüm olayların doğru sırada gönderilip gönderilmediğini kontrol eder. mock uygun değildir çünkü AnalyticsTracker arabelleğe alma ve gönderme mantığı içerir.

Zamanlayıcılar ve planlayıcılar — Handler (Android) veya Timer (iOS) kullanan kodu test etmek, gerçek zaman nedeniyle zordur. Scheduler için bir spy sarmalayıcı, hangi görevlerin hangi gecikmeyle planlandığını kaydeder. Test, gerçek Handler'ın bir spy'sını oluşturur, bir eylem gerçekleştirir ve Handler.postDelayed(runnable, delay)'in doğru gecikmeyle çağrılıp çağrılmadığını kontrol eder. Gerçek görev yürütülmez — spy çağrıyı engeller ve kaydeder.

Günlükleme ve hata ayıklama bilgileri — üretimde, günlükler devre dışı bırakılabilir veya bir dosyaya yazılabilir. Logger için bir spy sarmalayıcı, tüm mesajları testin yürütmeden sonra kontrol ettiği bellekteki bir listeye kaydeder. Bu, konsolu karıştırmadan hata durumunda doğru mesajın yazıldığını doğrulamaya olanak tanır. Logger için manuel casuslar, OSLog'un test API'sine sahip olmadığı iOS'ta özellikle kullanışlıdır.

Çağrı sırası doğrulama — bazı senaryolar katı bir işlem sırası gerektirir: bağlantıyı aç, veri gönder, bağlantıyı kapat. Mockito, InOrder.verify() aracılığıyla sırayı kontrol etmeye olanak tanır. spy da aynısını yapar ancak gerçek yürütmeyi korur. Hem sıra hem de her adımın sonucu (bağlantı gerçekten açıldı) önemliyse — mock değil, spy kullanın.

Sıkça sorulan sorular

Spy vs Mock: temel fark nedir?

Spy gerçek bir nesneyi sarar ve mantığını yürütür, ayrıca çağrıları kaydeder. Mock, nesneyi tamamen bir stub ile değiştirir — hiçbir gerçek mantık yürütülmez. spy davranışı korur, mock korumaz. Nesnenin gerçek çalışması önemli olduğunda spy seçin; testi harici bir bağımlılıktan izole etmeniz gerektiğinde mock seçin.

Spy ne zaman kötü bir seçimdir?

Spy sarmalayıcı gerçek G/Ç işlemlerine yol açtığında. Bir spy, dosyaya yazan, HTTP gönderen veya diskten okuyan bir nesneyi sarıyorsa — test artık birim test değildir. İkinci durum: test, çağrılarla ilgilenmeden yalnızca dönüş değerini kontrol eder — burada stub yeterlidir ve spy gereksizdir. Üçüncü: kod, spy'nin iç durumuna bağlıdır — bu kırılgan bir testtir.

MockK spy'yı destekliyor mu?

Evet, spyk() aracılığıyla — Mockito.spy()'nin eşdeğeri. MockK.spyk(), gerçek bir nesnenin etrafında bir casus oluşturur, kısmi stubbing için every ve suspend fonksiyonlar için coVerify/coroutinesVerify'i destekler. Sınırlama: final sınıflarla çalışmaz (open veya arayüz gerekir). Java sınıfları için MockK ayrıca spyk()'yi destekler ancak @MockKJvmInline ek açıklamasını gerektirir.

Mock'tan Spy yapılabilir mi?

Teknik olarak, hayır. Mock, gerçek uygulama içermeyen bir stub'tır. Spy, tanımı gereği gerçek bir nesneyi sarar. Mockito'da bir mock'u spy'ya dönüştüremezsiniz. Ancak tersini yapabilirsiniz: bir spy oluşturun ve doReturn/when (kısmi mocking) aracılığıyla bazı metotları geçersiz kılın. Bu, spy nesnesinin seçili metotları için mock benzeri bir davranış sağlar.

Swift'te Spy için protokol gerekli mi?

Evet, gereklidir. Swift'te Java/Kotlin'deki gibi dinamik proxy yoktur. Spy oluşturmak için, hem üretim sınıfının hem de spy sınıfının uyguladığı bir protokol gereklidir. Swift protokol tabanlı spy, gerçek bir nesne alan, çağrıları ona devreden ve meta verileri kaydeden manuel bir uygulamadır. Alternatif: SourceKit aracılığıyla spy sınıfları oluşturan Cuckoo kütüphanesi.

Özet

  • Spy — mantığı değiştirmeden tüm metot çağrılarını kaydeden gerçek bir nesnenin sarmalayıcısı
  • Mock'tan farkı — spy gerçek kodu yürütür, mock onu bir stub ile değiştirir
  • Mockito.spy() — Java/Kotlin için, verify ve kısmi mock desteğiyle gerçek nesneyi sarar
  • MockK.spyk() — coroutine, sealed sınıflar ve coVerify desteğiyle Kotlin eşdeğeri
  • iOS — Objective-C için OCMock, Swift için manuel protokol tabanlı spy
  • Ana senaryolar — analitik doğrulama, zamanlayıcılar, günlükler ve çağrı sırası doğrulama
  • Dikkat — G/Ç işlemleri olan bir spy, birim testi entegrasyon testine dönüştürür

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun