क्या परीक्षण के तहत कक्षा का फर्जी हिस्सा ठीक है?


22

मान लीजिए कि मेरे पास एक वर्ग है (आकस्मिक उदाहरण और इसके खराब डिजाइन को माफ करें):

class MyProfit
{
  public decimal GetNewYorkRevenue();
  public decimal GetNewYorkExpenses();
  public decimal GetNewYorkProfit();

  public decimal GetMiamiRevenue();
  public decimal GetMiamiExpenses();
  public decimal GetMiamiProfit();

  public bool BothCitiesProfitable();

}

(नोट GetxxxRevenue () और GetxxxExpenses () विधियों में निर्भरताएं हैं जो बाहर रखी गई हैं)

अब मैं UnitCitiesProfitable () का परीक्षण कर रहा हूं जो GetNewYorkProfit () और GetM मियामीProfit () पर निर्भर करता है। GetNewYorkProfit () और GetM मियामीProfit () को स्टब करना ठीक है?

ऐसा लगता है कि अगर मैं नहीं करता हूं, तो मैं एक साथ GetNewYorkProfit () और GetM मियामीProfit () के साथ BothCitiesProfitable () का परीक्षण कर रहा हूं। मुझे यह सुनिश्चित करना होगा कि मैं GetxxxRevenue () और GetxxxExpenses () के लिए स्टबिंग सेट कर दूं ताकि गेटएक्सएक्सएक्सप्रोफ़िट () तरीके सही मान लौटाएं।

अब तक मैंने केवल बाहरी तरीकों पर नहीं आंतरिक तरीकों पर निर्भरता को रोकने का उदाहरण देखा है।

और अगर यह ठीक है, तो क्या मुझे ऐसा करने के लिए एक विशेष पैटर्न का उपयोग करना चाहिए?

अद्यतन करें

मुझे चिंता है कि हम मूल मुद्दे को याद कर रहे हैं और शायद यह मेरे खराब उदाहरण का दोष है। मूल प्रश्न यह है: यदि किसी वर्ग में एक विधि का किसी अन्य सार्वजनिक रूप से उजागर विधि पर निर्भरता है, तो क्या उस अन्य विधि को ठीक करने के लिए यह (या यहां तक ​​कि अनुशंसित) ठीक है?

शायद मुझे कुछ याद आ रहा है, लेकिन मुझे यकीन नहीं है कि कक्षा को हमेशा विभाजित करना समझ में आता है। शायद एक और न्यूनतम बेहतर उदाहरण होगा:

class Person
{
 public string FirstName()
 public string LastName()
 public string FullName()
}

जहाँ पूरा नाम इस प्रकार है:

public string FullName()
{
  return FirstName() + " " + LastName();
}

क्या फ़र्स्टनेम () का परीक्षण करते समय फर्स्टनाम () और लास्टनाम () को स्टब करना ठीक है?


यदि आप एक साथ कुछ कोड का परीक्षण करते हैं, तो यह कैसे खराब हो सकता है? समस्या क्या है? आम तौर पर, कोड का अधिक बार और अधिक गहनता से परीक्षण करना बेहतर होना चाहिए।
उपयोगकर्ता अज्ञात

बस आपके अपडेट के बारे में पता चला, मैंने अपना जवाब अपडेट किया है
विंस्टन एवर्ट

जवाबों:


27

आपको प्रश्न के तहत कक्षा को तोड़ देना चाहिए।

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

इस डिजाइन की नासमझी को अनदेखा करना:

class NewYork
{
    decimal GetRevenue();
    decimal GetExpenses();
    decimal GetProfit();
}


class Miami
{
    decimal GetRevenue();
    decimal GetExpenses();
    decimal GetProfit();
}

class MyProfit
{
     MyProfit(NewYork new_york, Miami miami);
     boolean bothProfitable();
}

अद्यतन करें

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

तथ्य यह है कि FullName FirstName और LastName का उपयोग करता है एक कार्यान्वयन विवरण है। वर्ग के बाहर कुछ भी परवाह नहीं करनी चाहिए कि यह सच है। ऑब्जेक्ट का परीक्षण करने के लिए सार्वजनिक तरीकों का मज़ाक उड़ाते हुए, आप उस चीज़ के बारे में एक धारणा बना रहे हैं।

भविष्य में कुछ बिंदु पर, यह धारणा सही हो सकती है। शायद नाम तर्क के सभी एक नाम वस्तु के लिए स्थानांतरित कर दिया जाएगा जिसे व्यक्ति बस कहता है। शायद FullName सीधे सदस्य चरों को पहले_नाम और last_name तक पहुँचाएगा, फिर FirstName और LastName को कॉल करेगा।

दूसरा सवाल यह है कि आपको ऐसा करने की आवश्यकता क्यों महसूस होती है। आखिर आपके व्यक्ति वर्ग का परीक्षण कुछ इस तरह किया जा सकता है:

Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");

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

केवल ऐसा मामला जहां यह प्रतीत होता है कि FullName द्वारा उपयोग की जाने वाली विधियों के लिए यह गलत है कि क्या FirstName () और LastName () गैर-तुच्छ कार्य थे। हो सकता है कि आप उन यादृच्छिक नाम जनरेटर, या FirstName और LastName में से किसी एक को उत्तर के लिए डेटाबेस या कुछ लिख रहे हों। लेकिन अगर ऐसा हो रहा है तो यह सुझाव देता है कि वस्तु कुछ ऐसा कर रही है जो व्यक्ति वर्ग में नहीं है।

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

यदि आपकी कक्षा सरल है, तो आपको परीक्षण के दौरान इसके टुकड़ों का मजाक उड़ाने की आवश्यकता महसूस नहीं होनी चाहिए। यदि आपकी कक्षा इतनी जटिल है कि आपको नकली की आवश्यकता महसूस होती है, तो आपको कक्षा को सरल टुकड़ों में तोड़ देना चाहिए।

अद्यतन रखें

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

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

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

लेकिन, क्या फर्क पड़ता है? यदि FirstName () और LastName () किसी अन्य ऑब्जेक्ट के सदस्य हैं तो क्या यह वास्तव में FullName () का मुद्दा बदल देता है? अगर हम यह तय करते हैं कि फर्स्टनाम और मेज़नाम का मजाक उड़ाना वास्तव में उनके लिए एक और वस्तु पर होना है?

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

संपादित करें

चलो एक कदम पीछे लेते हैं और पूछते हैं: जब हम परीक्षण करते हैं तो हम वस्तुओं का मजाक क्यों उड़ाते हैं?

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

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

मुझे लगता है कि आपकी चिंता कारण संख्या 5 है। चिंता का विषय यह है कि भविष्य में फर्स्टनाम और लास्टनाम के कार्यान्वयन में बदलाव से परीक्षण टूट जाएगा। भविष्य में FirstName और LastName एक अलग स्थान या स्रोत से नाम प्राप्त कर सकते हैं। लेकिन FullName शायद हमेशा रहेगा FirstName() + " " + LastName()। इसलिए आप FirstName और LastName का मजाक उड़ाते हुए FullName का परीक्षण करना चाहते हैं।

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

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

मुझे संदेह है कि आपको अपनी वस्तु को विभाजित करने में आपत्ति हो सकती है, लेकिन क्यों?

संपादित करें

मैं गलत था।

आपको अलग-अलग तरीकों का इस्तेमाल करके वस्तुओं को विभाजित करना चाहिए, फिर अलग-अलग तरीकों का इस्तेमाल करके तदर्थ विभाजन करना चाहिए। हालाँकि, मैं वस्तुओं के विभाजन की एक विधि पर अत्यधिक ध्यान केंद्रित कर रहा था। हालाँकि, OO किसी ऑब्जेक्ट को विभाजित करने के कई तरीके प्रदान करता है।

मैं क्या प्रस्ताव दूंगा:

class PersonBase
{
      abstract sring FirstName();
      abstract string LastName();

      string FullName()
      {
            return FirstName() + " " + LastName();
      }
 }

 class Person extends PersonBase
 {
      string FirstName(); 
      string LastName();
 }

 class FakePerson extends PersonBase
 {
      void setFirstName(string);
      void setLastName(string);
      string getFirstName();
      string getLastName();
 }

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

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


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

2
"वस्तु का परीक्षण करने के लिए सार्वजनिक तरीकों का मज़ाक उड़ाकर, आप [कैसे] उस वस्तु के बारे में एक धारणा बना रहे हैं।" लेकिन ऐसा नहीं है कि किसी भी समय आप किसी वस्तु को ठुकरा दें? उदाहरण के लिए, मान लीजिए कि मुझे पता है कि मेरा तरीका xyz () किसी अन्य वस्तु को कहता है। अलगाव में xyz () का परीक्षण करने के लिए, मुझे दूसरी वस्तु को स्टब करना होगा। इसलिए मेरे परीक्षण को मेरे xyz () पद्धति के कार्यान्वयन विवरण के बारे में पता होना चाहिए।
उपयोगकर्ता

मेरे मामले में "FirstName ()" और "LastName ()" विधियां सरल हैं, लेकिन वे अपने परिणाम के लिए एक तृतीय पक्ष एपीआई की क्वेरी करते हैं।
उपयोगकर्ता

@User, अपडेट किया गया, दृढ़ता से आप FirstName और LastName का परीक्षण करने के लिए 3rd पार्टी एपीआई का मज़ाक उड़ा रहे हैं। FullName का परीक्षण करते समय एक ही मॉकिंग करने में क्या गलत है?
विंस्टन एवर्ट

@Winston, यह मेरे पूरे बिंदु की तरह है, वर्तमान में मैं fullname () का परीक्षण करने के लिए firstname और lastname में इस्तेमाल की गई 3rd पार्टी एपीआई का मजाक उड़ाता हूं, लेकिन मैं इस बात का ध्यान नहीं रखना चाहूंगा कि fullname और बेशकनाम को तब लागू किया जाता है जब fullname (बेशक) का परीक्षण किया जाता है यह ठीक है जब मैं Firstname और lastname का परीक्षण कर रहा हूं)। इसलिए फर्स्टनाम और लास्टनाम के बारे में मेरा सवाल।
उपयोगकर्ता

10

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

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


1
माना। लेकिन जो कोई भी इसे पढ़ता है उसके लिए बिंदु पर जोर देने के लिए, यह समाधान आप उपयोग करते हैं यदि आप बेहतर समाधान नहीं कर सकते हैं।
विंस्टन एवर्ट

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

@ Péter Török, अच्छी बात है। हालांकि मुझे लगता है कि इसके अंतर्गत "बेहतर समाधान नहीं हो सकता।" :)
विंस्टन Ewert

@allProfit () विधि का परीक्षण करना बहुत मुश्किल होगा क्योंकि getExpenses () और getRevenues () के लिए अलग-अलग परिदृश्य वास्तव में getProfit () के लिए आवश्यक परिदृश्यों को गुणा करेंगे। अगर हम getExpenses () का परीक्षण कर रहे हैं और Revenues () को स्वतंत्र रूप से प्राप्त कर रहे हैं, तो क्या GetProfit () के परीक्षण के लिए इन तरीकों का मजाक उड़ाना अच्छा नहीं है?
मोहम्मद फरीद

7

अपने उदाहरणों को और सरल बनाने के लिए, यह कहें कि आप परीक्षण कर रहे हैं C(), जो निर्भर करता है A()और B(), जिनमें से प्रत्येक की अपनी निर्भरताएँ हैं। IMO यह बताता है कि आपका परीक्षण क्या हासिल करने की कोशिश कर रहा है।

आप के व्यवहार परीक्षण कर रहे हैं C()के ज्ञात दिया व्यवहार A()और B()उसके बाद यह शायद सबसे आसान और सबसे अच्छा बाहर ठूंठ है A()औरB() । इसे शायद शुद्धतावादियों द्वारा एक इकाई परीक्षण कहा जाएगा।

यदि आप पूरे सिस्टम के व्यवहार का परीक्षण कर रहे हैं ( C()मैं 'परिप्रेक्ष्य से), तो मैं छोड़ देता हूं A()और B()लागू किया जाता है और या तो उनकी निर्भरता को रोक देता है (यदि यह परीक्षण करना संभव बनाता है) या सैंडबॉक्स वातावरण सेट करें, जैसे कि परीक्षण डेटाबेस। मैं इसे एकीकरण परीक्षण कहूंगा।

दोनों दृष्टिकोण मान्य हो सकते हैं, इसलिए यदि इसे डिज़ाइन किया गया है, तो आप इसे या तो ऊपर-सामने परीक्षण कर सकते हैं यह संभवतः लंबे समय में बेहतर होगा।

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