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 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 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.
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.
| Senaryo | Spy | Mock |
|---|---|---|
| Gerçek mantık gerekli | Evet | Hayır (stub) |
| Çağrı doğrulama | Evet (sayı, argümanlar) | Evet (sayı, argümanlar) |
| Kısmi stubbing | Evet (bazı metotlar spy, diğerleri stub) | Hayır (tüm metotlar stub) |
| Yan etki riski | Yüksek (gerçek kod) | Sıfır |
| Hız | Daha düşük (gerçek mantık) | Daha yüksek (stub) |
| Okunabilirlik | Daha düşük (neyin gerçek olduğunu anlamak zor) | Daha yüksek (her şey açık) |
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.
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.
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.
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)
}
}
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.
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.
// 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.
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.
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 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 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.
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.
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.
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
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.
Ayrıca okuyun