अत्यधिक मॉकिंग की आवश्यकता के कारण भंगुर इकाई परीक्षण


21

मैं अपनी यूनिट परीक्षणों के बारे में बढ़ती परेशान समस्या से जूझ रहा हूं जिसे हम अपनी टीम में लागू कर रहे हैं। हम लीगेसी कोड में इकाई परीक्षणों को जोड़ने का प्रयास कर रहे हैं जो अच्छी तरह से डिज़ाइन नहीं किया गया था और जब तक हम परीक्षण नहीं कर रहे हैं तब तक जिन परीक्षणों के साथ हम संघर्ष शुरू कर रहे हैं उनके वास्तविक जोड़ के साथ हमें कोई कठिनाई नहीं हुई।

समस्या के एक उदाहरण के रूप में मान लें कि आपके पास एक ऐसा तरीका है जो 5 अन्य तरीकों को इसके निष्पादन के हिस्से के रूप में कहता है। इस पद्धति के लिए एक परीक्षण यह पुष्टि करने के लिए हो सकता है कि इन 5 अन्य तरीकों में से एक के परिणामस्वरूप एक व्यवहार होता है। इसलिए, क्योंकि एक इकाई परीक्षण केवल एक कारण और एक कारण के लिए असफल होना चाहिए, आप इन 4 अन्य तरीकों को कॉल करके संभावित समस्याओं को समाप्त करना चाहते हैं और उनका मजाक उड़ाते हैं। महान! यूनिट परीक्षण निष्पादित करता है, नकली तरीकों की अनदेखी की जाती है (और उनके व्यवहार को अन्य यूनिट परीक्षणों के हिस्से के रूप में पुष्टि की जा सकती है), और सत्यापन कार्य करता है।

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

स्वाभाविक रूप से समस्या को कुछ हद तक कम किया जा सकता है, बस अधिक विधियाँ कम व्यवहारों को पूरा करती हैं, लेकिन मैं उम्मीद कर रहा था कि शायद अधिक सुरुचिपूर्ण समाधान उपलब्ध था।

यहां एक उदाहरण इकाई परीक्षण है जो समस्या को पकड़ता है।

एक त्वरित नोट के रूप में 'मर्जटेस्ट' एक इकाई परीक्षण वर्ग है जो उस वर्ग से प्राप्त होता है जिसे हम परीक्षण कर रहे हैं और आवश्यकतानुसार व्यवहार को ओवरराइड करते हैं। यह एक 'पैटर्न' है जिसे हम अपने परीक्षणों में नियोजित करते हैं ताकि हम बाहरी वर्गों / निर्भरताओं के कॉल को ओवरराइड कर सकें।

[TestMethod]
public void VerifyMergeStopsSpinner()
{
    var mockViewModel = new Mock<MergeTests> { CallBase = true };
    var mockMergeInfo = new MergeInfo(Mock.Of<IClaim>(), Mock.Of<IClaim>(), It.IsAny<bool>());

    mockViewModel.Setup(m => m.ClaimView).Returns(Mock.Of<IClaimView>);
    mockViewModel.Setup(
        m =>
        m.TryMergeClaims(It.IsAny<Func<bool>>(), It.IsAny<IClaim>(), It.IsAny<IClaim>(), It.IsAny<bool>(),
                         It.IsAny<bool>()));
    mockViewModel.Setup(m => m.GetSourceClaimAndTargetClaimByMergeState(It.IsAny<MergeState>())).Returns(mockMergeInfo);
    mockViewModel.Setup(m => m.SwitchToOverviewTab());
    mockViewModel.Setup(m => m.IncrementSaveRequiredNotification());
    mockViewModel.Setup(m => m.OnValidateAndSaveAll(It.IsAny<object>()));
    mockViewModel.Setup(m => m.ProcessPendingActions(It.IsAny<string>()));

    mockViewModel.Object.OnMerge(It.IsAny<MergeState>());    

    mockViewModel.Verify(mvm => mvm.StopSpinner(), Times.Once());
}

आपने बाकी लोगों को इससे कैसे निपटा है या इसे संभालने का कोई बड़ा 'सरल' तरीका नहीं है?

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


वाह, मैं केवल नकली सेटअप देख रहा हूं, कोई SUT तात्कालिकता या कुछ भी नहीं, क्या आप यहां किसी भी वास्तविक कार्यान्वयन का परीक्षण कर रहे हैं? स्टॉपस्पिनर को कॉल करने वाला कौन है? OnMerge? आपको किसी भी निर्भरता का मज़ाक उड़ाना चाहिए, लेकिन यह बात खुद नहीं कह सकता है ..
जोप

यह देखना थोड़ा कठिन है, लेकिन Mock <MergeTests> SUT है। हम वास्तविक वस्तु पर 'ऑनमर्ज़' पद्धति को सुनिश्चित करने के लिए कॉलबेस ध्वज को सेट करते हैं, लेकिन 'ऑनमर्ज़' द्वारा बताए गए तरीकों का मज़ाक उड़ाते हैं जो कि निर्भरता के मुद्दों आदि के कारण परीक्षण को विफल कर सकते हैं आदि परीक्षण का लक्ष्य अंतिम पंक्ति है - सत्यापित करने के लिए हमने इस मामले में स्पिनर को रोक दिया।
प्रीमियमटियर

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


1
आपके अन्य मुद्दों से पूरी तरह अलग, यह मुझे गलत लगता है कि आपका SUT एक मॉक <मर्जटेस्ट्स> है। आप मॉक टेस्ट क्यों करेंगे? आप खुद मर्जटेस्ट क्लास का परीक्षण क्यों नहीं कर रहे हैं?
एरिक किंग

जवाबों:


18
  1. बेहतर डिजाइन के लिए कोड को ठीक करें। यदि आपके परीक्षणों में ये समस्याएं हैं, तो जब आप चीजों को बदलने की कोशिश करेंगे तो आपके कोड में और भी बुरे मुद्दे होंगे।

  2. यदि आप नहीं कर सकते हैं, तो शायद आपको कम आदर्श होने की आवश्यकता है। विधि की पूर्व और बाद की स्थितियों के खिलाफ परीक्षण करें। यदि आप अन्य 5 विधियों का उपयोग कर रहे हैं, तो कौन परवाह करता है? संभवत: उनकी अपनी यूनिट परीक्षण है, जिससे यह स्पष्ट होता है कि परीक्षण विफल होने पर क्या कारण हुआ।

"यूनिट परीक्षणों के असफल होने का केवल एक कारण होना चाहिए" एक अच्छी दिशानिर्देश है, लेकिन मेरे अनुभव में, अव्यावहारिक है। परीक्षण लिखने के लिए मुश्किल नहीं लिखा है। नाजुक परीक्षणों पर विश्वास नहीं किया जाता है।


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

यदि एक बेहतर डिजाइन यथार्थवादी नहीं है, तो मैं सहमत हूं कि यदि आप अन्य 5 विधियों का उपयोग कर रहे हैं, तो कौन परवाह करता है? विधि सत्यापित करें आवश्यक फ़ंक्शन करता है, न कि यह कैसे कर रहा है।
Kwebble

@Kwebble - समझ में आया, हालाँकि इस सवाल का लक्ष्य निर्धारित करना था कि क्या विधि के लिए व्यवहार को सत्यापित करने का एक सरल तरीका है जब आपको परीक्षण चलाने के लिए विधि के भीतर कहे जाने वाले अन्य व्यवहारों का भी मजाक उड़ाना पड़े। मैं 'कैसे' को हटाना चाहता हूं, लेकिन मुझे नहीं पता कि कैसे :)
PremiumTier

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

8

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

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

लेकिन मैं ध्यान देता हूं कि यह मेरी ओर से एक विधर्म है। जो लोग इकाई परीक्षण में भारी हैं वे "शुद्ध" इकाई परीक्षणों की वकालत करते हैं और मुझे "एकीकरण परीक्षण" का वर्णन करते हैं। मैं व्यक्तिगत रूप से उस भेद की चिंता नहीं करता।


3

मैं मोक्स पर सहजता से विचार करूंगा और केवल परीक्षण तैयार करूंगा जिसमें उन तरीकों को शामिल किया जा सकता है जिनमें इसे कॉल किया जाता है।

कैसे का परीक्षण न करें , क्या परीक्षण करें । यह परिणाम है कि मायने रखता है, अगर जरूरत हो तो उप-विधियों को शामिल करें।

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

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


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

विधि के नामों को देखते हुए मुझे लगता है कि आपका परीक्षित वर्ग बहुत अधिक जिम्मेदारियों को उठा रहा है। एकल जिम्मेदारी सिद्धांत पर पढ़ें। एमवीसी से उधार लेने से थोड़ी मदद मिल सकती है, आपकी कक्षा यूआई, बुनियादी ढांचे और व्यावसायिक चिंताओं दोनों को संभालती है।
Joppe

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