Spy (जासूस) — एक परीक्षण ऑब्जेक्ट जो वास्तविक उदाहरण को लपेटता है और प्रत्येक कॉल के बारे में जानकारी रिकॉर्ड करता है: कौन से मेथड बुलाए गए, किन आर्गुमेंट्स के साथ, और कितनी बार। mock के विपरीत, spy लपेटे गए ऑब्जेक्ट के वास्तविक कार्यान्वयन का उपयोग करता है — कॉल वास्तविक कोड से गुजरते हैं, और spy केवल तथ्यों को रिकॉर्ड करता है। परीक्षण के बाद, डिवेलपर जासूस के रिकॉर्ड की जाँच करता है: «क्या sendAnalytics मेथड तीन बार बुलाया गया था?»। और अधिक जानकारी के लिए Android परीक्षण गाइड देखें।
मुख्य बातें
Spy — एक वास्तविक ऑब्जेक्ट के चारों ओर एक रॅपर है जो सभी मेथड कॉल्स को इंटरसेप्ट करता है और उन्हें रिकॉर्ड करता है। ऑब्जेक्ट का वास्तविक तर्क निष्पादित होता है: यदि मेथड डेटा संग्रहीत करता है, मान की गणना करता है, या अनुरोध करता है — सब कुछ हमेशा की तरह होता है। इसके अतिरिक्त, spy मेटाडेटा रिकॉर्ड करता है: मेथड नाम, आर्गुमेंट्स, कॉल की संख्या, निष्पादन समय। यह शब्द Meszaros (2007) वरीकरण का हिस्सा है और Martin Fowler के लेख «Mocks Aren’t Stubs» में विस्तार से वर्णित है।
Mock से मुख्य अंतर — mock ऑब्जेक्ट को पूरी तरह से एक परीक्षण stub से बदल देता है; सभी मेथड डिफ़ॉल्ट रूप से कुछ नहीं करते। Spy एक मौजूदा ऑब्जेक्ट को लपेटता है: सभी मेथड डिफ़ॉल्ट रूप से हमेशा की तरह काम करते हैं, लेकिन रिकॉर्ड भी होते हैं। यह अंतर मौलिक है: mock परीक्षित कोड को वास्तविकता से अलग करता है, जबकि spy वास्तविकता को संरक्षित रखता है और इसका निरीक्षण करने की अनुमति देता है। उनके बीच चुनाव इस बात पर निर्भर करता है कि क्या परीक्षित किया जा रहा है।
Spy सही विकल्प है — यदि परीक्षित कोड वास्तविक ऑब्जेक्ट की स्थिति को संशोधित करता है, और परीक्षण को परिणाम (स्थिति) और यह सुनिश्चित करना दोनों करना चाहिए कि कॉल सही क्रम में किए गए थे। mock उपयुक्त नहीं है क्योंकि यह वास्तविक कार्यान्वयन नहीं करता है। stub उपयुक्त नहीं है क्योंकि यह कॉल रिकॉर्ड नहीं करता है। Spy एकमात्र परीक्षण डबल है जो एकसाथ वास्तविक तर्क को संरक्षित रखता है और कॉल के बारे में जानकारी प्रदान करता है।
Mock — पूर्ण पृथक्करण. यदि परीक्षण वास्तविक ऑब्जेक्ट के कार्यान्वयन पर निर्भर नहीं होना चाहिए (उदाहरण के लिए, डेटाबेस या नेटवर्क क्लाइंट), mock का उपयोग करें। mock सुनिश्चित करता है कि कोई कॉल वास्तविक घटक तक न पहुंचे। यह सुरक्षित और पूर्वानुमानीय है। नुकसान: mock वास्तविक तर्क नहीं चलाता, इसलिए यदि परीक्षित कोड वापसी मान पर निर्भर करता है, तो इसे when/stub के माध्यम से स्पष्ट रूप से कॉन्फ़िगर करने की आवश्यकता है।
Spy — वास्तविक तर्क + अवलोकन. यदि परीक्षित कोड एक ऐसे ऑब्जेक्ट के साथ इंटरैक्ट करता है जिसका तर्क परीक्षण के लिए महत्वपूर्ण है, और सिर्फ डेटा नहीं — spy का उपयोग करें। उदाहरण के लिए, AnalyticsTracker जो ईवेंट एकत्रित करता है और समय-समय पर भेजता है। परीक्षण यह सत्यापित करता है कि ईवेंट बफर में जोड़े गए, और भेजने के बाद बफर साफ हो गया। mock इसकी पुष्टि नहीं कर सकता क्योंकि यह ट्रैकर का वास्तविक तर्क नहीं चलाता।
| परिदृश्य | Spy | Mock |
|---|---|---|
| वास्तविक तर्क आवश्यक | हाँ | नहीं (stub) |
| कॉल सत्यापन | हाँ (संख्या, आर्गुमेंट्स) | हाँ (संख्या, आर्गुमेंट्स) |
| आंशिक stubbing | हाँ (कुछ मेथड spy, दूसरे stub) | नहीं (सभी मेथड stub) |
| दुष्प्रभावों का जोखिम | उच्च (वास्तविक कोड) | शून्य |
| गति | कम (वास्तविक तर्क) | अधिक (stub) |
| पढ़ने योग्यता | कम (यह समझना मुश्किल कि क्या वास्तविक है) | अधिक (सभी स्पष्ट है) |
सबकुछ के लिए spy — सभी परीक्षणों में mock के बजाय spy का उपयोग करना एक गलती है। spy वास्तविक कोड चलाता है जिसके दुष्प्रभाव हो सकते हैं: फ़ाइल में लिखना, HTTP भेजना, ग्लोबल स्थिति बदलना। यदि परीक्षित मॉड्यूल एक spy ऑब्जेक्ट की एक मेथड को कॉल करता है जो HTTP अनुरोध करता है, तो परीक्षण एक एकीकरण परीक्षण बन जाता है, न कि यूनिट परीक्षण। नियम: यदि spy एक ऐसे ऑब्जेक्ट को लपेटता है जिसमें I/O संचालन हैं — तो यह अब यूनिट परीक्षण नहीं रहा। I/O को अलग करने के लिए mock का उपयोग करें, और spy का उपयोग केवल बाहरी प्रभावों के बिना मेमरी में रहने वाले ऑब्जेक्ट्स के लिए करें।
Mockito.spy() — Java/Kotlin परियोजनाओं में जासूस बनाने का क्लासिक तरीका। spy() एक वास्तविक ऑब्जेक्ट लेता है और एक रॅपर लौटाता है। सभी कॉल डिफ़ॉल्ट रूप से वास्तविक ऑब्जेक्ट को दे दिए जाते हैं, और परिणाम रिकॉर्ड किए जाते हैं। परीक्षण के बाद, आप कॉल की संख्या और आर्गुमेंट्स की जाँच करने के लिए verify() का उपयोग कर सकते हैं। जो मेथड परीक्षण डेटा लौटाना चाहिए, उनके लिए doReturn/when का उपयोग किया जाता है — इसे «आंशिक मॉकिंग» कहा जाता है।
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() — कोरूटिन और sealed क्लास के बेहतर समर्थन के साथ Kotlin परियोजनाओं का एक विकल्प। MockK.spyk() एक जासूस बनाता है, जो Mockito.spy() के समान है। यह suspend फ़ंक्शन्स के लिए coVerify और आंशिक stubbing के लिए every का समर्थन करता है। Mockito के विपरीत, MockK final क्लासों के लिए spy का समर्थन नहीं करता (Kotlin में सभी क्लास डिफ़ॉल्ट रूप से final हैं) — आपको क्लास को खोलना (open) या इंटरफ़ेस का उपयोग करना होगा।
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)
}
}
आंशिक stubbing — एक शक्तिशाली लेकिन खतरनाक तकनीक। आप एक ऑब्जेक्ट का spy बना सकते हैं और केवल कुछ मेथडों को ओवरराइड (stub) कर सकते हैं, बाकी को वास्तविक छोड़ सकते हैं। उदाहरण: एक spy रिपॉजिटरी जहाँ getUser() परीक्षण डेटा लौटाता है, जबकि saveUser() वास्तव में एक in-memory सूची में संग्रहीत करता है। यह stub (नियंत्रित डेटा) और spy (वास्तविक तर्क) दोनों के लाभ को संयोजित करने की अनुमति देता है। नुकसान: परीक्षण की पढ़ने योग्यता खराब होती है — यह स्पष्ट नहीं होता कि कौन से मेथड वास्तविक हैं और कौन से stub।
Objective-C के लिए OCMock — एक लाइब्रेरी जो niceMock के माध्यम से spy ऑब्जेक्ट बनाने का समर्थन करती है। OCMock Objective-C runtime का उपयोग करके मेथड कॉल्स को इंटरसेप्ट करता है और उन्हें रिकॉर्ड करता है। परीक्षण के बाद verify कहा जाता है। OCMock किसी भी ऑब्जेक्ट के लिए spy का समर्थन करता है (Objective-C में सभी मेथड डायनेमिक हैं), जो Swift के मुकाबले में एक लाभ है, जहाँ spy केवल प्रोटोकॉल के माध्यम से ही संभव है।
// एक वास्तविक ऑब्जेक्ट के लिए spy बनाना
AnalyticsTracker *realTracker = [[AnalyticsTracker alloc] init];
AnalyticsTracker *spy = [OCMockObject partialMockForObject:realTracker];
// परीक्षण चलाना
[spy trackEvent:@"login"];
// सत्यापन
[[spy verify] trackEvent:@"login"];
XCTAssertEqual([realTracker eventCount], 1);
Swift प्रोटोकॉल-आधारित spy — Swift में Objective-C जैसा runtime प्रतिबिंब नहीं है, इसलिए spy मैनुअली बनाए जाते हैं। एक परीक्षण संरचना एक प्रोटोकॉल को लागू करती है और आंतरिक रूप से वास्तविक ऑब्जेक्ट को कॉल करते हुए एकसाथ कॉल रिकॉर्ड करती है। इसके लिए अधिक कोड की आवश्यकता होती है, लेकिन यह पूर्ण रूप से नियंत्रणयोग्य और टाइप-सुरक्षित है। मैनुअल जासूसों को बाहरी लाइब्रेरी की आवश्यकता नहीं होती और न ही वे runtime का उपयोग करते हैं — सब कुछ कंपाइल समय पर जांचा जाता है।
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 बनाम मैनुअल spy कब उपयोग करें — Objective-C कोड के लिए OCMock का उपयोग करें (कम boilerplate)। Swift के लिए प्रोटोकॉल के माध्यम से मैनुअल जासूसों को प्राथमिकता दी जाती है। एक मैनुअल spy कॉल रिकॉर्डिंग पर पूर्ण नियंत्रण देता है, इसे प्रतिबिंब की आवश्यकता नहीं होती और यह मान प्रकारों (structs) के साथ काम करता है। एकमात्र नुकसान: नए मेथड जोड़ते समय आपको spy क्लास कोड को प्रोटोकॉल के साथ संगतित रखना होगा।
एनलिटिक्स सत्यापन — जासूसों का सबसे आम उपयोग मामला। प्रोडक्शन कोड में, एनलिटिक्स कॉल पूरे एप्लिकेशन में फैले होते हैं: लॉगिन, लॉगआउट, खरीदारी, त्रुटि। परीक्षण AnalyticsTracker का एक spy रॅपर बनाता है, एक परिदृश्य (लॉगिन, उत्पाद देखें, कार्ट में जोड़ें, खरीदें) को निष्पादित करता है, और यह जाँचता है कि सभी आवश्यक ईवेंट सही क्रम में भेजे गए। mock उपयुक्त नहीं है क्योंकि AnalyticsTracker में बफरिंग और भेजने का तर्क है।
टाइमर और शेड्यूलर — Handler (Android) या Timer (iOS) का उपयोग करने वाले कोड का परीक्षण वास्तविक समय के कारण मुश्किल है। Scheduler का एक spy रॅपर रिकॉर्ड करता है कि कौन से कार्य निर्धारित किए गए और किस विलंब के साथ। परीक्षण वास्तविक Handler का spy बनाता है, एक कार्रवाई करता है, और जाँचता है कि Handler.postDelayed(runnable, delay) को सही विलंब के साथ बुलाया गया था। वास्तविक कार्य निष्पादित नहीं होता — spy कॉल को इंटरसेप्ट करता है और रिकॉर्ड करता है।
लॉगिंग और डिबग जानकारी — प्रोडक्शन में, लॉग बंद हो सकते हैं या फ़ाइल में लिखे जा सकते हैं। Logger का एक spy रॅपर सभी संदेशों को एक in-memory सूची में रिकॉर्ड करता है जिसे परीक्षण निष्पादन के बाद जाँचता है। यह यह सत्यापित करने की अनुमति देता है कि त्रुटि पर सही संदेश लिखा गया है, बिना कंसोल को अव्यवस्थित किए। Logger के लिए मैनुअल जासूस iOS पर विशेष रूप से उपयोगी हैं, जहाँ OSLog के पास कोई परीक्षण API नहीं है।
कॉल क्रम सत्यापन — कुछ परिदृश्यों को संचालनों के सही क्रम की आवश्यकता होती है: कनेक्शन खोलें, डेटा भेजें, कनेक्शन बंद करें। Mockito InOrder.verify() के माध्यम से क्रम की जाँच की अनुमति देता है। spy भी ऐसा ही करता है, लेकिन वास्तविक निष्पादन को संरक्षित रखता है। यदि क्रम और प्रत्येक चरण का परिणाम (कनेक्शन वास्तव में खुला) दोनों महत्वपूर्ण हैं — spy का उपयोग करें, mock का नहीं।
अक्सर पूछे जाने वाले प्रश्न
Spy एक वास्तविक ऑब्जेक्ट को लपेटता है और उसका तर्क चलाता है, इसके साथ ही कॉल रिकॉर्ड करता है। Mock ऑब्जेक्ट को पूरी तरह से stub से बदल देता है — कोई वास्तविक तर्क नहीं चलता। spy व्यवहार को संरक्षित रखता है, mock नहीं रखता। spy चुनें जब ऑब्जेक्ट का वास्तविक कार्य महत्वपूर्ण हो; mock चुनें जब आपको परीक्षण को बाहरी निर्भरता से अलग करने की आवश्यकता हो।
जब spy रॅपर वास्तविक I/O संचालनों को जन्म देता है। यदि spy एक ऐसे ऑब्जेक्ट को लपेटता है जो फ़ाइल में लिखता है, HTTP भेजता है, या डिस्क से पढ़ता है — तो परीक्षण यूनिट परीक्षण नहीं रहता। दूसरा मामला: परीक्षण कॉल में दिलचस्पी किए बिना केवल वापसी मान की जाँच करता है — यहाँ stub पर्याप्त है, और spy अनावश्यक है। तीसरा: कोड spy की आंतरिक स्थिति पर निर्भर करता है — यह एक नाज़ुक परीक्षण है।
हाँ, spyk() के माध्यम से — Mockito.spy() का समान। MockK.spyk() वास्तविक ऑब्जेक्ट के चारों ओर एक जासूस बनाता है, आंशिक stubbing के लिए every और suspend फ़ंक्शन्स के लिए coVerify/coroutinesVerify का समर्थन करता है। सीमा: final क्लासों के साथ काम नहीं करता (open या interface की आवश्यकता है)। Java क्लासों के लिए, MockK spyk() का भी समर्थन करता है लेकिन @MockKJvmInline एनोटेशन की आवश्यकता होती है।
तकनीकी रूप से, नहीं। Mock एक stub है जिसमें कोई वास्तविक कार्यान्वयन नहीं है। Spy, परिभाषा के अनुसार, एक वास्तविक ऑब्जेक्ट को लपेटता है। Mockito में, आप mock को spy में नहीं बदल सकते। लेकिन आप उलटा कर सकते हैं: एक spy बनाएं और doReturn/when (आंशिक mocking) के माध्यम से कुछ मेथडों को ओवरराइड करें। यह spy ऑब्जेक्ट के चुने हुए मेथडों के लिए mock जैसा व्यवहार देता है।
हाँ, आवश्यक है। Swift में Java/Kotlin जैसी गतिशील प्रॉक्सींग नहीं है। spy बनाने के लिए एक प्रोटोकॉल की आवश्यकता है जिसे प्रोडक्शन क्लास और spy क्लास दोनों लागू करते हैं। Swift प्रोटोकॉल-आधारित spy एक मैनुअल कार्यान्वयन है जो एक वास्तविक ऑब्जेक्ट लेता है, उसे कॉल सौंपता है, और मेटाडेटा रिकॉर्ड करता है। विकल्प: Cuckoo लाइब्रेरी, जो SourceKit के माध्यम से spy क्लास जनरेट करती है।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें