Siamak Mahmoudi द्वारा
TDD, या टेस्ट-ड्रिवन डेवलपमेंट, एक सॉफ्टवेयर डेवलपमेंट दृष्टिकोण है जहां वास्तविक कोड लागू होने से पहले टेस्ट लिखे जाते हैं।
इसके लिए प्रोजेक्ट/फीचर की आवश्यकताओं में "क्या" और "कैसे" की स्पष्ट समझ की आवश्यकता होती है।
TDD कम, लेकिन पर्याप्त कोड लिखने में मदद करता है। यह सामान्य सॉफ्टवेयर डेवलपमेंट गलतियों को रोकने में मदद करता है, जैसे ओवर-इंजीनियरिंग, बहुत अधिक टेस्ट कवरेज, मुख्य आवश्यकताओं को भूल जाना, बहुत बड़े फंक्शन और क्लासेस, और बहुत जटिल कोड स्टेटमेंट्स।
कुल मिलाकर, यह एक संक्षिप्त, पहले से यूनिट-टेस्ट कवर, क्लीन कोडबेस रखने में मदद करता है। समय के साथ, यह डेवलपमेंट और कोड रखरखाव लागत भी बचाता है।
इस लेख में हम TDD को एक्शन में चर्चा करेंगे।
संदर्भ Android डेवलपमेंट एनवायरनमेंट है, इसलिए हम एक नमूना प्रोजेक्ट के साथ चरणों को प्रदर्शित करने के लिए Kotlin और JUnit5 का उपयोग करेंगे।
हालांकि, यहां दिए गए निर्देश और तकनीकों का अभ्यास अन्य प्रोग्रामिंग भाषाओं में भी किया जा सकता है।
पूर्वापेक्षाएँ
- Kotlin का बुनियादी ज्ञान
- यूनिट टेस्ट लिखने का बुनियादी ज्ञान
- मॉक्स और एसर्शन का ज्ञान
हम Kotlin को प्रोग्रामिंग भाषा के रूप में और JUnit5 का उपयोग यूनिट टेस्ट लिखने के लिए करेंगे।
Mockito का उपयोग मॉक्स और स्पाई के साथ काम करने के लिए किया जाएगा।
लक्षित दर्शक किसी भी प्लेटफॉर्म से कोई भी सॉफ्टवेयर डेवलपर है जो अपने करियर में एक नया अध्याय चाहता है।
भले ही संदर्भ Android है, सामग्री प्लेटफ़ॉर्म-विशिष्ट गुणों के बारे में बात नहीं करती। इसके बजाय, हम TDD के साथ विकसित करते समय तकनीकों, नोट्स और चुनौतियों पर ध्यान केंद्रित करते हैं।
यदि उपरोक्त आपके लिए ठीक है, तो आइए शुरू करें।
टेस्ट ड्रिवन डेवलपमेंट कैसे काम करता है
TDD चक्र
डेवलपमेंट प्रक्रिया एक चक्र का पालन करती है:
- एक फेलिंग टेस्ट लिखना (गुलाबी वर्ग)।
- टेस्ट पास करने के लिए कोड लागू करना (हरा वर्ग)
- कोड को रीफैक्टर करना (नीला वर्ग) आवश्यकतानुसार जबकि यह सुनिश्चित करते हुए कि टेस्ट पास होते रहें (हल्का हरा वर्ग)।
- एक नया फेलिंग टेस्ट लिखना (प्रवाह को फिर से शुरू करें)
फेलिंग टेस्ट लिखना (गुलाबी वर्ग)
इस चरण में, आप यह वर्णन करके शुरू करते हैं कि आप अपने कोड को क्या करना चाहते हैं।
कल्पना करें कि आप अपने कोड को यह जांचने के लिए एक टेस्ट दे रहे हैं कि क्या यह सही व्यवहार करता है। यह टेस्ट आपके कोड से पूछे गए एक प्रश्न की तरह है, जैसे "क्या आप यह कार्य कर सकते हैं?"
पहले, आपका कोड उत्तर नहीं जानता, इसलिए आप एक टेस्ट लिखते हैं जो विफल होना चाहिए क्योंकि आपका कोड अभी तक नहीं जानता कि कार्य कैसे करना है। यह फेलिंग टेस्ट एक गुलाबी चेतावनी संकेत की तरह है जो आपको बताता है कि कुछ सही नहीं है।
एक बार जब आप इस चरण को समाप्त कर लेते हैं, तो JUnit5 आपके द्वारा बनाए गए टेस्ट से एक व्यापक रिपोर्ट उत्पन्न करेगा। ये टेस्ट आपके काम के एक ठोस प्रतिनिधित्व के रूप में खड़े होंगे।
अब, कल्पना करें कि आपका प्रोजेक्ट मैनेजर इन टेस्ट केस को पढ़ रहा है ताकि उनके कवरेज और फीचर या उत्पाद पर आपकी समझ की सटीकता का आकलन कर सके। इस परिप्रेक्ष्य को अपनाने से इस विकासात्मक चरण के महत्व की स्पष्ट समझ मिलती है।
तकनीकी जटिलताओं से सॉफ्टवेयर के व्यवहार की ओर अपना ध्यान केंद्रित करें।
तकनीकी पहलुओं की बारीकियों में उलझने के बजाय, अपना ध्यान इस बात पर केंद्रित करें कि सॉफ्टवेयर कैसे कार्य करता है और उपयोगकर्ताओं और अन्य घटकों के साथ कैसे इंटरैक्ट करता है।
परिप्रेक्ष्य में यह बदलाव आपको सॉफ्टवेयर के इच्छित कार्यों और परिणामों को प्राथमिकता देने की अनुमति देता है, जिससे टेस्ट बनते हैं जो इसके वास्तविक-विश्व व्यवहार को सटीक रूप से दर्शाते हैं।
व्यवहार पर ध्यान केंद्रित करके तकनीकी बारीकियों के बजाय, आप यह सुनिश्चित करते हैं कि आपके टेस्ट सॉफ्टवेयर के उद्देश्य और उपयोगकर्ता अपेक्षाओं के साथ निकटता से संरेखित हैं।
कुछ मामलों में, आप प्रति घटक कुछ टेस्ट केस के साथ समाप्त हो सकते हैं (जो उद्देश्य है: कम काम लेकिन लक्षित) और यह पूरी तरह से ठीक है, जब तक आप प्रोजेक्ट की सभी व्यवहारिक आवश्यकताओं को कवर करते हैं।
सुझाव
- विशिष्ट बनें: स्पष्ट और विशिष्ट टेस्ट केस लिखें जो आपके कोड के व्यवहार के एक पहलू पर ध्यान केंद्रित करें।
- सरल से शुरू करें: सबसे सरल टेस्ट केस से शुरू करें जो आपके द्वारा आवश्यक बुनियादी कार्यक्षमता को कवर करता है।
@Test fun `a sum is calculated from two input numbers`() {}
- सार्थक नामों का उपयोग करें: अपने टेस्ट का वर्णनात्मक नाम रखें ताकि कोई भी उन्हें पढ़कर जान सके कि टेस्ट क्या जांच रहा है।
@Test fun `Font Ratio is fetched from data source INITIALLY`() {}
बचने के लिए सामान्य गलतियां
- एक साथ बहुत अधिक टेस्ट करना: एक ही टेस्ट में कई चीजों का टेस्ट करने से बचें। इससे यह पहचानना कठिन हो सकता है कि क्या विफल हो रहा है।
// ऐसा न करें @Test fun `pixelSize fits the standart sizes while fontSize is bigger than minumum supported font size but matches the list of special levels of size`() {}
- कार्यान्वयन विवरण पर भरोसा करना: ऐसे टेस्ट न लिखें जो कोड के आंतरिक कामकाज से कसकर जुड़े हों। टेस्ट को व्यवहार पर ध्यान केंद्रित करना चाहिए, कार्यान्वयन पर नहीं।
// ऐसा न करें @Test fun `pixelSize is Long and Non-Null and fits the standart sizes then calculated font size is non-null and of type Dimention`() {}
टेस्ट पास करने के लिए कोड लागू करना (हरा वर्ग)
अब जब आपके पास टेस्ट है, तो आपके कोड को सिखाने का समय है कि कार्य को सही तरीके से कैसे करना है।
आप वास्तविक कोड लिखते हैं जो टेस्ट को पास कराना चाहिए और आपका कोड प्रश्न का सही उत्तर दे सके।
जब आपका कोड टेस्ट पास करता है, तो यह एक हरी बत्ती की तरह है जो कहती है, "हाँ, मैं अब कार्य कर सकता हूं!"।
यह चरण यह सुनिश्चित करने के बारे में है कि आपका कोड आपके द्वारा पूछी गई समस्या को समझता है और हल कर सकता है।
सुझाव
- न्यूनतम कोड लिखें: सबसे सरल कोड लिखें जो फेलिंग टेस्ट को पास कराता है। आप यहां ओवर-इंजीनियरिंग से बचने के बारे में अधिक पढ़ सकते हैं।
// Test Case @Test fun `Storage stores font ratio in key-value`() { // Given val fontRatio = 2.0f val mockEditor = mockk<SharedPreferences.Editor>(relaxed = true) every { mockSharedPreference.edit() } returns mockEditor every { mockEditor.putFloat(any(), any()) } returns mockEditor every { mockEditor.apply() } just Runs // When storage.saveFontRatio(fontRatio) // Then verify(exactly = 1) { mockEditor.apply() } } // Correct Implementation - Avoid extra implementation class SharedPreferenceHelper( private val sharedPreferences: SharedPreferences ) { fun saveFontRatio(fontRatio: Float) { sharedPreferences.edit().putFloat("font-ratio", fontRatio).apply() } } // Wrong Implementation class SharedPreferenceHelper( private val sharedPreferences: SharedPreferences ) { fun saveFontRatio(fontRatio: Float) { if (fontRatio <= 0.0f) throw IllegalArgumentException("Font ratio must be greater than 0.0f") storeValue(key = FONT_RATIO_KEY, value = fontRatio) } private fun storeValue(key: String, value: Float){ val editor = sharedPreferences.edit() editor.putFloat(key, value) editor.apply() } fun getFontRatio(): Float { return sharedPreferences.getFloat("font_ratio", 1.0f) } }
- डुप्लीकेशन से बचें: कोड दोहराएं नहीं। यदि आप खुद को कई स्थानों पर समान लॉजिक लिखते हुए पाते हैं, तो रीफैक्टरिंग पर विचार करें। यह प्राथमिक कोड सुधार इस चरण में किए जा सकते हैं, लेकिन यदि संशोधन का दुष्प्रभाव हो सकता है तो इसे नजरअंदाज करें।
class ... { override fun getDefaultFontSize(): Float { val zoomRatio = DEFAULT_SSPEED * DEFAULT_FONT_RATIO / deviceDensity val fontSize = zoomRatio * standardFontSize return fontSize } override fun getFontSizeBySSpeed(speed: Int): Float { val zoomRatio = speed * DEFAULT_FONT_RATIO / deviceDensity val fontSize = zoomRatio * standardFontSize return fontSize } }
class ... { override fun getDefaultFontSize(): Float = calculate(DEFAULT_AGE) override fun getFontSizeByAge(age: Int): Float = calculate(age) private fun calculate(age: Int): Float { val zoomRatio = age * DEFAULT_FONT_RATIO / deviceDensity val fontSize = zoomRatio * standardFontSize return fontSize } }
बचने के लिए सामान्य गलतियां:
- आगे कूदना: टेस्ट पास करने के लिए आवश्यकता से अधिक कोड न लिखें। TDD वृद्धिशील डेवलपमेंट के बारे में है। TDD डेवलपमेंट के लिए एक वृद्धिशील और चरण-दर-चरण दृष्टिकोण को प्रोत्साहित करता है। जब आप आगे कूदते हैं, तो आप अनिवार्य रूप से उन समस्याओं को हल करने की कोशिश कर रहे हैं जो अभी तक आपके द्वारा काम किए जा रहे वर्तमान टेस्ट से सीधे संबंधित नहीं हैं। प्राथमिक लक्ष्य वर्तमान कार्य पर ध्यान केंद्रित करना है - वर्तमान टेस्ट पास करना - बिना भविष्य की कार्यक्षमताओं से विचलित हुए।
- टेस्ट विफलताओं को नजरअंदाज करना: यदि कोई टेस्ट शुरू में विफल नहीं होता है, तो आप एक महत्वपूर्ण केस को भूल सकते हैं। यह पहली नज़र में असंभव लग सकता है, लेकिन आपके टेस्ट कंपोनेंट्स में कुछ डेवलपमेंट के बाद आप एक ही मेथड के विभिन्न पहलुओं का टेस्ट करने के लिए कई टेस्ट लिखना शुरू कर देंगे। यह वह जगह है जहां आपको खुश नहीं होना चाहिए यदि आपका अनिम्प्लिमेंटेड लॉजिक टेस्ट पास करता है। सरल शब्दों में, यह है कि आप डेवलपमेंट चरण में बग कैसे पकड़ते हैं। इसलिए, जब यह होना चाहिए तो विफलता की अपेक्षा करें।
कोड रीफैक्टर करना (नीला वर्ग) और टेस्ट सफलता सुनिश्चित करना (हल्का हरा वर्ग)
एक बार जब आपका कोड टेस्ट पास कर लेता है, तो चीजों को साफ करने का समय आ जाता है।
आप अपने कोड को अधिक व्यवस्थित, समझने में आसान, या यहां तक कि तेज बनाने के तरीके देख सकते हैं। इसे अपने कमरे को साफ करने के रूप में सोचें एक बार जब आप खेलना समाप्त कर लेते हैं, खेलना समाप्त करने के बाद सब कुछ साफ और व्यवस्थित करना। आप अपने कोड को बदले बिना सुधारते हैं।
जैसे ही आप ऐसा करते हैं, आप अपने सभी टेस्ट चलाते रहते हैं ताकि यह सुनिश्चित हो सके कि वे अभी भी पास होते हैं। यदि इस चरण के दौरान कोई टेस्ट विफल होता है, तो यह एक हल्के हरे सावधानी संकेत की तरह है जो आपको बता रहा है कि आपने जो साफ किया वह गलती से कोड को तोड़ सकता है।
आप इस भाग को एक अलग कोड रखरखाव चरण के रूप में मान सकते हैं।
मान लें कि आपको पुराने कोड को साफ करने और यह सुनिश्चित करने का कार्य दिया गया है कि यह टीम कोड गुणवत्ता दिशानिर्देशों के साथ-साथ उत्पाद आवश्यकताओं का पालन करता है।
इस प्रक्रिया के दौरान, कुंजी एक सावधानीपूर्वक संतुलन बनाए रखना है - परिष्कृत करते हुए यह सुनिश्चित करते हुए कि आपके टेस्ट सफल होते रहें।
रीफैक्टरिंग चरण के लिए कुछ विचार और रणनीतियाँ यहां दी गई हैं:
- कोड स्पष्टता: जटिल वर्गों को सरल बनाएं, अस्पष्ट वेरिएबल नामों को बदलें, और कोड को दूसरों (और आपके भविष्य के स्वयं) के लिए समझने में आसान बनाने के लिए टिप्पणियों को बढ़ाएं।
- मॉड्यूलैरिटी: बड़े फंक्शन को छोटे, केंद्रित फंक्शन में तोड़ें। यह आपके कोड को अधिक मॉड्यूलर बनाता है और आसान रखरखाव और टेस्टिंग की अनुमति देता है।
- रिडंडेंसी हटाएं: डुप्लिकेट कोड की पहचान करें और इसे पुन: प्रयोज्य फंक्शन या क्लास में समेकित करें। यह पुनरावृत्ति को खत्म करने और स्थिरता सुनिश्चित करने में मदद करता है।
- ऑप्टिमाइज़ेशन: उन क्षेत्रों की पहचान करें जहां प्रदर्शन में सुधार किया जा सकता है। हालांकि, केवल तभी ऑप्टिमाइज़ करें यदि आपके पास विशिष्ट प्रदर्शन लक्ष्य हैं और सबूत हैं कि कोड एक बॉटलनेक है। यहां ऑप्टिमाइज़ेशन संसाधन निकास से बचने के लिए है न कि कोड को प्रदर्शनकारी बनाने के लिए।
- सुसंगत फॉर्मेटिंग: अपने टीम या प्रोजेक्ट के कन्वेंशन का पालन करते हुए एक सुसंगत कोड स्टाइल बनाए रखें।
- अनुपयोगी कोड: किसी भी अनुपयोगी वेरिएबल, फंक्शन, या आयात को हटा दें जो कोडबेस को अव्यवस्थित करते हैं।
- टेस्ट सुधार: TDD के बारे में सामान्य धारणा के विपरीत, जब भी आवश्यकता हो आप टेस्ट जोड़ सकते हैं। उन परिदृश्यों को कवर करने के लिए नए टेस्ट केस जोड़कर टेस्ट सूट को बढ़ाएं जिन्हें पहले संबोधित नहीं किया गया था। यह व्यापक टेस्ट कवरेज बनाए रखने में मदद करता है।
- डॉक्यूमेंटेशन: यदि आपके कोड का उद्देश्य कोड से तुरंत स्पष्ट नहीं है, तो इसके इरादे और उपयोग को समझाने के लिए डॉक्यूमेंटेशन जोड़ने या सुधारने पर विचार करें। इसे आदत न बनाएं। यह महत्वपूर्ण मामलों के लिए भ्रम से बचने के लिए एक पूरक व्याख्या के रूप में कार्य करने के लिए है।
ध्यान दें कि TDD कोड आत्म-अभिव्यंजक होना चाहिए और डॉक्यूमेंटेशन से स्वतंत्र होना चाहिए।
याद रखें, रीफैक्टरिंग करते समय, यह सुनिश्चित करने के लिए अपने सभी टेस्ट चलाते रहना महत्वपूर्ण है कि वे पास होते रहें।
सुझाव
- टेस्ट को व्यापक रखें: सुनिश्चित करें कि आपके टेस्ट विभिन्न परिदृश्यों को कवर करते हैं ताकि रीफैक्टरिंग के दौरान अनपेक्षित दुष्प्रभावों को पकड़ा जा सके।
- धीरे-धीरे रीफैक्टर करें: अपने कोड में छोटे बदलाव करें और प्रतिगमन को जल्दी पकड़ने के लिए टेस्ट को बार-बार चलाएं।
बचने के लिए सामान्य गलतियां:
- टेस्ट के बिना रीफैक्टरिंग: टेस्ट के बिना रीफैक्टरिंग अप्रत्याशित व्यवहार का कारण बन सकती है। यदि लॉजिक का एक हिस्सा छूटने का मौका है तो इसके लिए टेस्ट लिखने पर विचार करें।
- बड़ा कोड परिवर्तन: कभी-कभी हम टेस्ट पास करने के लिए विकसित की गई लाइनों से अधिक संख्या में लाइनों को बदल देते हैं। डेवलपमेंट चरण में बहुत अधिक परिवर्तन करने के बजाय हमेशा एक अलग रीफैक्टरिंग चरण पर विचार करें क्योंकि यह एक सुरक्षित और कम खर्चीला विकल्प है।
एक नया फेलिंग टेस्ट लिखना (प्रवाह को पुनः आरंभ करना)
अब, आप अगली चीज के बारे में सोचते हैं जो आप अपने कोड से करवाना चाहते हैं।
आप एक नया टेस्ट लिखकर शुरू करते हैं जो विफल होना चाहिए क्योंकि आपका कोड अभी तक नया कार्य करना नहीं जानता। यह आपके कोड को हल करने के लिए एक नई चुनौती देने जैसा है।
फिर, आप पूरे चक्र को दोहराते हैं: कोड के साथ टेस्ट पास कराएं (हरा वर्ग), यदि आवश्यक हो तो साफ करें (नीला वर्ग), और यह सुनिश्चित करने के लिए टेस्ट करते रहें कि सब कुछ काम करता है (हल्का हरा वर्ग)।
इस तरह, आप हमेशा आगे बढ़ रहे हैं और अपने कोड को चरण-दर-चरण बना रहे हैं।
सुझाव
- वृद्धिशील चरण: एक स्पष्ट डेवलपमेंट पथ बनाए रखने के लिए नई कार्यक्षमता के लिए छोटे वृद्धिशील में नए टेस्ट जोड़ें। एक जटिल फीचर को एक बार में लागू करने का प्रयास करने के बजाय, आप इसे छोटे, प्रबंधनीय टुकड़ों में तोड़ते हैं और इनमें से प्रत्येक टुकड़े के लिए टेस्ट बनाते हैं। यह दृष्टिकोण एक स्पष्ट और स्थिर डेवलपमेंट पथ बनाए रखता है, जिससे आपको केंद्रित रहने, जोखिम कम करने, और यह सुनिश्चित करने में मदद मिलती है कि आपके सॉफ्टवेयर में प्रत्येक जोड़ को अच्छी तरह से टेस्ट किया गया है।
- फीडबैक लूप: अपने कार्यान्वयन को मार्गदर्शन करने के लिए फेलिंग टेस्ट लिखने से फीडबैक का उपयोग करें। फीडबैक लूप TDD की पुनरावृत्त प्रकृति को उजागर करता है। जैसे ही आप नए टेस्ट बनाते हैं और उन्हें विफल होते हुए देखते हैं, आप मूल्यवान अंतर्दृष्टि प्राप्त करते हैं जो आपके कार्यान्वयन का मार्गदर्शन करती है।
यहां बताया गया है कि फीडबैक लूप कैसे काम करता है:
- अपेक्षा सेट करना: जब आप एक नया टेस्ट लिखते हैं, तो आप परिभाषित करते हैं कि कोड को कैसे व्यवहार करना चाहिए इसके लिए आपकी अपेक्षाएं। यह स्पष्ट करता है कि आप अपनी नई सुविधा के साथ क्या हासिल करना चाहते हैं।
- प्रारंभिक विफलता: टेस्ट पहले विफल होता है क्योंकि इसकी अपेक्षाओं को पूरा करने वाला संबंधित कोड गायब या अधूरा है। यह प्रारंभिक विफलता TDD प्रक्रिया का एक स्वाभाविक हिस्सा है।
- आपके कार्यान्वयन का मार्गदर्शन करता है: टेस्ट की विफलता से फीडबैक आपको उस दिशा में इंगित करता है जहां कोड लिखने या संशोधित करने की आवश्यकता है। यह आपके डेवलपमेंट के लिए एक रोडमैप बन जाता है, यह रूपरेखा करता है कि नई कार्यक्षमता कैसी दिखनी चाहिए।
- वृद्धिशील प्रगति: जैसे ही आप टेस्ट पास करने के लिए आवश्यक कोड लागू करते हैं, आप वांछित कार्यक्षमता को वृद्धिशील रूप से बना रहे हैं। प्रत्येक चरण फेलिंग टेस्ट द्वारा प्रदान की गई फीडबैक द्वारा निर्देशित है।
- सत्यापन: एक बार जब आपका कार्यान्वयन पूरा हो जाता है, तो आप टेस्ट को फिर से चलाते हैं। यदि यह पास होता है, तो यह सत्यापित करता है कि आपका नया कोड उन अपेक्षाओं को पूरा करता है जो आपने शुरू में निर्धारित की थीं।
फीडबैक लूप यह सुनिश्चित करता है कि आपका डेवलपमेंट आपके सॉफ्टवेयर के इच्छित लक्ष्यों के साथ कसकर संरेखित है।
बचने के लिए सामान्य गलतियां:
- कार्यान्वयन के बाद टेस्ट लिखना: आपके द्वारा फीचर लागू करने के बाद टेस्ट न लिखें। TDD टेस्ट पहले लिखने के बारे में है। लॉजिक का एक छोटा सा टुकड़ा टेस्ट कोड से पहले जोड़ा गया भी संसाधन बर्बादी/बग का संभावित स्रोत है। पूरा बिंदु यह है कि कोई लॉजिक न जोड़ें जब तक कि टेस्ट सूट से इसकी आवश्यकता न हो।
- फेलिंग टेस्ट को छोड़ना: इस चरण को न छोड़ें भले ही आपको लगता है कि आप फीचर को कैसे लागू करना जानते हैं।
यहां बताया गया है कि आपको फेलिंग टेस्ट चरण को नहीं छोड़ना चाहिए भले ही आप आश्वस्त हों:
- इरादे की स्पष्टता: एक फेलिंग टेस्ट लिखना फीचर के लिए आपके इरादे को स्पष्ट करता है। यह आपको कार्यान्वयन में गोता लगाने से पहले आपके द्वारा लक्षित सटीक व्यवहार और परिणामों पर विचार करने के लिए मजबूर करता है।
- मान्यताओं का सत्यापन: भले ही आपको लगता है कि आप फीचर को समझते हैं, एक टेस्ट बनाना यह सुनिश्चित करता है कि आपकी मान्यताएं वैध हैं। आपकी समझ सही हो सकती है, लेकिन टेस्ट इसे मान्य करता है।
- सेफ्टी नेट: एक फेलिंग टेस्ट लिखकर, आप एक सेफ्टी नेट स्थापित करते हैं जो भविष्य में प्रतिगमन को रोकता है। यह फीचर के लिए एक विनिर्देश के रूप में कार्य करता है और अनपेक्षित दुष्प्रभावों को पकड़ने में मदद करता है।
- वृद्धिशील डेवलपमेंट: TDD वृद्धिशील डेवलपमेंट को प्रोत्साहित करता है। प्रत्येक नई सुविधा चरण-दर-चरण बनाई जाती है, फेलिंग टेस्ट से वर्किंग इम्प्लिमेंटेशन तक एक स्पष्ट प्रगति के साथ। इस चरण को छोड़ने से वह प्रगति बाधित होती है।
- डॉक्यूमेंटेशन: फेलिंग टेस्ट फीचर के अपेक्षित व्यवहार को डॉक्यूमेंट करता है। यह डॉक्यूमेंटेशन आपके और आपकी टीम के लिए मूल्यवान है, खासकर जब भविष्य में कोड को फिर से देखा जाता है। हमेशा याद रखें कि आपके सभी टेस्ट कोड को सूचीबद्ध करके प्रोडक्ट मैनेजर्स और QAs के लिए रिपोर्ट उत्पन्न करने के लिए सिस्टम हैं। उन रिपोर्टों में आपके द्वारा उत्पाद में देखे गए विवरण उजागर होते हैं, इसलिए, उन्हें समझाने की कोशिश करें कि आपने बिंदु को पूरी तरह से समझा है।
TDD का उपयोग करके कैसे विकसित करें
TDD सॉफ्टवेयर के डिजाइन और डेवलपमेंट को चलाने के लिए स्वचालित टेस्ट लिखने के महत्व पर जोर देता है। यह कोड की ओर ले जाता है जो अधिक विश्वसनीय, रखरखाव योग्य, और समय के साथ बदलने में आसान होता है।
लेकिन हम इसे व्यवहार में कैसे ला सकते हैं? इसे आजमाकर और धीरे-धीरे इसकी आदत डालकर।
आइए एक नई सुविधा विकसित करते हुए TDD को आजमाएं ताकि यह प्रदर्शित किया जा सके कि हम वास्तविक दुनिया में इसका उपयोग कैसे शुरू कर सकते हैं।
हम एक फ़ॉन्ट साइज़ ऑटो सेटिंग फीचर लागू करेंगे।
हमारे पास एक न्यूज़ ऐप है और एक उपयोगकर्ता न्यूज़ फीड के लिए ऑटो-स्क्रॉल-स्पीड सेट कर सकता है।
हम एक ऐसी सुविधा लागू करना चाहते हैं जो यूजर प्रोफाइल पेज में सेट स्क्रॉल स्पीड से स्क्रीन फ़ॉन्ट साइज़ को समायोजित करती है।
यदि उपयोगकर्ता स्क्रॉल स्पीड को 0 से 1 पर सेट करता है, तो फ़ॉन्ट साइज़ 1.3 से बढ़नी चाहिए।
1 से अधिक कोई भी स्क्रॉल स्पीड वृद्धि फ़ॉन्ट साइज़ में 1.2 की वृद्धि का परिणाम देगी।
यह सुविधा उपयोगकर्ताओं को समाचार पढ़ते समय बेहतर अनुभव प्राप्त करने की अनुमति देती है।
मैंने इस GitHub रिपॉजिटरी में हमारे द्वारा खोजे गए कोड को भी साझा किया है।
इसे क्लोन करने और इसके साथ खेलने के लिए स्वतंत्र महसूस करें।
जैसे-जैसे हम डेवलपमेंट में आगे बढ़ते हैं, चरणों का पालन करने का प्रयास करें। यह आपको TDD के संदर्भ में तकनीकों और सोचने के तरीके को व्यावहारिक रूप से समझने में मदद करेगा।
तो, Android Studio खोलें और एक नया प्रोजेक्ट बनाएं।
फ़ॉन्ट साइज़ ऑटो सेटिंग फीचर डेटा फ्लो चार्ट
एक Android ऐप में नमूना सुविधा का DFD
ऊपर वह फ्लोचार्ट है जो अंत में डेटा फ्लो कैसा दिखना चाहिए।
यहां प्रत्येक आकर्षक घटक की रूपरेखा दी गई है:
AutoScrollSettingsUseCase क्लास चयनित स्क्रॉल स्पीड के आधार पर FontRatio की गणना और स्टोर करने के लिए लॉजिक को संभालेगा।
इस यूज़ केस में UserRepository पर एक डिपेंडेंसी होगी जो FontRatio मान को स्टोर करता है।
UserRepository में, Storage मैकेनिज्म का उपयोग करके FontRatio मान को स्टोर और पुनर्प्राप्त करने के तरीके हैं। जब भी एक नया FontRatio स्टोरेज को भेजा जाता है, तो कोई भी ऑब्जर्वेबल नवीनतम मान के साथ एक उत्सर्जन प्राप्त करेगा।
UserProfileViewModel में, AutoScrollSettingsUseCase का एक इंस्टेंस है जिसे जब भी उपयोगकर्ता स्क्रॉल स्पीड अपडेट करता है तो बुलाया जाता है। यह FontRatio की पुनर्गणना को ट्रिगर करेगा और इसे रिपॉजिटरी के माध्यम से संग्रहीत किया जाएगा।
यूजर सेटिंग सेक्शन में आवश्यक UI कंपोनेंट होंगे जो उपयोगकर्ता को उनकी वांछित स्क्रॉल स्पीड दर्ज करने की अनुमति देंगे। यह मानक Android UI एलिमेंट्स जैसे NumberPicker या कस्टम UI कंपोनेंट्स का उपयोग करके किया जा सकता है (हम इन भागों पर चर्चा नहीं करेंगे)।
यह एक सरल सुविधा के लिए ब्रेकडाउन है और हर बार जब आप इससे गुजरते हैं, तो यह और स्पष्ट हो जाता है कि चरण और अंतिम परिणाम क्या हैं। यह आपके परिवर्तनों के लिए करना महत्वपूर्ण है।
टेस्ट कैसे लिखें
पहला चरण हमेशा टेस्ट क्लास को स्वयं बनाना है। इस मामले में, हमारे पास कम से कम निम्नलिखित टेस्ट क्लास होंगी:
UserRepositoryTestAutoScrollSettingTestUserSettingsViewModelTest
मैं ViewModel भाग से शुरू करना पसंद करता हूं।
एक ViewModel एक Android आर्किटेक्चरल कंपोनेंट है जो लाइफसाइकिल परिवर्तनों (जैसे फोरग्राउंड, बैकग्राउंड, फोकस्ड) से बचता है। तो, यह हमारे स्टेट्स को स्टोर करने के लिए एक अच्छी जगह है।
आइए सोर्स कोड के समान पैकेज पथ का पालन करते हुए unitTest डायरेक्टरी के अंदर टेस्ट फाइल बनाएं।
TDD व्यवहार में लीगेसी वर्क डेवलपमेंट से भिन्न है।
TDD के साथ हम फाइल और प्रॉपर्टी (फील्ड्स) निर्माण प्रक्रिया को बढ़ावा देने के लिए एक IDE का उपयोग करते हैं। लेकिन हम टेस्ट फाइलें मैन्युअली बनाते हैं! कुछ कोशिशों के बाद, IDE का यह पहलू आसान हो जाएगा।
कोड संरचना (पैकेज) बनाएं और फिर पैकेज पर राइट क्लिक करके और Class प्रकार चुनकर अपनी टेस्ट क्लास बनाएं।
इसके लिए एक वर्णनात्मक नाम चुनें (शायद यदि आप पहले से नहीं कर रहे हैं तो इसके लिए एक कन्वेंशन का पालन करना चाहिए)।
उदाहरण के लिए: xxx is tested for, जहां xxx टेस्ट किए गए घटक का नाम है।
फाइल बनाने के लिए IDE का उपयोग करें
अब, आइए खाली टेस्ट बनाएं। जितना संभव हो उतना व्यापक होने का प्रयास करें।
आरेख के अनुसार, इस सुविधा के लिए हमारे पास ज्यादा लॉजिक नहीं होगा।
यूनिट टेस्ट फंक्शन लिखने के लिए दो मुख्य रणनीतियाँ हैं:
- AAA
- Given/When/Then
@Test fun `strategy A`(){ // Arrange // Act // Assert } @Test fun `strategy B`(){ // Given // When // Then }
एक चुनें और इसे अपने सभी टेस्ट के लिए फॉलो करें।
अवधारणा समान है: अपने टेस्ट कोड को क्लस्टर करें ताकि इसे पढ़ना और रखरखाव करना आसान हो।
यह मेरे पास अभी के लिए है:
class `UserProfileViewModel is tested for` { // Unimplemented Class val viewModel = UserProfileViewModel() @Test fun Font Ratio is fetched from data source`(){} @Test fun `Scroll Speed update is called so fontSize calculations are triggered`() {} @Test fun `Font Ratio is updated with new emissions from data source`() {} }
आइए टेस्ट चलाएं!
मिसिंग टेस्ट टारगेट के कारण फेल टेस्ट
यह विफल हो रहा है। वास्तव में, बिल्ड विफल हुआ है- टेस्ट नहीं।
बधाई! हमने TDD चक्र में पहला चरण बना लिया:
TDD चक्र में पहला चरण
चूंकि ViewModel अभी तक मौजूद नहीं है, हमारे पास लाल रंग हैं।
अब, आइए ViewModel का एक इंस्टेंस बनाएं,
तो, हम एक लापता क्लास या अनिम्प्लिमेंटेड कोड बनाने के लिए IDE का उपयोग करते हैं।
इस डायलॉग को पॉपअप कराने के लिए, मैं पॉइंटर को अनिम्प्लिमेंटेड भाग पर ले जाता हूं और Option + Return (macOS पर) दबाता हूं।
फिर, प्रदान किए गए विकल्पों का पालन करें:
TDD एक्शन: यूनिट टेस्ट फाइल के माध्यम से टारगेट फाइल बनाएं
नई फाइल के लिए सही गंतव्य चुनें
और अब आइए टेस्ट को फिर से चलाएं (अंतिम चरण):
टेस्ट पास होते हुए दिखा रहा है
हाँ! यह पास हो गया।
ध्यान दें कि इन टेस्ट का बॉडी खाली है और वे कुछ भी टेस्ट नहीं करते! यह सही और ठीक है।
हमें सभी टेस्ट क्लास बनाना जारी रखना चाहिए (अभी भी खाली टेस्ट बॉडी के साथ) DFD आरेख में हर घटक के लिए - मैंने इन्हें लेख की शुरुआत में साझा किया था।
यह सुविधा को लागू करने से पहले और स्पष्ट समझ रखने में मदद करता है।
आखिरकार, हमारे पास लगभग 3-4 टेस्ट क्लास होंगी जिनमें सामान्य परिदृश्य और कवर करने के लिए यूनिट टेस्ट होंगे।
यह कुछ इस तरह दिखेगा:
न्यूनतम खाली टेस्ट केस
आइए उनमें से एक को उदाहरण के रूप में लागू करें।
लेकिन, उससे पहले, हमें इस सुविधा के UI और डोमेन डेटा मॉडल के साथ काम करने की आवश्यकता होगी।
तो, डेटा को इधर-उधर ले जाने में सक्षम होने के लिए, आइए पहले से आवश्यक डेटा क्लास बनाएं।
वापस हमारे ProfileViewModel टेस्ट क्लास पर, हमारे पास एक खाली यूनिट टेस्ट फंक्शन है। आइए उस एक को लागू करें।
ध्यान दें कि यहां कुंजी टेस्ट को ध्यान से पढ़ना है और न तो अतिरिक्त कार्यान्वयन और न ही एसर्शन से बचना है।
केवल आवश्यकता को लागू करने की अनुमति है।
इस मामले में, हमें डेटा की एक स्ट्रीम की आवश्यकता है जो पहले से बनाए गए डेटा स्रोत (UserRepository) से जुड़ी हो।
मत भूलें: हमें पहले एक फेलिंग टेस्ट चाहिए।
इनर बॉडी लागू करें
टेस्ट फंक्शन बॉडी के अंदर अनिम्प्लिमेंटेड भागों को नोटिस करें (लाल फ़ॉन्ट के साथ चिह्नित)।
अब, कोड लागू करें, फिर इसे केवल पास करने के लिए रीफैक्टर करें।
मैं मॉकिंग क्लासेस और ऑब्जेक्ट्स के लिए MockK लाइब्रेरी और Flow स्ट्रीम टेस्ट करने के लिए Turbine का उपयोग कर रहा हूं।
यदि आप उनसे परिचित नहीं हैं तो घबराएं नहीं! बस उनके आधिकारिक वेबपेज देखें और उन्हें आजमाएं।
आइए पहले डिपेंडेंसी बनाएं और इसे नेम्ड आर्ग्यूमेंट्स का उपयोग करके ViewModel में जोड़ें। नेम्ड आर्ग्यूमेंट टेस्ट कोड के माध्यम से उचित नाम पेश करने के लिए IDE को पैरामीटर बनाने में मदद करता है।
IDE डायलॉग्स का उपयोग करके लापता पैरामीटर बनाएं
Repository के अंदर FontRatio वेरिएबल के लिए भी ऐसा ही करें।
आखिरकार, अंतिम टेस्ट कोड नीचे दिए गए कोड जैसा कुछ हो सकता है:
class `UserProfileViewModel is tested for` { init { Dispatchers.setMain(Dispatchers.Unconfined) } val mockUserRepository = mockk<UserRepository>() @Test fun `Font Ratio is fetched from data source`() = runTest { // Given val expectedRatio = 2.0f every { mockUserRepository.fontRatio } returns flowOf(FontRatioUiModel(expectedRatio)) val viewModel = UserProfileViewModel(userRepository = mockUserRepository) // When viewModel.fontRatio.test { val fromDataSource = expectItem() // Then assertEquals(/* expected = */ expectedRatio, /* actual = */ fromDataSource.fontRatio) } } ... }
ध्यान दें कि हम यहां ViewModel या Repository के आंतरिक भागों को लागू नहीं करते हैं।
हम केवल टेस्ट बॉडी से त्रुटि हटाने के लिए लापता भागों को बनाते हैं।
हम उन विवरणों को अगले पुनरावृत्ति में लागू करेंगे।
अब टेस्ट चलाएं।
बेशक यह विफल होगा, क्योंकि हमने ProfileViewModel के अंदर FontRatio को लागू नहीं किया।
अब, ViewModel को टेस्ट पास करने के लिए रीफैक्टर करें (न्यूनतम कार्यान्वयन)।
इस मामले में, बस स्टेट फ्लो को रिपॉजिटरी से कनेक्ट करने की आवश्यकता है। हमने इसे पिछले पुनरावृत्ति में डिपेंडेंसी के रूप में पहले ही जोड़ दिया था।
यहां अंतिम कोड है:
class UserProfileViewModel( userRepository: UserRepository ) : ViewModel() { val fontRatio: StateFlow<FontRatioUiModel> = userRepository.fontRatio.stateIn( initialValue = FontRatioUiModel(DEFAULT_FONT_RATIO), scope = viewModelScope, started = SharingStarted.Lazily ) companion object { private const val DEFAULT_FONT_RATIO = 1.0f } }
टेस्ट को फिर से चलाएं, और बूम! यह पास हो जाता है!
मुख्य कोड के न्यूनतम कार्यान्वयन के बाद पासिंग टेस्ट
इस यूनिट टेस्ट के साथ, हमने UserProfileViewModel घटक का एक प्राथमिक भाग लागू किया। लेकिन केवल आवश्यक भाग। बाकी टेस्ट केस के लिए भी ऐसा ही करें।
इन टेस्ट केस को सामान्य यूनिट टेस्ट की तरह न मानें (जो जल्दी चलते और पास होते हैं)।
इस पर कुछ समय बिताएं ताकि पहले तकनीकी और उत्पाद आवश्यकताओं को समझ सकें। फिर, योजना बनाएं और लागू करना शुरू करें। कुछ कोशिशों के बाद, TDD तरीके से सोचना आसान हो जाएगा।
सोर्स कोड
इस प्रोजेक्ट का सोर्स कोड और रिपॉजिटरी मेरे GitHub पेज पर उपलब्ध है।
इसे देखने और अगले चरण पूरे करने के लिए स्वतंत्र महसूस करें। मैंने विभिन्न शाखाओं में पुनरावृत्तियों को अलग किया है ताकि आप इसकी तुलना अपने कार्यान्वयन से कर सकें।
निष्कर्ष
तो, Test-Driven Development (TDD) में गोता लगाने और इसके अंदर और बाहर का पता लगाने के बाद, मुझे कहना होगा, यह एक गेम-चेंजर है!
इसे आपके लिए तोड़ देता हूं:
मुख्य बिंदु:
- TDD वास्तविक कोड लिखने से पहले टेस्ट लिखने के बारे में है। यह पहली बार में थोड़ा अजीब लग सकता है, लेकिन मेरा विश्वास करें, यह अद्भुत काम करता है।
- TDD प्रक्रिया एक सरल चक्र का पालन करती है: फेलिंग टेस्ट लिखें, उन टेस्ट को पास करने के लिए कोड लागू करें, और फिर सब कुछ सुचारू रूप से चलते रहने के लिए आवश्यकतानुसार रीफैक्टर करें।
- स्वचालित टेस्ट पर जोर देकर, TDD हमें सॉफ्टवेयर डिजाइन और विकसित करने में मदद करता है जो ठोस, रखरखाव योग्य, और समय के साथ अनुकूलनीय है।
हम टेस्ट क्लास बनाकर और खाली टेस्ट फंक्शन लिखकर शुरू किया लेकिन अब इसे खत्म करना आपका काम है (या आप तुरंत शेयर्ड रिपॉजिटरी पर जा सकते हैं :) )।
ऐसा लग सकता है कि टेस्ट जो कुछ नहीं करते हैं वे थोड़े अजीब हैं, लेकिन यह सब योजना का हिस्सा है।
अगला, हमने डेटा फ्लो चार्ट के आधार पर हमारी सुविधा को क्या करना चाहिए इसकी एक स्पष्ट योजना तैयार की। इससे हमें कार्यान्वयन में गोता लगाने से पहले आवश्यकताओं को समझने में मदद मिली।
योजना हाथ में होने के बाद, हम आवश्यक घटकों (इस मामले में ViewModel) को लागू करना शुरू किया, यह सुनिश्चित करते हुए कि हमारे टेस्ट पहले विफल हों। यह सही है, TDD में फेलिंग टेस्ट वास्तव में एक अच्छी चीज है!
धीरे-धीरे, हमने टुकड़ों को जोड़ा, जैसे UserAutoScrollSettingsUseCase क्लास सेट करना जो ऑटो-स्क्रॉल स्पीड के आधार पर फ़ॉन्ट साइज़ गणना को संभालता है (प्रोजेक्ट रिपो देखें)।
हमने UI घटकों से भी निपटा, उपयोगकर्ताओं को उनकी वांछित स्क्रॉल स्पीड दर्ज करने की अनुमति दी, और यह सुनिश्चित किया कि फ़ॉन्ट साइज़ तदनुसार समायोजित हो (प्रोजेक्ट रिपो देखें)।
पूरी प्रक्रिया के दौरान, हमने अपने कोड को साफ और सरल रखना सुनिश्चित किया, केवल वही ध्यान केंद्रित किया जो टेस्ट पास करने के लिए आवश्यक था। कोई अनावश्यक जटिलता नहीं!
अंत तक, हमारे पास हमारी "Font Size Auto Setting" सुविधा चालू थी और चल रही थी, टेस्ट के साथ जो उड़ते रंगों के साथ पास हुए।
याद रखें, TDD टेस्ट या कोडिंग के माध्यम से दौड़ने के बारे में नहीं है। यह आपकी डेवलपमेंट प्रक्रिया में जानबूझकर और विचारशील होने के बारे में है, जो लंबे समय में बड़ा भुगतान करता है।
तो, यदि आप अपने सॉफ्टवेयर डेवलपमेंट गेम को लेवल अप करना चाहते हैं, तो TDD को एक शॉट दें! यह एक शक्तिशाली दृष्टिकोण है जो आपके कोड को अधिक ठोस बना देगा, बग कम करेगा, और आपको कुल मिलाकर एक बेहतर डेवलपर बना देगा।
मैंने जो साझा किया है, वह है कि हम मेरी टीम में कैसे काम करते हैं और यह हमारे लिए काम कर रहा है, लेकिन यह हर टीम/कंपनी के लिए सही समाधान नहीं है। आपको यह पता लगाने की आवश्यकता है कि यह आपका है या नहीं। मुझे बताएं अगर आपको लगता है कि मैं इस समाधान में सुधार कर सकता हूं।
हैप्पी कोडिंग! 🚀
मुफ्त में कोड करना सीखें। freeCodeCamp का ओपन सोर्स पाठ्यक्रम 40,000 से अधिक लोगों को डेवलपर्स के रूप में नौकरी पाने में मदद कर चुका है। शुरू करें