फ्रैजाइल यूनिट टेस्ट से कैसे बचें?


24

हमने 3,000 परीक्षणों के करीब लिखा है - डेटा को हार्ड कोडित किया गया है, कोड का बहुत कम पुन: उपयोग। यह कार्यप्रणाली हमें गधे में काटने लगी है। जैसे-जैसे व्यवस्था में बदलाव होता है हम टूटे हुए परीक्षणों को ठीक करने में अधिक समय लगाते हैं। हमारे पास इकाई, एकीकरण और कार्यात्मक परीक्षण हैं।

मैं जो देख रहा हूं वह प्रबंधनीय और रखरखाव योग्य परीक्षण लिखने का एक निश्चित तरीका है।

फ़्रेमवर्क


यह Programmers.StackExchange, IMO के लिए बहुत बेहतर है ...
19

जवाबों:


21

उन्हें "टूटी यूनिट परीक्षणों" के रूप में मत सोचो, क्योंकि वे नहीं हैं।

वे विनिर्देश हैं, जो आपके कार्यक्रम का समर्थन नहीं करते हैं।

इसे "परीक्षणों को ठीक करने" के रूप में मत समझो, लेकिन "नई आवश्यकताओं को परिभाषित करना" के रूप में।

परीक्षणों को आपके आवेदन को पहले निर्दिष्ट करना चाहिए, न कि दूसरे तरीके से।

आप यह नहीं कह सकते हैं कि आपके पास एक कार्यशील कार्यान्वयन है जब तक आप यह नहीं जानते कि यह काम करता है। जब तक आप इसका परीक्षण नहीं करते, तब तक आप यह नहीं कह सकते कि यह काम करता है।

कुछ अन्य नोट जो आपका मार्गदर्शन कर सकते हैं:

  1. परीक्षण और परीक्षण के तहत कक्षाएं छोटी और सरल होनी चाहिए । प्रत्येक परीक्षण को केवल कार्यक्षमता के एक कोइसेसिव टुकड़े की जांच करनी चाहिए। यही है, यह उन चीजों की परवाह नहीं करता है जो अन्य परीक्षण पहले से ही जांचते हैं।
  2. परीक्षण, और आपकी वस्तुएं, शिथिल रूप से युग्मित होनी चाहिए, इस तरह से कि यदि आप किसी वस्तु को बदलते हैं, तो आप केवल इसके निर्भरता ग्राफ को नीचे की ओर बदल रहे हैं, और अन्य वस्तुएं जो उस वस्तु का उपयोग करती हैं, वे इससे प्रभावित नहीं होती हैं।
  3. आप गलत सामग्री का निर्माण और परीक्षण कर रहे होंगे । क्या आपकी वस्तुओं को आसान इंटरफेसिंग या आसान कार्यान्वयन के लिए बनाया गया है? यदि यह बाद का मामला है, तो आप अपने आप को बहुत सारे कोड को बदलने जा रहे हैं जो पुराने कार्यान्वयन के इंटरफ़ेस का उपयोग करता है।
  4. सर्वोत्तम मामले में, एकल जिम्मेदारी सिद्धांत का कड़ाई से पालन करें। बदतर स्थिति में, इंटरफ़ेस अलगाव सिद्धांत का पालन करें। ठोस सिद्धांतों को देखें ।

5
+1 के लिएDon't think of it as "fixing the tests", but as "defining new requirements".
स्टॉपरयूज़र

2
+1 परीक्षणों को पहले अपना आवेदन निर्दिष्ट करना चाहिए, अन्य तरीके से नहीं
treecoder

11

आप जो वर्णन करते हैं, वह वास्तव में इतनी बुरी चीज नहीं हो सकती है, लेकिन आपके परीक्षणों की खोज में आने वाली समस्याओं के लिए एक सूचक है

जैसे-जैसे व्यवस्था में बदलाव होता है हम टूटे हुए परीक्षणों को ठीक करने में अधिक समय लगाते हैं। हमारे पास इकाई, एकीकरण और कार्यात्मक परीक्षण हैं।

यदि आप अपना कोड बदल सकते हैं, और आपके परीक्षण नहीं टूटेंगे, तो यह मेरे लिए संदेहास्पद होगा। एक वैध परिवर्तन और एक बग के बीच का अंतर केवल यह तथ्य है कि यह अनुरोध किया गया है, जो अनुरोध किया गया है वह (TDD मान लिया गया) आपके परीक्षणों द्वारा परिभाषित किया गया है।

डेटा को हार्ड कोडित किया गया है।

परीक्षणों में हार्ड कोडित डेटा एक अच्छी बात है। परीक्षण मिथ्याकरण के रूप में काम करते हैं, सबूत के रूप में नहीं। यदि बहुत अधिक गणना है, तो आपके परीक्षण तनातनी हो सकते हैं। उदाहरण के लिए:

assert sum([1,2,3]) == 6
assert sum([1,2,3]) == 1 + 2 + 3
assert sum([1,2,3]) == reduce(operator.add, [1,2,3])

अमूर्तता जितनी अधिक होगी, आप एल्गोरिथ्म के जितने करीब पहुंचेंगे, और उसके अनुसार, अपने आप में एक्यूट कार्यान्वयन की तुलना करने के लिए उतना ही करीब होगा।

कोड का बहुत कम पुन: उपयोग

परीक्षणों में कोड का सबसे अच्छा पुन: उपयोग imho 'Checks' है, जैसा कि jUnits में है assertThat, क्योंकि वे परीक्षणों को सरल रखते हैं। इसके अलावा, यदि परीक्षणों को कोड साझा करने के लिए फिर से शुरू किया जा सकता है , तो परीक्षण किया गया वास्तविक कोड भी होने की संभावना हो सकती है , इस प्रकार परीक्षण को हटाए गए आधार का परीक्षण करने वालों को कम कर सकता है


मैं जानना चाहूंगा कि डाउनवॉटर कहां से असहमत है।
कीपला

कीपला - मैं downvoter नहीं हूं, लेकिन आम तौर पर, मैं मॉडल में कहां हूं, इस पर निर्भर करते हुए, मैं यूनिट स्तर पर डेटा के परीक्षण पर ऑब्जेक्ट इंटरैक्शन का परीक्षण करने का पक्ष लेता हूं। इंटीग्रेशन लेवल पर टेस्टिंग डेटा बेहतर काम करता है।
रिच मेल्टन

@keppla मेरे पास एक वर्ग है जो एक अलग चैनल के लिए एक आदेश को रूट करता है अगर इसकी कुल वस्तुओं में कुछ प्रतिबंधित आइटम शामिल हैं। मैं एक नकली आदेश बनाता हूं, इसे 4 आइटमों के साथ पॉपुलेट करें जिनमें से दो प्रतिबंधित हैं। जहाँ तक प्रतिबंधित वस्तुओं को जोड़ा जाता है, यह परीक्षण अद्वितीय है। लेकिन एक नकली आदेश बनाने और दो नियमित वस्तुओं को जोड़ने के चरण एक ही सेटअप है जो एक और परीक्षण का उपयोग करता है जो गैर-प्रतिबंधित आइटम वर्कफ़्लो का परीक्षण करता है। आइटम के साथ इस मामले में अगर ऑर्डर के लिए ग्राहक डेटा सेटअप और एड्रेस सेटअप आदि की आवश्यकता होती है, तो सेटअप सहायकों के पुन: उपयोग का यह अच्छा मामला नहीं है। केवल पुन: उपयोग क्यों करें?
आसिफ शिराज

6

मुझे भी यह समस्या हुई है। मेरा बेहतर तरीका इस प्रकार है:

  1. जब तक वे कुछ परीक्षण करने का एकमात्र अच्छा तरीका न हो, तब तक इकाई परीक्षण न लिखें

    मैं यह स्वीकार करने के लिए पूरी तरह से तैयार हूं कि यूनिट परीक्षणों में निदान और समय-निर्धारण की सबसे कम लागत है। यह उन्हें एक मूल्यवान उपकरण बनाता है। समस्या यह है कि स्पष्ट रूप से आपके माइलेज-में-भिन्नता के साथ, कोड मास को बनाए रखने की लागत को पूरा करने के लिए यूनिट परीक्षण अक्सर बहुत छोटा होता है। मैंने नीचे एक उदाहरण लिखा, एक नज़र।

  2. जहाँ भी वे उस घटक के लिए इकाई परीक्षण के समतुल्य हैं, अभिकथन का प्रयोग करें । कथनों में अच्छी संपत्ति है कि वे हमेशा किसी भी डिबग बिल्ड में सत्यापित होते हैं । इसलिए परीक्षणों की एक अलग इकाई में "कर्मचारी" वर्ग बाधाओं का परीक्षण करने के बजाय, आप सिस्टम में प्रत्येक परीक्षण मामले के माध्यम से कर्मचारी वर्ग का प्रभावी ढंग से परीक्षण कर रहे हैं कथनों में यह भी अच्छा गुण होता है कि वे कोड मास को उतनी नहीं बढ़ाते हैं जितना इकाई परीक्षण (जिसमें अंततः मचान / मॉकिंग / जो भी हो) की आवश्यकता होती है।

    इससे पहले कि कोई मुझे मारे: उत्पादन जोर पर दावा नहीं करना चाहिए। इसके बजाय, उन्हें "त्रुटि" स्तर पर लॉग इन करना चाहिए।

    किसी ऐसे व्यक्ति के लिए सावधानी बरतने के बारे में जो अभी तक इसके बारे में नहीं सोचा है, उपयोगकर्ता या नेटवर्क इनपुट पर कुछ भी नहीं कहता है। यह एक बहुत बड़ी गलती है ™।

    अपने नवीनतम कोड आधारों में, मैं जहाँ भी परख के लिए एक स्पष्ट अवसर देखता हूँ, मैं विवेकपूर्ण रूप से यूनिट परीक्षणों को हटा रहा हूँ। इसने समग्र रूप से रखरखाव की लागत को काफी कम कर दिया है और मुझे बहुत खुश व्यक्ति बना दिया है।

  3. सिस्टम / एकीकरण परीक्षणों को प्राथमिकता दें , उन्हें अपने प्राथमिक प्रवाह और उपयोगकर्ता के सभी अनुभवों के लिए लागू करना। कॉर्नर-मामलों को शायद यहां रहने की आवश्यकता नहीं है। एक प्रणाली परीक्षण सभी घटकों को चलाकर उपयोगकर्ता के अंत में व्यवहार की पुष्टि करता है। उसके कारण, एक सिस्टम टेस्ट आवश्यक रूप से धीमा होता है, इसलिए उन बातों को लिखें (कोई अधिक नहीं, कम नहीं) और आप सबसे महत्वपूर्ण समस्याओं को पकड़ लेंगे। सिस्टम परीक्षणों में बहुत कम रखरखाव ओवरहेड होता है।

    यह याद रखना महत्वपूर्ण है कि, चूंकि आप अभिकथन का उपयोग कर रहे हैं, इसलिए प्रत्येक सिस्टम परीक्षण एक ही समय में कुछ सौ "इकाई परीक्षण" चलाएगा। आप बल्कि यह भी आश्वस्त हैं कि सबसे महत्वपूर्ण कई बार चलते हैं।

  4. मजबूत एपीआई लिखें जिन्हें कार्यात्मक रूप से परीक्षण किया जा सकता है। कार्यात्मक परीक्षण अजीब हैं (और इसका सामना करते हैं) इस तरह के अर्थहीन हैं यदि आपका एपीआई अपने स्वयं के कामकाज घटकों को सत्यापित करने के लिए बहुत कठिन बनाता है। अच्छा एपीआई डिजाइन ए) परीक्षण के चरणों को सीधा बनाता है और बी) स्पष्ट और मूल्यवान दावे को भूल जाता है।

    कार्यात्मक परीक्षण सही पाने के लिए सबसे कठिन काम है, खासकर जब आपके पास प्रक्रिया-अवरोधों के दौरान कई-से-कई या (इससे भी बदतर, ओह भगवान) संचार करने वाले घटक होते हैं। किसी एकल घटक से जुड़े अधिक इनपुट और आउटपुट, कठिन कार्यात्मक परीक्षण है, क्योंकि आपको वास्तव में इसकी कार्यक्षमता का परीक्षण करने के लिए उनमें से एक को अलग करना होगा।


"इकाई परीक्षण न लिखें" के मुद्दे पर, मैं एक उदाहरण प्रस्तुत करूंगा:

TEST(exception_thrown_on_null)
{
    InternalDataStructureType sink;
    ASSERT_THROWS(sink.consumeFrom(NULL), std::logic_error);
    try {
        sink.consumeFrom(NULL);
    } catch (const std::logic_error& e) {
        ASSERT(e.what() == "You must not pass NULL as a parameter!");
    }
}

इस परीक्षण के लेखक ने सात पंक्तियों को जोड़ा है जो अंतिम उत्पाद के सत्यापन में बिल्कुल भी योगदान नहीं करते हैं । उपयोगकर्ता को ऐसा होते हुए कभी भी नहीं देखना चाहिए , क्योंकि या तो) किसी को भी कभी NULL नहीं होना चाहिए (इसलिए एक बार लिखें, फिर) या b) NULL मामले में कुछ अलग व्यवहार होना चाहिए। यदि मामला है (बी), एक परीक्षण लिखें जो वास्तव में उस व्यवहार की पुष्टि करता है।

मेरा दर्शन बन गया है कि हमें कार्यान्वयन कलाकृतियों का परीक्षण नहीं करना चाहिए। हमें केवल कुछ भी परीक्षण करना चाहिए जिसे वास्तविक आउटपुट माना जा सकता है। अन्यथा, यूनिट परीक्षणों (जो किसी विशेष कार्यान्वयन को लागू करने के लिए) और स्वयं कार्यान्वयन के बीच दो बार कोड के मूल द्रव्यमान से बचने का कोई तरीका नहीं है।

यहां यह ध्यान रखना महत्वपूर्ण है कि यूनिट परीक्षणों के लिए अच्छे उम्मीदवार हैं। वास्तव में, यहां तक ​​कि कई परिस्थितियां हैं जहां एक इकाई परीक्षण एकमात्र पर्याप्त साधन है जिसके द्वारा किसी चीज़ को सत्यापित करना और जिसमें उन परीक्षणों को लिखना और बनाए रखना उच्च मूल्य का है। मेरे सिर के शीर्ष पर, इस सूची में nontrivial एल्गोरिदम शामिल हैं, एक एपीआई में डेटा कंटेनर, और अत्यधिक-अनुकूलित कोड जो "जटिल" दिखाई देता है (उर्फ "अगला आदमी शायद इसे खराब कर देगा।")।

आपको मेरी विशिष्ट सलाह, फिर: यूनिट टेस्ट को निर्णायक रूप से हटाना शुरू करें क्योंकि वे टूटते हैं, अपने आप से सवाल पूछते हैं, "क्या यह एक आउटपुट है, या क्या मैं कोड बर्बाद कर रहा हूं?" आप शायद उन चीजों को कम करने में सफल होंगे जो आपका समय बर्बाद कर रहे हैं।


3
प्रणाली / एकीकरण परीक्षण को प्राथमिकता दें - यह दिमाग सुन्न करने वाला है। आपका सिस्टम उस बिंदु पर पहुंच जाता है, जहां इसका उपयोग (धीमी गति से!) उन चीजों का परीक्षण करने के लिए परीक्षण करता है जो एक इकाई स्तर पर जल्दी से पकड़े जा सकते हैं और उन्हें चलाने में घंटों लगते हैं क्योंकि आपके पास इतने ही समान और धीमे परीक्षण हैं।
रिच मेल्टन

1
@RitchMelton पूरी तरह से चर्चा से अलग है, ऐसा लगता है जैसे आपको एक नया CI सर्वर चाहिए। सीआई को ऐसा व्यवहार नहीं करना चाहिए।
एंड्रेस जान टैक

1
एक दुर्घटनाग्रस्त कार्यक्रम (जो कि क्या करता है) आपके परीक्षण धावक (CI) को नहीं मारना चाहिए। इसलिए आपके पास एक परीक्षण धावक है; इसलिए कुछ लोग ऐसी विफलताओं का पता लगा सकते हैं और रिपोर्ट कर सकते हैं।
एंड्रेस जान टैक

1
डिबग-ओनली 'एसरस्ट-स्टाइल असेस्मेंट्स, जिनसे मैं परिचित हूँ (टेस्ट अस्सिटन्स नहीं) एक संवाद को पॉप करता है जो CI को लटका देता है क्योंकि यह डेवलपर इंटरैक्शन की प्रतीक्षा कर रहा है।
रिच मेल्टन

1
आह, यह अच्छी तरह से हमारी असहमति के बारे में बहुत कुछ समझाएगा। :) मैं C- शैली के कथनों का उल्लेख कर रहा हूँ। मैंने केवल अभी देखा कि यह एक .NET प्रश्न है। cplusplus.com/reference/clibrary/cassert/assert
एंड्रेस जान टैक

5

मुझे लगता है कि आपकी इकाई परीक्षण एक आकर्षण की तरह काम करता है। यह अच्छी बात है कि यह बदलाव के लिए बहुत नाजुक है, क्योंकि यह पूरे बिंदु की तरह है। कोड ब्रेक परीक्षणों में छोटे बदलाव ताकि आप अपने पूरे कार्यक्रम में त्रुटि की संभावना को समाप्त कर सकें।

हालांकि, ध्यान रखें कि आपको केवल उन स्थितियों के लिए परीक्षण करने की आवश्यकता है जो आपके तरीके को विफल कर देंगे या अप्रत्याशित परिणाम देंगे। यह आपकी इकाई परीक्षण को "तोड़" करने के लिए अधिक प्रवण रखेगा यदि तुच्छ चीजों के बजाय एक वास्तविक समस्या है।

हालांकि यह मुझे लगता है कि आप इस कार्यक्रम को भारी रूप से नया स्वरूप दे रहे हैं। ऐसे उदाहरणों में, आपको जो कुछ भी करने की आवश्यकता है उसे करें और पुराने परीक्षणों को हटा दें और उन्हें बाद में नए लोगों के साथ बदल दें। यदि आप अपने कार्यक्रम में आमूल-चूल परिवर्तन के कारण ठीक नहीं कर रहे हैं तो रिपेयरिंग यूनिट परीक्षण केवल सार्थक है। अन्यथा आप पा सकते हैं कि आप अपने नए लिखित प्रोग्राम कोड में लागू होने के लिए पुनर्लेखन परीक्षणों के लिए बहुत अधिक समय समर्पित कर रहे हैं।


3

मुझे यकीन है कि दूसरों के पास बहुत अधिक इनपुट होगा, लेकिन मेरे अनुभव में, ये कुछ महत्वपूर्ण चीजें हैं जो आपकी मदद करेंगे:

  1. इनपुट डेटा संरचनाओं के निर्माण के लिए एक परीक्षण वस्तु कारखाने का उपयोग करें, इसलिए आपको उस तर्क की नकल करने की आवश्यकता नहीं है। शायद टेस्ट सेटअप के लिए आवश्यक कोड में कटौती करने के लिए ऑटफ़िक्चर जैसी एक सहायक लाइब्रेरी देखें ।
  2. प्रत्येक परीक्षण वर्ग के लिए, SUT के सृजन को केंद्रीकृत करें, इसलिए चीजों को रिफैक्ट किए जाने पर बदलना आसान होगा।
  3. याद रखें, परीक्षण कोड उत्पादन कोड जितना ही महत्वपूर्ण है। यह भी फिर से सक्रिय किया जाना चाहिए, यदि आप पाते हैं कि आप अपने आप को दोहरा रहे हैं, अगर कोड अप्राप्य लगता है, आदि, आदि।

जितना अधिक आप परीक्षणों में कोड का पुन: उपयोग करते हैं, उतने ही नाजुक हो जाते हैं, क्योंकि अब एक परीक्षण बदलने से दूसरा टूट सकता है। यह एक उचित लागत हो सकती है, स्थिरता के बदले में - मैं उस तर्क में यहाँ नहीं आ रहा हूँ - लेकिन यह तर्क देने के लिए कि अंक 1 और 2 परीक्षण कम नाजुक बनाते हैं (जो प्रश्न था) सिर्फ गलत है।
पीडीआर

@driis - राइट, टेस्ट कोड में रनिंग कोड की तुलना में अलग मुहावरे हैं। 'कॉमन' कोड को रीक्रिएट करके और आईओसी कंटेनर जैसे सामान का उपयोग करके चीजों को छुपाना सिर्फ आपके परीक्षणों से उजागर होने वाली समस्याओं को डिजाइन करता है।
रिच मेल्टन

हालांकि बिंदु @pdr बनाता है इकाई परीक्षणों के लिए मान्य है, मैं तर्क देता हूं कि एकीकरण / प्रणाली परीक्षणों के लिए, "कार्य एक्स के लिए आवेदन तैयार करें" के संदर्भ में सोचना उपयोगी हो सकता है। इसमें उचित स्थान पर नेविगेट करना, कुछ रन-टाइम सेटिंग्स सेट करना, डेटा फ़ाइल खोलना और इसी तरह शामिल हो सकता है। यदि एक ही स्थान पर कई एकीकरण परीक्षण शुरू होते हैं, तो उस कोड को कई परीक्षणों में पुन: उपयोग करने के लिए फिर से भरना एक खराब बात नहीं हो सकती है यदि आप इस तरह के दृष्टिकोण के जोखिमों और सीमाओं को समझते हैं।
बजे एक सीवीएन

2

हैंडल टेस्ट जैसे आप इसे सोर्स कोड से करते हैं।

संस्करण नियंत्रण, चेकपॉइंट रिलीज़, अंक ट्रैकिंग, "सुविधा स्वामित्व", योजना और प्रयास अनुमान आदि आदि। वहाँ किया गया है कि - मुझे लगता है कि यह आपके द्वारा वर्णित समस्याओं से निपटने का सबसे कुशल तरीका है।


1

आपको निश्चित रूप से जेरार्ड मेस्जारोस के XUnit परीक्षण पैटर्न पर एक नज़र डालनी चाहिए । आपके परीक्षण कोड का पुन: उपयोग करने और दोहराव से बचने के लिए इसमें कई व्यंजनों के साथ एक शानदार अनुभाग है

यदि आपके परीक्षण भंगुर हैं, तो यह भी हो सकता है कि आप डबल्स का परीक्षण करने के लिए पर्याप्त सहारा न लें। विशेष रूप से, यदि आप प्रत्येक इकाई परीक्षण की शुरुआत में वस्तुओं के पूरे रेखांकन को फिर से बनाते हैं, तो आपके परीक्षणों में अरेंज सेक्शन की देखरेख हो सकती है और आप अक्सर खुद को उन स्थितियों में पा सकते हैं, जहाँ आपको काफी संख्या में परीक्षणों में अरेंज सेक्शन को फिर से लिखना पड़ता है। आपके सबसे अधिक इस्तेमाल किए जाने वाले वर्गों में से एक बदल गया है। मोक्स और स्टब्स आपको उन वस्तुओं की संख्या में कटौती करके यहां मदद कर सकते हैं जिन्हें आपको प्रासंगिक परीक्षण संदर्भ के लिए पुनर्जलीकरण करना है।

नकल और स्टब्स के माध्यम से अपने परीक्षण सेटअप से महत्वहीन विवरण लेना और कोड का पुन: उपयोग करने के लिए परीक्षण पैटर्न को लागू करना, उनकी सुगंध को काफी कम करना चाहिए।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.