प्रस्तावना जैसा कुछ
लेख
"कैसे दो प्रोग्रामर ने पके हुए ब्रेड" पहली बार में मुझे सिर्फ एक मजाक लग रहा था - उन "आवश्यकताओं" के आधार पर "डिज़ाइन" के कुछ प्रकार के निर्माण के प्रयास इतने बेतुके हैं, जो "प्रबंधक" बनाता है। लेकिन हर मजाक में कुछ सच्चाई होती है ... सामान्य तौर पर, एक सवाल खुद से उठता है: इस स्थिति में मैं अपने अभ्यास कार्य में पालन करने का प्रयास कैसे करूंगा? जब जवाब देने की कोशिश की जाती है, तो वास्तव में नीचे प्रस्तुत किया गया है।
टीडीडी + स्मालटाक
दरअसल, मेरे द्वारा उपयोग किए गए दृष्टिकोण का सार शीर्षक में रखा गया है, लेकिन, मुझे लगता है, यहां कुछ स्पष्टीकरण की आवश्यकता है।
मैं "स्वच्छ" TDD (TDD एक परीक्षण संचालित
विकास है , इसलिए स्त्री) का समर्थक हूं। इस मामले में "स्वच्छता" का मतलब है कि सॉफ्टवेयर विकास के कुछ चरणों में, अर्थात् कार्यात्मक आवश्यकताओं को प्राप्त करने के तुरंत बाद और अगली रिलीज से पहले (जो कि आवश्यकताओं के विश्लेषण, डिजाइन और प्रोग्राम कोड के निर्माण के चरणों में), डेवलपर इस पद्धति के अनुसार पूर्ण रूप से कार्य करता है, उससे विचलित हुए बिना।
टीडीडी "शुद्धता" का परीक्षण (राज्य विश्लेषण के आधार पर) बनाने की "क्लासिक शैली" और सरोगेट ऑब्जेक्ट्स के साथ टीडीडी (मॉक-एस) "(अन्य वस्तुओं के साथ विकसित सबसिस्टम के इंटरैक्शन के विश्लेषण के आधार पर) सुनिश्चित किया जाता है। मोकी आपको एक टॉप-डाउन सिस्टम (कार्यात्मक अपघटन को अंजाम देने के लिए, सिस्टम का एक "खाली फ्रेम") बनाने की अनुमति देता है, और क्लासिक टीडीडी तब एक नीचे-अप कार्यान्वयन का विकास प्रदान करता है। और, मेरे अनुभव में, यह दृष्टिकोण बहुत अच्छी तरह से है (कम से कम मुझे पता है कि मुख्यधारा के सभी विकल्पों से बेहतर) स्मॉलटाक पर्यावरण के उपयोग के साथ संयुक्त है। यहां मैं विवरणों में नहीं जाऊंगा, उन्हें अगले लेखों के लिए छोड़ दूंगा, लेकिन बस यह देखने की पेशकश करेगा कि यह दृष्टिकोण इस पर कुछ अजीब कैसे काम करता है, लेकिन इससे और कुछ दिलचस्प उदाहरण।
चरण 1. "दोस्तों, हमें रोटी बनाने की ज़रूरत है"
हम पहली परीक्षा बनाते हैं, उसी समय आवश्यक शब्दावली के साथ आ रहे हैं (यदि आप चाहें, तो आप शायद इसे एक रूपक कह सकते हैं)।
अब तक, केवल एक चीज जो हम जानते हैं: बाहर निकलने पर, सिस्टम "रोटी" देता है। चूंकि मौजूदा "स्टेटमेंट" में कोई कार्यक्षमता रोटी के साथ नहीं जुड़ी है, इसलिए संबंधित वर्ग (
Bread
) से संबंधित आउटपुट ऑब्जेक्ट को बस जांचने की इच्छा है।
और रोटी कौन बनाता है? बेकर, शायद ... तदनुसार, परीक्षण में हम निम्नलिखित कार्यात्मक आवश्यकता को ठीक करते हैं, जिसे हम समस्या के मौजूदा "बयान" से निकालने में कामयाब रहे: बेकर से रोटी बनाने के लिए कहा जा सकता है, जिसके जवाब में उसे हमें इसी वर्ग की एक वस्तु देनी चाहिए। यह आवश्यकता बेकर की कार्यक्षमता से संबंधित है, पहले परीक्षण के लिए वर्ग को (अभी के लिए) सिर्फ
BakerTests
कहा जाता है, और इसमें हम एक परीक्षण बनाते हैं:
BakerTests >> testProducesBread | baker product | baker := Baker new. product := baker produceBread. product class should be: Bread
परीक्षण का कार्यान्वयन उतना ही तुच्छ है जितना कि यह।
इस पुनरावृत्ति के परिणामों को सारांशित करते हुए, हम अपने लिए यह नोट कर सकते हैं कि ब्रेड पर पूरी तरह से कोई आवश्यकता नहीं लगाई गई है, इसी वर्ग का उद्देश्य स्पष्ट नहीं है, और संभवतः इस बारे में एक प्रबंधक की कोशिश करना समझ में आता है: यही कारण है कि हम इस तरह के एक तुच्छ परीक्षण के लिए खुद को सीमित करने के लिए मजबूर हैं, और ऐसा लगता है कि हमने विकास को उच्चतम स्तर के अमूर्तता से शुरू नहीं किया, और यह अक्सर बाद में समस्याओं का कारण बनता है। फिर भी, हमारे उदाहरण के ढांचे में, हम मानते हैं कि इस पर पहला चलना पूरा हो गया है।
चरण 2. "हमें न केवल रोटी की जरूरत है, बल्कि ओवन में पकाया जाता है"
हम परीक्षण में हमारे सिस्टम के बारे में नए ज्ञान के प्राप्त टुकड़ों को ठीक करते हैं। हमें क्या पता चला? बेकिंग के दौरान केवल बेकर ओवन के साथ इंटरैक्ट करता है। इसे ठीक करने के लिए, सरोगेट ऑब्जेक्ट हमारे लिए उपयोगी होगा (आखिरकार, वे ऐसी चीजों के लिए अभिप्रेत हैं)। मैं
Mocketry फ्रेमवर्क का उपयोग करता
हूं । इसके साथ और मुझे निम्नलिखित कोड मिला:
BakerTests >> testUsesOvenToProduceBread | product | [ :oven | baker oven: oven. [ product := baker produceBread ] should strictly satisfy: [ (oven produceBread) willReturn: #bread ]. product should be: #bread ] runScenario
यहां हमने निम्नलिखित कार्य किए हैं:
- हमने
#runScenario
बाहरी ब्लॉक में एक #runScenario
संदेश भेजकर एक परीक्षण स्क्रिप्ट ऑब्जेक्ट बनाया (शब्द "बंद होना" किसी के करीब हो सकता है)। - उन्होंने कहा कि
oven
एक सरोगेट ऑब्जेक्ट है (Mocketry स्वचालित रूप से स्क्रिप्ट ब्लॉक मापदंडों को तदनुसार इनिशियलाइज़ करता है)। - उन्होंने कहा कि जब बेकर को रोटी (पहला नेस्टेड ब्लॉक) बनाने के लिए कहा जाता है, तो स्टोव को
#produceBread
संदेश प्राप्त करना चाहिए (दूसरे नेस्टेड ब्लॉक में संदेश #satisfy:
के तर्क के रूप में पारित किया गया। वास्तव में, यह परीक्षण की पहली स्थिति है। - इसके अलावा, हमने इस संदेश के जवाब में अपने नकली ओवन को एक निश्चित वस्तु (
#bread
) को वापस करने के लिए कहा, जो भविष्य में रोटी पकाने के लिए मूल अनुरोध का परिणाम होना चाहिए। इन वस्तुओं की पहचान परीक्षण की दूसरी शर्त है। और यहां हम इस परिणामी वस्तु की प्रकृति में रुचि नहीं रखते हैं, लेकिन केवल उस वस्तु की अपनी पहचान जो भट्ठी ने दी है वह महत्वपूर्ण है। इसलिए, इस भूमिका में, हम वास्तव में, एक साधारण स्ट्रिंग स्थिरांक का उपयोग करते हैं: #bread
।
मैं यह भी ध्यान देता हूं कि परीक्षण का थोड़ा रिफैक्टेड संस्करण यहां प्रस्तुत किया गया है: हमने पहले ही दोनों परीक्षणों में एक बेकर के निर्माण से जुड़े दोहराव से छुटकारा पा लिया, उदाहरण के चर में इसे "खींचा" और
#setUp
विधि में इनिशियलाइज़ किया गया, जिसे प्रत्येक परीक्षण शुरू करने से पहले स्वचालित रूप से कहा जाता है:
BakerTests >> setUp super setUp. baker := Baker new.
मैं ध्यान देता हूं कि परीक्षण लिखने की प्रक्रिया में मुझे निम्नलिखित निर्णय लेना था: बेकर अग्रिम में जानता है कि वह किस स्टोव का उपयोग करता है - यह उसकी स्थिति का हिस्सा बन जाता है। यह समाधान वास्तव में बहुत महत्वपूर्ण नहीं है, क्योंकि यदि आवश्यक हो तो इसे बदलना काफी आसान होगा: यदि स्टोव केवल काम के समय ही जाना जाता है, तो उत्पादन के लिए एक पैरामीटर जोड़ें; और अगर यह कहीं और से प्राप्त किया जाना चाहिए, तो हम एक ऐसी वस्तु का परिचय देंगे जो हमें सही समय पर सही स्टोव देगी।
इस परीक्षण को लागू करने के लिए, हम बेकर में
#produceBread
विधि को थोड़ा
#produceBread
करते हैं:
Baker >> produceBread ^ oven produceBread
इस विधि को संकलित करने की प्रक्रिया में, सिस्टम ओवन क्या है में रुचि रखता है। जवाब में, हम समझाते हैं कि हम एक उदाहरण चर बनाना चाहते हैं। उसके बाद, परीक्षण चलाने से, हम डिबगर संदेश देखते हैं और समझते हैं कि सिस्टम का असंतोष आवश्यक सेटर की कमी के कारण है। हम इसे सीधे डिबगर से बनाते हैं, परीक्षण के निष्पादन को बाधित किए बिना:
Baker >> oven: anOven oven := Oven new
वहीं, संकलन के दौरान, हम
Oven
क्लास बनाते हैं।
परीक्षण चलाने के लिए, हम देखते हैं कि यह सफलतापूर्वक पूरा होता है। लेकिन हमारे सिस्टम में सभी परीक्षण चल रहे हैं (और पहले से ही दो हैं), हम देखते हैं कि पहला टूट गया है। यदि कारण पहले से स्पष्ट नहीं है, तो हम इसे डीबगर में वर्तमान स्टैक के अनुसार निदान संदेश से या सिस्टम की स्थिति का विश्लेषण करके आसानी से पता लगा सकते हैं: स्टोव सेट नहीं है। ठीक है, हम एक डिफ़ॉल्ट स्टोव प्रदान करेंगे (यहाँ मैं
#initialize
आउट कर रहा हूँ, जैसे कि
स्क्वीक और
#initialize
,
Object
पहले से ही
#initialize
पद्धति के लिए एक कॉल है - अन्य
#initialize
वातावरण में यह बहुत - हाँ, वास्तव में, बहुत - इसे लागू करना आसान है अपने आप से):
Baker >> initialize super initialize. oven := Oven new.
परीक्षण चलाएं - सिस्टम रिपोर्ट करता है कि
Oven
वर्ग में
#produceBread
पद्धति लागू नहीं है। हम वहीं महसूस करते हैं:
Oven >> produceBread ^ Bread new
हम निष्पादन जारी रखते हैं - परीक्षण शुद्धता से हरा हो जाता है। सभी (दोनों) परीक्षण अब हरे हैं। हम रिफैक्टरिंग की ओर मुड़ते हैं ... लेकिन रिफ्लेक्टर के लिए कुछ भी नहीं लगता है (जो समझ में आता है, क्योंकि हम लगभग कोड नहीं लिखते हैं - हमारे खुश प्रोग्रामिंग के लिए पीएम को धन्यवाद)।
इस पुनरावृत्ति के बाद प्राप्त परिणाम, साथ ही पिछले एक के बाद, कुछ संदिग्ध दिखता है: बेकर खुद, यह पता चला है, व्यावहारिक रूप से कुछ भी नहीं करता है। लेकिन जो हुआ वह दी गई स्थितियों में सबसे सरल उपाय है। संक्षेप में, सभी प्रश्न पीएम के लिए हैं :)
चरण 3. "हमें विभिन्न प्रकार के स्टोव की आवश्यकता है"
फिर से: यह क्यों आवश्यक है - इतिहास चुप है। लेकिन जब से खेल की शर्तों को स्वीकार कर लिया गया है, हम खेलते हैं: प्रत्येक प्रकार के स्टोव के लिए आप एक परीक्षण बना और लागू कर सकते हैं। हालाँकि, यह तुरंत पता चलता है कि वास्तव में इस "मचान" के साथ कुछ ऐसा है जो आपको नहीं करना है! हम इसे गैस स्टोव के उदाहरण के साथ सत्यापित करेंगे (केवल एक जिसे हमें भविष्य में आवश्यकता होगी):
GasOvenTests >> testProducesBread | oven | oven := GasOven new. oven produceBread should be a kind of: Bread
लेकिन, तार्किक रूप से
Oven
से गैस स्टोव विरासत में मिला है, जहां
#produceBread
पहले से ही लागू किया गया है, हमें तुरंत हरे रंग में परीक्षण मिलता है। सामान्य तौर पर, यह एक बुरा लक्षण है: हमें लगता है कि एक अर्थहीन परीक्षा लिखी गई है। प्रबंधक के खिलाफ आरोप एक आम बात हो जाती है, मुझे उनकी याद आती है ... :) शायद असली काम में विभिन्न प्रकार की भट्टियों के साथ किसी न किसी प्रकार की कार्यक्षमता जुड़ी हुई है, लेकिन इस मामले में यह इस तरह के अंधेरे में कवर किया गया है कि यह कल्पना करने का कोई मतलब नहीं है।
चरण 4. "हमें गैस के बिना गैस चूल्हे की जरूरत नहीं है"
फिर से उत्तर से अधिक प्रश्न हैं, आपको सोचना होगा। इस शब्द के लिए सबसे सरल, लेकिन उचित रूप से उचित समाधान मेरे लिए इस तरह दिखता है:
GasOvenTests >> testConsumesGasToProduceBread [ :gasProducer | oven gasProducer: gasProducer. [ oven produceBread ] should strictly satisfy: [ gasProducer consume ] ] runScenario GasOven >> produceBread gasProducer consume. ^ super produceBread >> gasProducer: gasProducer gasProducer := aGasProducer
क्या चूल्हा शायद ही अपने विवेक पर गैस स्रोत को बदलता है? और वास्तव में खपत कैसे होती है यह अभी तक स्पष्ट नहीं है - इसलिए, हम केवल स्रोत को खपत के तथ्य के बारे में सूचित करते हैं।
समाधान अनिवार्य रूप से पिछले एक के समान है, जो आसानी से समझा जा सकता है - कार्यों को एक ही शैली में सेट किया गया है, और तदनुसार हम उन्हें इसी तरह से हल करते हैं कि एक बार काम किया (आखिरकार, कोई समस्या नहीं थी)।
पिछली बार की तरह, एक परीक्षण टूट गया (गैस स्रोत डिफ़ॉल्ट रूप से सेट नहीं है), मरम्मत:
GasOven >> initialize super initialize. gasProducer := GasProducer new. GasProducer >> consume
- हां, हम इस विधि को छोड़ देते हैं (मुझे उम्मीद है कि अब तक) खाली है, क्योंकि इसके लिए कोई विशिष्ट आवश्यकताएं निर्धारित नहीं की गई हैं।
चरण 5. "हमें ज़रूरत है ताकि ओवन भी पिस (अलग-अलग - मांस के साथ, अलग से - गोभी के साथ), और केक बेक कर सकें।"
फिर से कोहरा: क्या इसका मतलब है पेक को सेंकना? वे रोटी से कैसे अलग हैं? और केक से? मैंने दो संभावित विकल्प देखे:
- ये उत्पाद उनके कुछ गुणों में भिन्न होते हैं (अधिक सटीक, व्यवहार में) - लेकिन हमें इस बारे में कुछ भी पता नहीं है, इसलिए यह विकल्प हमें इस स्थिति में कुछ भी नहीं देता है। हम इसे गिरा देते हैं।
- उत्पाद विनिर्माण विधि में भिन्न होते हैं। सिस्टम के बारे में ज्ञान के संदर्भ में यह विकल्प अधिक उत्पादक है: एक उत्पाद बनाने के लिए, आपको इसके निर्माण की विधि निर्दिष्ट करने की आवश्यकता है। हम इसे परीक्षण में ठीक करते हैं।
निर्माण विधि क्या कहलाती है? मेरी राय में, यह एक नुस्खा है ...
testUsesOvenToProduceByRecipe | product | [ :oven | baker oven: oven. [ product := baker cookWith: #recipe ] should strictly satisfy: [ (oven produce: #recipe) willReturn: #product ]. product should be: #product ] runScenario
यहाँ हमने निम्न दर्ज किया है:
- एक नुस्खा के साथ कुछ पकाने के लिए बेकर से अनुरोध
- बेकर स्टोव के लिए एक ही नुस्खा पास करता है (वास्तव में, सबसे अधिक संभावना है, यह किसी अन्य तरीके से किया जाता है, लेकिन हम अभी इसके बारे में कुछ भी नहीं जानते हैं - इसलिए, हम इसे यथासंभव सरल बनाते हैं)
- स्टोव से बेकर को जो मिलता है वह अंतिम परिणाम के रूप में सामने आता है
- नुस्खा और अंतिम उत्पाद के बीच का संबंध, दुर्भाग्य से, "पर्दे के पीछे" बना हुआ है - केवल इसलिए कि इस (अभी तक) के बारे में कुछ भी स्पष्ट नहीं है।
आप कुछ और प्रकार के पुनरावृत्तियों कर सकते हैं, विभिन्न प्रकार के व्यंजनों पर "फेंक" परीक्षण कर सकते हैं ... लेकिन इसके लिए मैं कुछ जानना चाहूंगा कि यह कैसे काम करना चाहिए। आप निश्चित रूप से सपना देख सकते हैं, लेकिन यह समय के लिए एक दया है ... इसलिए हम निम्नलिखित बिंदु पर जाते हैं।
चरण 6. "हमें अलग-अलग व्यंजनों के अनुसार रोटी, पाई और केक सेंकना चाहिए"
हम, ऐसा लगता है, यह पहले ही कर चुके हैं ... ठीक है, जैसा कि हम कर सकते थे।
चरण 7. "हमें ईंटों को भट्ठी में जलाया जाना चाहिए"
यदि हम मानते हैं कि एक नुस्खा के अनुसार एक ईंट को बेकर द्वारा बेक किया जा सकता है (और क्यों नहीं? हमें इसके विपरीत कोई जानकारी नहीं मिली है), तो हमें फिर से कुछ भी करने की आवश्यकता नहीं है ... ठीक है, परीक्षणों के संग्रह में एक और परीक्षण जोड़ने के लिए, जो हमने अभी तक (अब तक) नहीं किया है। व्यंजनों।
सामान्य तौर पर, सब कुछ लगता है ...
परिणाम
हमें क्या मिला? छह कक्षाएं ... और बहुत अधिक नहीं (यहां तक कि स्पष्ट रूप से बोलना - बस थोड़ा सा) कार्यक्षमता ... लेकिन व्यक्तिगत रूप से, मैं इसके लिए हमारे प्रबंधक को "धन्यवाद" करने के लिए इच्छुक हूं।
Baker Bread Oven ElectricOven GasOven GasProducer
परिणाम और प्रक्रिया के बारे में आपकी राय सुनना दिलचस्प होगा ...