एक "नहीं सेटर" दुनिया में इकाई परीक्षण


23

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

public class MyClass
{
    public Boolean IsPublished
    {
        get { return PublishDate != null; }
    }

    public DateTime? PublishDate { get; set; }

    public void Publish()
    {
        if (IsPublished)
            throw new InvalidOperationException("Already published.");

        PublishDate = DateTime.Today;

        Raise(new PublishedEvent());
    }
}

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

चलो निम्नलिखित कोड के साथ परिदृश्य को थोड़ा और वास्तविक बनाते हैं:

public class Document
{
    public Document(String title)
    {
        if (String.IsNullOrWhiteSpace(title))
            throw new ArgumentException("title");

        Title = title;
    }

    public String ApprovedBy { get; private set; }
    public DateTime? ApprovedOn { get; private set; }
    public Boolean IsApproved { get; private set; }
    public Boolean IsPublished { get; private set; }
    public String PublishedBy { get; private set; }
    public DateTime? PublishedOn { get; private set; }
    public String Title { get; private set; }

    public void Approve(String by)
    {
        if (IsApproved)
            throw new InvalidOperationException("Already approved.");

        ApprovedBy = by;
        ApprovedOn = DateTime.Today;
        IsApproved = true;

        Raise(new ApprovedEvent(Title));
    }

    public void Publish(String by)
    {
        if (IsPublished)
            throw new InvalidOperationException("Already published.");

        if (!IsApproved)
            throw new InvalidOperationException("Cannot publish until approved.");

        PublishedBy = by;
        PublishedOn = DateTime.Today;
        IsPublished = true;

        Raise(new PublishedEvent(Title));
    }
}

मैं यूनिट परीक्षण लिखना चाहता हूं जो सत्यापित करें:

  • मैं तब तक प्रकाशित नहीं कर सकता जब तक कि दस्तावेज़ को स्वीकृति नहीं मिल जाती
  • मैं एक दस्तावेज़ को फिर से प्रकाशित नहीं कर सकता
  • प्रकाशित होने पर, WinBy और PublishedOn मान ठीक से सेट हो जाते हैं
  • जब पबलाइज़ किया जाता है, तो PublishedEvent उठाया जाता है

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

आप (डी) इस समस्या का समाधान (कैसे) करते हैं?


जितना अधिक मैं इस बारे में सोचता हूं, उतना ही मुझे लगता है कि आपकी पूरी समस्या के साइड-इफेक्ट्स के तरीके हैं। या बल्कि, एक उत्परिवर्तनीय अपरिवर्तनीय वस्तु। DDD-world में, क्या आपको इस ऑब्जेक्ट की आंतरिक स्थिति को अपडेट करने के बजाय, स्वीकृत और प्रकाशित दोनों से एक नई दस्तावेज़ ऑब्जेक्ट वापस नहीं करना चाहिए?
पीडीआर

1
त्वरित प्रश्न, आप किस ओ / आरएम का उपयोग कर रहे हैं। मैं ईएफ का बहुत बड़ा प्रशंसक हूं, लेकिन बसने वालों को संरक्षित घोषित करते हुए मुझे गलत तरीके से थोड़ा रगड़ना चाहिए।
माइकल ब्राउन

हमारे पास अभी एक मिक्स है क्योंकि फ्री-रेंज डेवलपमेंट के लिए मुझ पर छेड़छाड़ करने का आरोप लगाया गया है। कुछ ADO.NET ऑटोरेपरर का उपयोग करके DataReader से, कुछ लाइनक-टू-SQL मॉडल (जो बदलने के लिए अगला होगा) ) और कुछ नए ईएफ मॉडल।
SonOfPirate

पब्लिश को दो बार कॉल करना बदबूदार नहीं है और इसे करने का तरीका है।
पियोट्र पर्क

जवाबों:


27

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

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

  • मैं तब तक प्रकाशित नहीं कर सकता जब तक कि दस्तावेज़ को अनुमोदित नहीं किया गया हो: एक परीक्षण लिखें, जो कॉल करने से पहले प्रकाशन को मंजूरी दे, वस्तु स्थिति को बदलने के बिना सही त्रुटि का कारण बनता है।

    void testPublishBeforeApprove() {
        doc = new Document("Doc");
        AssertRaises(doc.publish, ..., NotApprovedException);
    }
    
  • मैं एक दस्तावेज़ को फिर से प्रकाशित नहीं कर सकता: एक परीक्षण लिखो जो किसी वस्तु को अनुमोदित करता है, फिर एक बार प्रकाशन को सफल कहना, लेकिन दूसरी बार वस्तु स्थिति को बदलने के बिना सही त्रुटि का कारण बनता है।

    void testRePublish() {
        doc = new Document("Doc");
        doc.approve();
        doc.publish();
        AssertRaises(doc.publish, ..., RepublishException);
    }
    
  • प्रकाशित होने पर, PublishedBy और PublishedOn मान ठीक से सेट किए जाते हैं: एक परीक्षण लिखें जो कॉल को स्वीकृत करता है, फिर कॉल प्रकाशित करें, इस बात का दावा करता है कि ऑब्जेक्ट सही तरीके से बदलता है

    void testPublish() {
        doc = new Document("Doc");
        doc.approve();
        doc.publish();
        Assert(doc.PublishedBy, ...);
        ...
    }
    
  • जब पबलाइज़ किया जाता है, तो PublishedEvent उठाया जाता है: ईवेंट सिस्टम को हुक करें और यह सुनिश्चित करने के लिए एक ध्वज सेट करें कि इसे कहा जाता है

आपको अनुमोदन के लिए परीक्षण लिखने की भी आवश्यकता है।

दूसरे शब्दों में, आंतरिक क्षेत्रों और IsPublished और IsApproved के बीच के संबंध का परीक्षण न करें, यदि आप ऐसा करते हैं तो आपका परीक्षण काफी नाजुक होगा क्योंकि आपके क्षेत्र को बदलने का मतलब है कि आपके परीक्षण कोड को बदलना, इसलिए परीक्षण काफी व्यर्थ होगा। इसके बजाय आपको सार्वजनिक तरीकों के कॉल के बीच संबंध का परीक्षण करना चाहिए, इस तरह, भले ही आप उन क्षेत्रों को संशोधित करें जिन्हें आपको परीक्षण को संशोधित करने की आवश्यकता नहीं होगी।


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

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

4
मुझे अभी तक एक यूनिट टेस्ट देखना है जो केवल एक ही संभावित कारण से विफल हो सकता है। इसके अलावा, आप परीक्षण के "राज्य हेरफेर" भागों को एक setup()विधि में रख सकते हैं --- परीक्षण ही नहीं।
पीटर के।

12
क्यों approve()किसी तरह भंगुर पर निर्भर है , फिर setApproved(true)भी किसी पर निर्भर नहीं है? approve()परीक्षणों में एक वैध निर्भरता है क्योंकि यह आवश्यकताओं में एक निर्भरता है। यदि निर्भरता केवल परीक्षणों में मौजूद थी, तो यह एक और मुद्दा होगा।
कार्ल बेवेलफेल्ट

2
@pdr, आप स्टैक क्लास का परीक्षण कैसे करेंगे? आप स्वतंत्र रूप से push()और pop()तरीकों का परीक्षण करने की कोशिश करेंगे ?
विंस्टन इर्वर्ट

2

फिर भी एक अन्य दृष्टिकोण वर्ग का एक निर्माता बनाना है जो आंतरिक गुणों को तात्कालिकता पर सेट करने की अनुमति देता है:

 public Document(
  String approvedBy,
  DateTime? approvedOn,
  Boolean isApproved,
  Boolean isPublished,
  String publishedBy,
  DateTime? publishedOn,
  String title)
{
  ApprovedBy = approvedBy;
  ApprovedOn = approvedOn;
  IsApproved = isApproved;
  IsApproved = isApproved;
  PublishedBy = publishedBy;
  PublishedOn = publishedOn;
}

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

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

भले ही ऑब्जेक्ट में एक गुण हो और कभी नहीं बदलेगा यह समाधान खराब है। उत्पादन में इस निर्माता का उपयोग करने से किसी को क्या रोकता है? आप इस निर्माता को कैसे चिह्नित करेंगे [TestOnly]?
पियोट्र पर्क

यह उत्पादन में खराब क्यों है? (वास्तव में, मैं जानना चाहूंगा)। कभी-कभी निर्माण पर किसी वस्तु की सटीक स्थिति को फिर से बनाना आवश्यक होता है ... न कि केवल एक मान्य प्रारंभिक वस्तु।
पीटर के।

1
इसलिए जब वह वस्तु को एक प्रारंभिक प्रारंभिक अवस्था में रखने में मदद करता है, तो उसके जीवनचक्र के माध्यम से उस वस्तु के व्यवहार का परीक्षण करने से प्रगति होती है, इसके लिए आवश्यक है कि वस्तु को उसकी प्रारंभिक अवस्था से बदला जाए। मेरे ओपी को इन अतिरिक्त राज्यों के परीक्षण के साथ करना होगा जब आप ऑब्जेक्ट की स्थिति को बदलने के लिए बस गुण सेट नहीं कर सकते।
SonOfPirate

1

एक रणनीति यह है कि आप वर्ग को (इस मामले में दस्तावेज़) विरासत में देते हैं और विरासत में मिली कक्षा के खिलाफ परीक्षण लिखते हैं। विरासत में मिला वर्ग किसी तरह से वस्तु स्थिति को परीक्षणों में सेट करने की अनुमति देता है।

C # एक रणनीति में आंतरिक को व्यवस्थित करने के लिए हो सकता है, फिर परीक्षण परियोजना के लिए आंतरिक को उजागर करना।

आप वर्ग एपीआई का उपयोग भी कर सकते हैं जैसे कि आपने वर्णित किया ("मैं प्रकाशित दो बार कॉल करके निश्चित रूप से ऐसा कर सकता हूं")। यह ऑब्जेक्ट की सार्वजनिक सेवाओं का उपयोग करके ऑब्जेक्ट स्टेट सेट कर रहा होगा, यह मुझे बहुत बदबूदार नहीं लगता है। अपने उदाहरण के मामले में, यह संभवत: ऐसा ही होगा।


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

1

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

बस एक नोट संपादित करें , कुछ बार JSON क्रमांकन विफल रहता है (जैसा कि चक्रीय वस्तु के रेखांकन के मामले में, यह एक गंध, Btw है)। ऐसी स्थितियों में, मैं बाइनरी क्रमांकन के लिए बचाव करता हूं। यह थोड़ा व्यावहारिक है, लेकिन काम करता है। :-)


और अगर आप कोई सेट्टर नहीं है, तो आप अपेक्षित स्थिति में वस्तु कैसे तैयार करते हैं और आप इसे स्थापित करने के लिए सार्वजनिक तरीकों को कॉल नहीं करना चाहते हैं?
पियोट्र पर्क

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

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

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

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

-7

तुम कहो

जब भी संभव हो सर्वोत्तम प्रथाओं को लागू करने का प्रयास करें

तथा

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

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


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

यदि आप कोड ब्लोट के बारे में चिंतित हैं, तो बस डिबग कोड में उपलब्ध कराने के लिए #ifdefs में परीक्षण विधियों को लपेटें (शायद स्वयं एक सर्वोत्तम अभ्यास)


4
-1: अपने परीक्षण ढांचे को खंगालना और कक्षा के अंदर परीक्षण विधियों पर वापस जाना यूनिट परीक्षण के अंधेरे युगों में वापस जाएगा।
रॉबर्ट जॉनसन

9
मेरे से कोई -1, लेकिन उत्पादन में परीक्षण कोड सहित आम तौर पर एक खराब चीज (टीएम) है
पीटर के।

ओपी और क्या करता है? निजी बस्तियों के साथ पंगा लेना करने के लिए छड़ी ?! यह पसंद है कि आप कौन सा जहर पीना चाहते हैं। ओपी को मेरा सुझाव यूनिट टेस्ट को डिबग कोड में डालना था, न कि प्रोडक्शन। मेरे अनुभव में, यूनिट परीक्षणों को एक अलग परियोजना में डालने का मतलब है कि परियोजना को मूल रूप से वैसे भी बारीकी से बांधा जाता है, इसलिए एक देव पीओवी से, थोड़ा अंतर है।
gbjbaanb
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.