आप एक ही व्यवहार को प्रदर्शित करने वाली कई वस्तुओं के लिए संरचना इकाई परीक्षण कैसे करते हैं?


9

बहुत सारे मामलों में मेरे पास कुछ व्यवहार के साथ एक मौजूदा वर्ग हो सकता है:

class Lion
{
    public void Eat(Herbivore herbivore) { ... }
}

... और मेरे पास एक इकाई परीक्षण है ...

[TestMethod]
public void Lion_can_eat_herbivore()
{
    var herbivore = buildHerbivoreForEating();
    var test = BuildLionForTest();
    test.Eat(herbivore);
    Assert.IsEaten(herbivore);
}

अब, क्या होता है मुझे शेर के पहचान वाले व्यवहार के साथ एक टाइगर वर्ग बनाने की आवश्यकता है:

class Tiger
{
    public void Eat(Herbivore herbivore) { ... }
}

... और चूँकि मुझे वही व्यवहार चाहिए, मुझे उसी परीक्षा को चलाने की ज़रूरत है, मैं कुछ इस तरह से करता हूँ:

interface IHerbivoreEater
{
    void Eat(Herbivore herbivore);
}

... और मैंने अपना परीक्षण फिर से कराया:

[TestMethod]
public void Lion_can_eat_herbivore()
{
    IHerbivoreEater_can_eat_herbivore(BuildLionForTest);
}


public void IHerbivoreEater_can_eat_herbivore(Func<IHerbivoreEater> builder)
{
    var herbivore = buildHerbivoreForEating();
    var test = builder();
    test.Eat(herbivore);
    Assert.IsEaten(herbivore);
}

... और फिर मैं अपनी नई Tigerकक्षा के लिए एक और परीक्षा जोड़ता हूं :

[TestMethod]
public void Tiger_can_eat_herbivore()
{
    IHerbivoreEater_can_eat_herbivore(BuildTigerForTest);
}

... और फिर मैं अपनी Lionऔर Tigerकक्षाएं फिर से शुरू करता हूं (आमतौर पर विरासत द्वारा, लेकिन कभी-कभी रचना द्वारा):

class Lion : HerbivoreEater { }
class Tiger : HerbivoreEater { }

abstract class HerbivoreEater : IHerbivoreEater
{
    public void Eat(Herbivore herbivore) { ... }
}

...और सब ठीक है। हालाँकि, चूंकि कार्यक्षमता अब HerbivoreEaterकक्षा में है, इसलिए अब ऐसा लगता है कि प्रत्येक उपवर्ग पर इन व्यवहारों के लिए परीक्षण होने में कुछ गड़बड़ है। फिर भी यह उपवर्ग हैं जो वास्तव में उपभोग किए जा रहे हैं, और यह केवल एक कार्यान्वयन विवरण है कि वे अतिव्यापी व्यवहार साझा करने के लिए होते हैं ( Lionsऔर Tigersउदाहरण के लिए पूरी तरह से अलग-अलग उपयोग हो सकते हैं)।

यह एक ही कोड को कई बार परीक्षण करने के लिए बेमानी लगता है, लेकिन ऐसे मामले हैं जहां उपवर्ग बेस क्लास की कार्यक्षमता को ओवरराइड और कर सकता है (हाँ, यह एलएसपी का उल्लंघन कर सकता है, लेकिन इसका सामना करना पड़ता है, IHerbivoreEaterयह सिर्फ एक सुविधाजनक परीक्षण इंटरफ़ेस है - यह अंत उपयोगकर्ता के लिए कोई फर्क नहीं पड़ता)। इसलिए इन परीक्षणों का कुछ मूल्य है, मुझे लगता है।

इस स्थिति में अन्य लोग क्या करते हैं? क्या आप अपने परीक्षण को आधार वर्ग में ले जाते हैं, या आप अपेक्षित व्यवहार के लिए सभी उपवर्गों का परीक्षण करते हैं?

संपादित करें :

@Pdr के उत्तर के आधार पर मुझे लगता है कि हमें इस पर विचार करना चाहिए: IHerbivoreEaterयह सिर्फ एक विधि हस्ताक्षर अनुबंध है; यह व्यवहार को निर्दिष्ट नहीं करता है। उदाहरण के लिए:

[TestMethod]
public void Tiger_eats_herbivore_haunches_first()
{
    IHerbivoreEater_eats_herbivore_haunches_first(BuildTigerForTest);
}

[TestMethod]
public void Cheetah_eats_herbivore_haunches_first()
{
    IHerbivoreEater_eats_herbivore_haunches_first(BuildCheetahForTest);
}

[TestMethod]
public void Lion_eats_herbivore_head_first()
{
    IHerbivoreEater_eats_herbivore_head_first(BuildLionForTest);
}

तर्क के लिए, क्या आपके पास एक Animalवर्ग नहीं होना चाहिए जिसमें शामिल है Eat? सभी जानवर खाते हैं, और इसलिए Tigerऔर Lionवर्ग जानवर से विरासत में मिल सकता है।
मफिन मैन

1
@ निक - यह एक अच्छा बिंदु है, लेकिन मुझे लगता है कि यह एक अलग स्थिति है। जैसा कि @pdr ने बताया, यदि आप Eatव्यवहार को आधार वर्ग में रखते हैं, तो सभी उपवर्गों को समान Eatव्यवहार प्रदर्शित करना चाहिए । हालाँकि, मैं 2 अपेक्षाकृत असंबंधित वर्गों के बारे में बात कर रहा हूँ जो किसी व्यवहार को साझा करने के लिए होते हैं। उदाहरण के लिए विचार करें,, Flyके व्यवहार Brickऔर Personजो है, हम यह मान सकते हैं, एक ऐसी ही उड़ान व्यवहार प्रदर्शित करते हैं, लेकिन यह जरूरी मतलब नहीं है उन्हें एक आम आधार वर्ग से निकाले जाते हैं है।
स्कॉट व्हिटलॉक

जवाबों:


6

यह बहुत अच्छा है क्योंकि यह दिखाता है कि परीक्षण वास्तव में डिजाइन के बारे में आपके सोचने के तरीके को कैसे चलाते हैं। आप डिजाइन में समस्याएँ पूछ रहे हैं और सही सवाल पूछ रहे हैं।

इसको देखने के दो तरीके हैं।

IHerbivoreEater एक अनुबंध है। सभी IHerbivoreEaters में एक खाने का तरीका होना चाहिए जो एक Herbivore को स्वीकार करता है। अब, आपके परीक्षणों की परवाह नहीं है कि यह कैसे खाया जाता है; हो सकता है कि आपका शेर धड़ल्ले से शुरू हो और बाघ गले से लगे। आपके सभी परीक्षण के बारे में परवाह है कि इसे खाने के बाद, हर्बिवोर खाया जाता है।

दूसरी ओर, आप जो कह रहे हैं उसका एक हिस्सा यह है कि सभी IHerbivoreEaters Herbivore को बिल्कुल उसी तरह से खाते हैं (इसलिए आधार वर्ग)। यह मामला होने के नाते, IHerbivoreEater अनुबंध होने का कोई मतलब नहीं है। यह कुछ भी नहीं प्रदान करता है। तुम भी HerbivoreEater से बस विरासत में मिल सकता है।

या हो सकता है कि शेर और टाइगर को पूरी तरह से दूर कर दें।

लेकिन, अगर शेर और बाघ अपने खाने की आदतों के अलावा हर मायने में अलग हैं, तो आपको यह सोचने की ज़रूरत है कि क्या आप एक जटिल वंशानुक्रम वाले पेड़ की समस्याओं में भाग लेने जा रहे हैं। क्या होगा यदि आप भी दोनों वर्गों को फेलिन से प्राप्त करना चाहते हैं, या केवल KingOfItsDomain से शेर वर्ग (शार्क के साथ, शायद)। यह वह जगह है जहाँ एलएसपी वास्तव में आता है।

मेरा सुझाव है कि आम कोड बेहतर रूप से समझाया गया है।

public class Lion : IHerbivoreEater
{
    private IHerbivoreEatingStrategy _herbivoreEatingStrategy;
    private Lion (IHerbivoreEatingStrategy herbivoreEatingStrategy)
    {
        _herbivoreEatingStrategy = herbivoreEatingStrategy;
    }

    public Lion() : this(new StandardHerbivoreEatingStrategy())
    {
    }

    public void Eat(Herbivore herbivore)
    {
        _herbivoreEatingStrategy.Eat(herbivore);
    }
}

एक ही टाइगर के लिए जाता है।

अब, यहां एक सुंदर चीज विकसित हो रही है (सुंदर है क्योंकि मैंने इसे इरादा नहीं किया है)। यदि आप उस निजी कंस्ट्रक्टर को परीक्षण के लिए उपलब्ध कराते हैं, तो आप एक नकली IHerbivoreEatingStrategy में पास कर सकते हैं और बस यह परीक्षण कर सकते हैं कि संदेश एन्कैप्सुलेटेड ऑब्जेक्ट पर ठीक से पास हुआ है या नहीं।

और आपका जटिल परीक्षण, जिसे आप पहली बार में चिंता कर रहे थे, केवल StandardHerbivoreEatingStrategy का परीक्षण करना है। एक वर्ग, परीक्षणों का एक समूह, चिंता करने के लिए कोई डुप्लिकेट कोड नहीं।

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


+1 रणनीति पैटर्न पहली बात थी जो प्रश्न को पढ़ने में मेरे दिमाग में आई थी।
स्टुपरयूज़र

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

महान सवाल, महान जवाब। आपके पास निजी कंस्ट्रक्टर नहीं है; आप IHerbivoreEatingStrategy से StandardHerbivoreEatingStrategy को Io के कंटेनर का उपयोग करके तार कर सकते हैं।
अझेग्लोव

@ScottWhitlock: "मुझे नहीं लगता कि इंटरफ़ेस को व्यवहार का वादा करना चाहिए। परीक्षणों को ऐसा करना चाहिए।" बिलकुल यही मैं कह रहा हूं। यदि यह व्यवहार का वादा करता है, तो आपको इससे छुटकारा पाना चाहिए और बस (आधार-) वर्ग का उपयोग करना चाहिए। आपको इसके परीक्षण की आवश्यकता नहीं है।
pdr

@azheglov: सहमत हूं, लेकिन मेरा जवाब काफी पहले ही था :)
पीडीआर

1

यह एक ही कोड को कई बार परीक्षण करने के लिए बेमानी लगता है, लेकिन ऐसे मामले हैं जहां उपवर्ग बेस क्लास की कार्यक्षमता को ओवरराइड और कर सकते हैं

एक दौर के बारे में आप पूछ रहे हैं कि कुछ परीक्षणों को छोड़ने के लिए सफेद बॉक्स ज्ञान का उपयोग करना उचित है या नहीं। एक ब्लैक-बॉक्स परिप्रेक्ष्य से, Lionऔर Tigerविभिन्न वर्ग हैं। तो कोड से परिचित कोई भी उनका परीक्षण करेगा, लेकिन आप गहन कार्यान्वयन ज्ञान के साथ जानते हैं कि आप बस एक जानवर का परीक्षण कर सकते हैं।

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

[TestMethod]
public void Tiger_can_eat_herbivore()
{
    IHerbivoreEater_can_eat_herbivore(BuildTigerForTest);
}

ऐसा करना और सहना बहुत आसान होना चाहिए और यह सुनिश्चित करेगा कि आप यह पता लगा सकें कि वस्तुएं अपने अनुबंध को पूरा करने में विफल रहती हैं या नहीं।


मुझे आश्चर्य है कि अगर यह प्रश्न वास्तव में ब्लैक-बॉक्स बनाम व्हाइट-बॉक्स परीक्षण की वरीयता के लिए उबलता है। मैं ब्लैक-बॉक्स कैंप की ओर झुकता हूं, शायद यही वजह है कि मैं जिस तरह से परीक्षण कर रहा हूं। यह बात बताने के लिए धन्यवाद।
स्कॉट व्हिटलॉक 19

1

आप इसे सही कर रहे हैं। अपने नए कोड के एकल उपयोग के व्यवहार के परीक्षण के रूप में एक इकाई परीक्षण के बारे में सोचें। यह वही कॉल है जो आप उत्पादन कोड से करेंगे।

उस स्थिति में, आप पूरी तरह से सही हैं; लॉयन या टाइगर का उपयोगकर्ता (कम से कम) को यह ध्यान नहीं रहेगा कि वे दोनों हर्बीवोरएटर हैं और कोड जो वास्तव में विधि के लिए चलता है, बेस क्लास में दोनों के लिए आम है। इसी तरह, एक HerbivoreEater सार (कंक्रीट शेर या टाइगर द्वारा प्रदान) का एक उपयोगकर्ता परवाह नहीं करेगा जो यह है। उन्हें इस बात की परवाह है कि उनका शेर, टाइगर, या हर्बीवोरएटर के अज्ञात ठोस कार्यान्वयन को खाएगा () एक शाकाहारी को सही ढंग से खाएगा।

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

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