क्या मुझे विरासत में मिली विधियों का परीक्षण करना चाहिए?


30

मान लीजिए कि मेरे पास एक वर्ग प्रबंधक है जो एक आधार वर्ग के कर्मचारी से प्राप्त होता है , और उस कर्मचारी के पास एक विधि getEmail () है जो प्रबंधक द्वारा विरासत में मिली है । क्या मुझे परीक्षण करना चाहिए कि एक प्रबंधक के व्यवहार () विधि का व्यवहार वास्तव में एक कर्मचारी के समान है?

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

(ध्यान दें कि परीक्षण प्रबंधक :: getEmail () विधि कोड कवरेज में सुधार नहीं करता है (या वास्तव में किसी भी अन्य कोड गुणवत्ता मैट्रिक्स (?)) के लिए प्रबंधक तक : getEmail () बनाया / अधिग्रहित है।)

(यदि उत्तर "हां" है, तो आधार और व्युत्पन्न वर्गों के बीच साझा किए जाने वाले प्रबंध परीक्षणों के बारे में कुछ जानकारी उपयोगी होगी।)

प्रश्न का एक समतुल्य सूत्रीकरण:

यदि एक व्युत्पन्न वर्ग बेस क्लास से एक विधि प्राप्त करता है, तो आप कैसे व्यक्त (परीक्षण) करते हैं कि आप विरासत में मिली विधि की अपेक्षा कर रहे हैं:

  1. ठीक उसी तरह से व्यवहार करें जैसे आधार अभी करता है (यदि आधार का व्यवहार बदलता है, तो व्युत्पन्न विधि का व्यवहार नहीं होता है);
  2. सभी समय के लिए आधार के समान व्यवहार करें (यदि आधार वर्ग का व्यवहार बदलता है, तो व्युत्पन्न वर्ग का व्यवहार भी बदल जाता है); या
  3. हालांकि यह व्यवहार करना चाहता है (आप इस पद्धति के व्यवहार की परवाह नहीं करते हैं क्योंकि आप इसे कभी नहीं कहते हैं)।

1
IMO से एक Managerवर्ग प्राप्त Employeeकरना पहली बड़ी गलती थी।
कोडइन्चौस

4
@CodesInChaos हां, यह एक बुरा उदाहरण हो सकता है। लेकिन जब भी आपको विरासत मिलती है तो यही समस्या लागू होती है।
एमजे

1
आपको विधि को ओवरराइड करने की भी आवश्यकता नहीं है। बेस क्लास विधि अन्य उदाहरण विधियों को कह सकती है जो ओवरराइड होती हैं, और विधि के लिए समान स्रोत कोड निष्पादित करना अभी भी अलग व्यवहार बनाता है।
gnasher729

जवाबों:


23

मैं यहाँ व्यावहारिक दृष्टिकोण लेता हूँ: यदि कोई व्यक्ति, भविष्य में, प्रबंधक :: getMail को ओवरराइड करता है, तो यह उस डेवलपर की जिम्मेदारी है कि वह नए तरीके के लिए टेस्ट कोड प्रदान करे।

निश्चित रूप से यह तभी मान्य होगा जब Manager::getEmailवास्तव में समान कोड पथ हो Employee::getEmail! यहां तक ​​कि अगर विधि का उपयोग करने की अनुमति नहीं है, तो यह अलग तरह से व्यवहार कर सकता है:

  • Employee::getEmailकुछ संरक्षित वर्चुअल को कॉल कर सकता है getInternalEmailजो कि ओवरराइड है Manager
  • Employee::getEmailकुछ आंतरिक स्थिति (जैसे कुछ फ़ील्ड _email) तक पहुँच सकता है , जो दो कार्यान्वयनों में भिन्न हो सकते हैं: उदाहरण के लिए, डिफ़ॉल्ट कार्यान्वयन Employeeयह सुनिश्चित कर सकता है कि _emailहमेशा बना रहे firstname.lastname@example.com, लेकिन Managerमेल पते निर्दिष्ट करने में अधिक लचीला है।

ऐसे मामलों में, यह संभव है कि एक बग केवल स्वयं में प्रकट होता है Manager::getEmail, भले ही विधि का कार्यान्वयन स्वयं ही हो। उस मामले में Manager::getEmailअलग से परीक्षण से समझ में आ सकता है।


14

मैं।

यदि आप "अच्छी तरह से सोच रहे हैं, तो यह वास्तव में केवल कर्मचारी कह रहा है :: getEmail () क्योंकि मैं इसे ओवरराइड नहीं करता हूं, इसलिए मुझे प्रबंधक का परीक्षण करने की आवश्यकता नहीं है :: getEmail ()" तो आप वास्तव में व्यवहार का परीक्षण नहीं कर रहे हैं of मैनेजर :: getEmail ()।

मुझे लगता है कि केवल प्रबंधक :: getEmail () को क्या करना चाहिए, क्या यह विरासत में मिला है या ओवरराइड नहीं है। यदि प्रबंधक का व्यवहार :: getEmail () जो भी कर्मचारी :: getMail () रिटर्न लौटाता है, तो वह परीक्षण है। यदि व्यवहार को "pink@unicorns.com" वापस करना है, तो यह परीक्षा है। इससे कोई फर्क नहीं पड़ता कि यह विरासत द्वारा लागू किया गया है या ओवरराइड किया गया है।

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

कुछ लोग चेक में प्रतीत होने वाली अतिरेक से असहमत हो सकते हैं, लेकिन मेरा काउंटर यह है कि आप व्यवहार का परीक्षण कर रहे होंगे कर्मचारी :: getMail () और प्रबंधक :: getMail () अलग-अलग तरीकों के रूप में, चाहे वे विरासत में मिले हों या नहीं; ओवरराइड। अगर किसी भविष्य के डेवलपर को प्रबंधक :: getMail () के व्यवहार को बदलने की आवश्यकता है, तो उन्हें परीक्षणों को भी अपडेट करने की आवश्यकता है।

राय भिन्न हो सकती है, मुझे लगता है कि डिगर और हेंजजी ने विपरीत के लिए उचित औचित्य दिए।


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

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

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

5

इकाई परीक्षण मत करो। कार्यात्मक / स्वीकृति परीक्षण करें।

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

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


4

TDD के लिए रॉबर्ट मार्टिन के नियम हैं:

  1. आपको किसी भी उत्पादन कोड को लिखने की अनुमति नहीं है जब तक कि यह एक असफल इकाई परीक्षण पास न हो।
  2. आपको एक इकाई परीक्षण लिखने की अनुमति नहीं है जो असफल होने के लिए पर्याप्त है; और संकलन विफलताओं विफलताओं हैं।
  3. आपको किसी भी अधिक उत्पादन कोड को लिखने की अनुमति नहीं है, एक असफल इकाई परीक्षा पास करने के लिए पर्याप्त है।

यदि आप इन नियमों का पालन करते हैं, तो आप यह सवाल पूछने की स्थिति में नहीं आ पाएंगे। यदि getEmailविधि का परीक्षण करने की आवश्यकता है, तो इसका परीक्षण किया गया होगा।


3

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

जिस तरह से मैं इसका परीक्षण करता हूं, वह (संभवतः सार) बेस क्लास या इंटरफ़ेस का परीक्षण करने के लिए एक सार वर्ग बना रहा है, इस तरह (C # में NUnit का उपयोग करके):

public abstract class EmployeeTests
{
    protected abstract Employee CreateInstance(string name, int age);

    [Test]
    public void GetEmail_ReturnsValidEmailAddress()
    {
        // Given
        var sut = CreateInstance("John Doe", 20);

        // When
        string email = sut.GetEmail();

        // Then
        Assert.IsTrue(Helper.IsValidEmail(email));
    }
}

फिर, मेरे पास विशिष्ट के साथ परीक्षण के साथ एक वर्ग है Manager, और कर्मचारी के परीक्षणों को इस तरह एकीकृत करता है:

[TestFixture]
public class ManagerTests
{
    // Other tests.

    [TestFixture]
    public class ManagerEmployeeTests : EmployeeTests
    {
        protected override Employee CreateInstance(string name, int age);
        {
            return new Manager(name, age);
        }
    }
}

इसका कारण मैं यह कर सकता हूं कि यह लिस्कोव का प्रतिस्थापन सिद्धांत है: Employeeकिसी Managerवस्तु को पारित करने के बाद भी परीक्षण करना चाहिए , क्योंकि यह व्युत्पन्न है Employee। इसलिए मुझे केवल एक बार अपने परीक्षण लिखने हैं, और वे सत्यापित कर सकते हैं कि वे इंटरफ़ेस या बेस क्लास के सभी संभावित कार्यान्वयन के लिए काम करते हैं।


अच्छी बात फिर से लिस्कोव के प्रतिस्थापन सिद्धांत, आप सही हैं कि यदि यह धारण करता है, तो व्युत्पन्न वर्ग सभी आधार वर्ग के परीक्षण पास करेगा। हालांकि, LSP का अक्सर उल्लंघन किया जाता है, जिसमें xUnit की setUp()विधि भी शामिल है! और बहुत अधिक हर वेब एमवीसी फ्रेमवर्क जिसमें "इंडेक्स" पद्धति को ओवरराइड करना शामिल है, वह एलएसपी को भी तोड़ता है, जो मूल रूप से उन सभी में से एक है।
एमजे

यह बिल्कुल सही उत्तर है - मैंने इसे "एब्सट्रैक्ट टेस्ट पैटर्न" कहा है। जिज्ञासा से बाहर, केवल परीक्षण के माध्यम से मिश्रण करने के बजाय एक नेस्टेड वर्ग का उपयोग क्यों करें class ManagerTests : ExployeeTests? (यानी परीक्षण के तहत कक्षाओं की विरासत को प्रतिबिंबित करते हुए।) क्या यह मुख्य रूप से सौंदर्यशास्त्र है, परिणाम देखने में सहायता करने के लिए?
ल्यूक उशेरवुड

2

क्या मुझे परीक्षण करना चाहिए कि एक प्रबंधक के व्यवहार () विधि का व्यवहार वास्तव में एक कर्मचारी के समान है?

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

जिस समय इन परीक्षणों को लिखा जाता है व्यवहार समान होगा, लेकिन निश्चित रूप से भविष्य में किसी बिंदु पर कोई इस पद्धति को ओवरराइड कर सकता है, अपने व्यवहार को बदल सकता है

यदि विधि ओवरराइड हो गई है, तो आपको ओवरराइड व्यवहार की जांच के लिए नए परीक्षणों की आवश्यकता होगी। यह ओवरराइडिंग getEmail()पद्धति को लागू करने वाले व्यक्ति का काम है ।


1

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

इस परिदृश्य पर विचार करें: ईमेल पता Firstname.Lastname@example.com के रूप में इकट्ठा किया गया है:

class Employee{
    String firstname, lastname;
    String getEmail() { 
        return firstname + "." + lastname + "@example.com";
    }
}

आपने इकाई का परीक्षण कर लिया है और यह आपके लिए ठीक काम करता है Employee। आपने एक वर्ग भी बनाया है Manager:

class Manager extends Employee { /* nothing different in email generation */ }

अब आपके पास एक वर्ग है ManagerSortजो प्रबंधकों को उनके ईमेल पते के आधार पर सूची में सॉर्ट करता है। आपके द्वारा यह माना जाता है कि ईमेल पीढ़ी उसी में है Employee:

class ManagerSort {
    void sortManagers(Manager[] managerArrayToBeSorted)
        // sort based on email address omitted
    }
}

आप अपने लिए एक परीक्षा लिखते हैं ManagerSort:

void testManagerSort() {
    Manager[] managers = ... // build random manager list
    ManagerSort.sortManagers(managers);

    Manager[] expected = ... // expected result
    assertEquals(expected, managers); // check the result
}

सब कुछ ठीक काम करता है। अब कोई आता है और getEmail()विधि से आगे निकल जाता है:

class Manager extends Employee {
    String getEmail(){
        // managers should have their lastname and firstname order changed
        return lastname + "." + firstname + "@example.com";
    }
}

अब, क्या होता है? testManagerSort()अति getEmail()की वजह से आपकी असफलता होगी Manager। आप इस मुद्दे की जांच करेंगे और इसका कारण जानेंगे। और सभी विरासत में मिली विधि के लिए एक अलग टेस्टकेस लिखे बिना।

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

अन्यथा, जावा में जैसे, आप सभी तरीकों से विरासत में मिली परीक्षण करने के लिए होता है Objectकी तरह toString(), equals()हर कक्षा में आदि।


आप इकाई परीक्षणों के साथ चतुर होने वाले नहीं हैं। आप कहते हैं कि "मुझे X का परीक्षण करने की आवश्यकता नहीं है क्योंकि जब X विफल होता है, तो Y विफल होता है"। लेकिन इकाई परीक्षण आपके कोड में बग की धारणाओं पर आधारित हैं। आपके कोड में बग के साथ, आप यह क्यों सोचेंगे कि परीक्षणों के बीच जटिल संबंध उस तरह से काम करते हैं जिस तरह से आप उनसे काम करने की उम्मीद करते हैं?
gnasher729

@ gnasher729 मैं कहता हूं "मुझे उपवर्ग में एक्स का परीक्षण करने की आवश्यकता नहीं है क्योंकि यह सुपरक्लास के लिए यूनिट परीक्षणों में पहले से ही परीक्षण किया गया है "। बेशक, यदि आप एक्स के कार्यान्वयन को बदलते हैं, तो आपको इसके लिए उपयुक्त परीक्षण लिखना होगा।
उऊ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.