Spy (casus) — real nümunəni sarğılayan və hər çağırış haqqında məlumat yazan test obyektidir: hansı metodlar çağırıldı, hansı arqumentlərlə, neçə dəfə. Mock-dan fərqli olaraq, spy sarğılanmış obyektin real tətbiqindən istifadə edir — çağırışlar həqiqi koddan keçir, spy isə yalnız faktları qeyd edir. Test icra edildikdən sonra tərtibatçı casusun qeydlərini yoxlayır: „sendAnalytics metodu üç dəfə çağırıldı?“ Ətraflı — Android Test Bələdçisində.
Əsas məqamlar
Spy — real obyektin ətrafında sarğıdır, bütün metod çağırışlarını ələ keçirir və qeyd edir. Obyektin real məntiqi icra olunur: əgər metod məlumat saxlayırsa, dəyər hesablayırsa və ya sorğu edirsə — hər şey adi qaydada baş verir. Əlavə olaraq spy metadata qeyd edir: metod adı, arqumentlər, çağırış sayı, icra müddəti. Termin Mezaros (2007) təsnifatına daxildir və Martin Faulerin „Mocks Aren't Stubs“ məqaləsində ətraflı təsvir edilmişdir.
Mock-dan əsas fərq — mock obyekti tamamilə test qaralama ilə əvəz edir, bütün metodlar susmaya görə heç nə etmir. Spy mövcud obyekti sarğılayır: bütün metodlar susmaya görə adi qaydada işləyir, lakin eyni zamanda qeyd olunur. Bu fərq prinsipialdır: mock test edilən kodu reallıqdan təcrid edir, spy reallığı qoruyur və onu müşahidə etməyə imkan verir. Onlar arasında seçim nəyin test edildiyindən asılıdır.
Spy — düzgün seçim — əgər test edilən kod real obyektin vəziyyətini dəyişdirirsə və test həm nəticəni (vəziyyəti) yoxlamalı, həm də çağırışların düzgün ardıcıllıqla edildiyinə əmin olmalıdırsa. Mock uyğun deyil, çünki real tətbiqi icra etmir. Stub uyğun deyil, çünki çağırışları qeyd etmir. Spy eyni zamanda həm real məntiqi qoruyan, həm də çağırışlar haqqında məlumat verən yeganə test ikilisidir.
Mock — tam təcrid. Əgər test real obyektin tətbiqindən asılı olmamalıdırsa (məsələn, verilənlər bazası və ya şəbəkə müştərisi), mock istifadə edin. Mock heç bir çağırışın real komponentə çatmayacağına zəmanət verir. Bu təhlükəsiz və proqnozlaşdırıla biləndir. Çatışmazlıq: mock real məntiqi icra etmir, ona görə də test edilən kod qaytarılan dəyərə güvənirsə — onu when/stub vasitəsilə açıq şəkildə konfiqurasiya etmək lazımdır.
Spy — real məntiq + müşahidə. Əgər test edilən kod yalnız məlumat deyil, məntiqi test üçün vacib olan obyektlə qarşılıqlı əlaqədədirsə — spy istifadə edin. Məsələn, hadisələri toplayan və vaxtaşırı göndərən AnalyticsTracker. Test hadisələrin buferə əlavə edildiyini və göndərmədən sonra buferin təmizləndiyini yoxlayır. Mock bunu yoxlaya bilməz, çünki tracker-in real məntiqini icra etmir.
| Ssenari | Spy | Mock |
|---|---|---|
| Real məntiq lazımdır | Bəli | Xeyr (qaralama) |
| Çağırışların doğrulanması | Bəli (say, arqumentlər) | Bəli (say, arqumentlər) |
| Qismən stublama | Bəli (bəzi metodlar — spy, digərləri — stub) | Xeyr (bütün metodlar — qaralama) |
| Yan təsir riski | Yüksək (real kod) | Sıfır |
| Sürət | Aşağı (real məntiq) | Yüksək (qaralamalar) |
| Oxunaqlılıq | Aşağı (nəyin real olduğunu anlamaq çətindir) | Yüksək (hər şey aydındır) |
Hər şey üçün spy — bütün testlərdə mock əvəzinə spy istifadə etmək səhvdir. Spy yan təsirləri ola biləcək real kodu icra edir: fayla yazmaq, HTTP göndərmək, qlobal vəziyyəti dəyişdirmək. Əgər test edilən modul HTTP sorğusu edən spy-obyektin metodunu çağırırsa, test inteqrasiya testinə çevrilir, vahid test deyil. Qayda: əgər spy I/O əməliyyatları olan obyekti sarğılayırsa — bu artıq vahid test deyil. I/O təcridi üçün mock, yalnız xarici təsirləri olmayan yaddaşdaxili obyektlər üçün spy istifadə edin.
Mockito.spy() — Java/Kotlin layihələrində casus yaratmağın klassik üsulu. spy() real obyekti qəbul edir və sarğı qaytarır. Bütün çağırışlar susmaya görə real obyektə ötürülür, nəticələr isə qeyd olunur. Test icra edildikdən sonra verify() vasitəsilə çağırışların sayını və arqumentləri yoxlamaq olar. Test məlumatlarını qaytarmalı olan metodlar üçün doReturn/when istifadə olunur — buna „qismən stublama“ (partial mocking) deyilir.
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() — korutinləri və sealed-sinifləri daha yaxşı dəstəkləyən Kotlin layihələri üçün alternativ. MockK.spyk() casus yaradır, Mockito.spy()-nin analoqudur. Suspend-funksiyalar üçün coVerify və qismən stublama üçün every dəstəkləyir. Mockito-dan fərqli olaraq, MockK final-siniflər üçün spy dəstəkləmir (Kotlin-də bütün siniflər susmaya görə finaldır) — sinfi açmaq (open) və ya interfeys istifadə etmək lazımdır.
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)
}
}
Qismən stublama — güclü, lakin təhlükəli texnikadır. Siz obyektin spy-ni yaradıb yalnız bəzi metodları ləğv edə (stub) bilərsiniz, qalanlarını real olaraq buraxa bilərsiniz. Nümunə: getUser()-i test məlumatları qaytaran, saveUser()-i isə həqiqətən yaddaşdaxili siyahıya yazan spy-repozitoriya. Bu, stubların (idarə olunan məlumat) və spy-nin (real məntiq) üstünlüklərini birləşdirməyə imkan verir. Çatışmazlıq: testin oxunmasının mürəkkəbliyi — hansı metodların real, hansının stub olduğu aydın deyil.
Objective-C üçün OCMock — niceMock vasitəsilə spy-obyektləri yaratmağı dəstəkləyən kitabxana. OCMock Objective-C iş vaxtı mühitindən istifadə edərək metod çağırışlarını ələ keçirir və qeyd edir. Test icra edildikdən sonra verify çağırılır. OCMock istənilən obyekt üçün spy dəstəkləyir (Objective-C-də bütün metodlar dinamikdir), bu da Swift-dən üstünlük verir, çünki Swift-də spy yalnız protokollar vasitəsilə mümkündür.
// Real obyekt üçün spy yaradılması
AnalyticsTracker *realTracker = [[AnalyticsTracker alloc] init];
AnalyticsTracker *spy = [OCMockObject partialMockForObject:realTracker];
// Testin icrası
[spy trackEvent:@"login"];
// Doğrulama
[[spy verify] trackEvent:@"login"];
XCTAssertEqual([realTracker eventCount], 1);
Swift protocol-based spy — Swift-də Objective-C-nin iş vaxtı refleksiyası yoxdur, buna görə spy əl ilə yaradılır. Test strukturu protokolu tətbiq edir və daxildə real obyekti çağırır, eyni zamanda çağırışları qeyd edir. Bu daha çox koddur, lakin tam idarə olunan və tip təhlükəsizdir. Əl casusları xarici kitabxanalar tələb etmir və iş vaxtı mühitindən istifadə etmir — hər şey kompilyasiya mərhələsində yoxlanılır.
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 əl spy nə vaxt istifadə olunmalı — Objective-C kodu üçün OCMock istifadə edin (az boilerplate). Swift üçün — protokollar vasitəsilə əl casusları üstünlük təşkil edir. Əl spy çağırışların qeydiyyatı üzərində tam nəzarət verir, refleksiya tələb etmir və dəyər tipləri (struct) ilə işləyir. Yeganə çatışmazlıq: yeni metodlar əlavə edərkən spy-sinif kodunu protokolla sinxron saxlamalısınız.
Analitikanın yoxlanması — spy istifadəsinin ən çox yayılmış ssenarisi. İstehsal kodunda analitika çağırışları bütün tətbiqə səpələnmişdir: giriş, çıxış, satınalma, xəta. Test AnalyticsTracker üçün spy-sarğı yaradır, ssenarini icra edir (giriş, məhsula baxış, səbətə əlavə etmə, satınalma) və bütün lazımi hadisələrin düzgün ardıcıllıqla göndərildiyini yoxlayır. Mock uyğun deyil, çünki AnalyticsTracker buferləmə və göndərmə məntiqini ehtiva edir.
Timerlər və planlayıcılar — Handler (Android) və ya Timer (iOS) istifadə edən kodun test edilməsi real vaxta görə çətindir. Scheduler üçün spy-sarğı hansı tapşırıqların və hansı gecikmə ilə planlaşdırıldığını qeyd edir. Test real Handler-in spy-ni yaradır, hərəkəti icra edir və Handler.postDelayed(runnable, delay) düzgün gecikmə ilə çağırıldığını yoxlayır. Real tapşırıq bu zaman icra olunmur — spy çağırışı ələ keçirir və qeyd edir.
Loglama və debug məlumatı — istehsalda loglar söndürülə və ya fayla yazıla bilər. Logger üçün spy-sarğı bütün mesajları testin icradan sonra yoxladığı yaddaşdaxili siyahıya yazır. Bu, konsolu zibilləmədən xəta zamanı düzgün mesajın yazıldığını yoxlamağa imkan verir. Logger üçün əl casusları xüsusilə iOS-da faydalıdır, çünki OSLog-un test API-si yoxdur.
Çağırışların ardıcıllığının yoxlanması — bəzi ssenarilər ciddi əməliyyat ardıcıllığı tələb edir: əlaqəni aç, məlumat göndər, əlaqəni bağla. Mockito InOrder.verify() vasitəsilə ardıcıllığı yoxlamağa imkan verir. Spy eyni şeyi edir, lakin real icranı qoruyur. Əgər təkcə ardıcıllıq deyil, həm də hər addımın nəticəsi vacibdirsə (əlaqə həqiqətən açıldı) — mock yox, spy istifadə edin.
Tez-tez verilən suallar
Spy real obyekti sarğılayır və onun məntiqini icra edir, əlavə olaraq çağırışları qeyd edir. Mock obyekti tamamilə qaralama ilə əvəz edir — heç bir real məntiq icra olunmur. Spy davranışı qoruyur, mock isə yox. Obyektin real işi vacib olduqda spy seçin; testi xarici asılılıqdan təcrid etmək lazım olduqda mock seçin.
Spy-sarğı real I/O əməliyyatlarına gətirib çıxardıqda. Əgər spy fayla yazan, HTTP göndərən və ya diskdən oxuyan obyekti sarğılayırsa — test vahid test olmaqdan çıxır. İkinci hal: test yalnız qaytarılan dəyəri çağırışlara maraq göstərmədən yoxlayır — burada stub kifayətdir, spy artıqdır. Üçüncü: kod spy-nin daxili vəziyyətinə güvənir — bu kövrək testdir.
Bəli, spyk() vasitəsilə — Mockito.spy()-nin analoqu. MockK.spyk() real obyektin ətrafında casus yaradır, qismən stublama üçün every və suspend-funksiyalar üçün coVerify/coroutinesVerify dəstəkləyir. Məhdudiyyət: final-siniflərlə işləmir (open və ya interfeys lazımdır). Java sinifləri üçün MockK da spyk() dəstəkləyir, lakin @MockKJvmInline annotasiyası tələb edir.
Texniki olaraq — yox. Mock real tətbiqi olmayan qaralamadır. Spy tərifinə görə real obyekti sarğılayır. Mockito-da mock-u spy-ə çevirmək mümkün deyil. Amma əksini etmək olar: spy yaradıb doReturn/when (partial mocking) vasitəsilə metodların bir hissəsini ləğv etmək. Bu, spy-obyektin seçilmiş metodları üçün mock-a bənzər davranış verir.
Mütləq. Swift-də Java/Kotlin-dəki kimi dinamik proksiləmə yoxdur. Spy yaratmaq üçün həm istehsal sinfi, həm də spy sinfi tərəfindən tətbiq olunan protokol lazımdır. Swift-protocol-based spy — real obyekti qəbul edən, ona çağırışları ötürən və metadata qeyd edən əl tətbiqdir. Alternativ: Cuckoo kitabxanası, SourceKit vasitəsilə spy-sinifləri yaradır.
Xülasə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun