2008-08-14 11 views
10

मान लें कि मेरे पास एक जटिल .NET क्लास है, जिसमें बहुत सारे सरणी और अन्य क्लास ऑब्जेक्ट सदस्य हैं। मुझे इस ऑब्जेक्ट का एक गहरा क्लोन उत्पन्न करने में सक्षम होना चाहिए - इसलिए मैं क्लोन() विधि लिखता हूं, और इसे सरल बाइनरीफॉर्मेटर धारावाहिक/deserialize के साथ कार्यान्वित करता हूं - या शायद मैं किसी अन्य तकनीक का उपयोग करके गहरा क्लोन करता हूं जो अधिक त्रुटि प्रवण होता है और मैं निश्चित रूप से परीक्षण करना चाहता हूं।गहरे क्लोनिंग के लिए यूनिट परीक्षण

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

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

उत्तर

1

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

आपको अपनी ऑब्जेक्ट क्लोन करने की आवश्यकता है और फिर प्रत्येक ऑब्जेक्ट और वैरिएबल से गुजरना है कि आपके ऑब्जेक्ट में यह निर्धारित है कि यह सही ढंग से कॉपी किया गया है या सही तरीके से क्लोन किया गया है या नहीं।

2

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

अपने विशिष्ट सवालों के जवाब देने के लिए:

आप लिखते है (या एक बार आप क्लोन के लिए जरूरत की खोज को फिर से लिखने) वर्ग के लिए अपने सभी इकाई परीक्षण इतना है कि वे या तो 'कुंवारी' के साथ लागू किया जा सकता है वस्तु या इसके एक क्लोन के साथ?

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

क्लोनिंग का हिस्सा पर्याप्त गहरा नहीं था, तो आप कैसे परीक्षण करेंगे - क्योंकि यह केवल ऐसी समस्या है जो बाद में घृणास्पद-बग बग दे सकती है?

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

1

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

इसके अलावा, मैं इस

[Conditional("DEBUG")] 
public static void DebugAssertValueEquality<T>(T current, T other, bool expected, 
               params string[] ignoredFields) { 
    if (null == current) 
    { throw new ArgumentNullException("current"); } 
    if (null == ignoredFields) 
    { ignoredFields = new string[] { }; } 

    FieldInfo lastField = null; 
    bool test; 
    if (object.ReferenceEquals(other, null)) 
    { Debug.Assert(false == expected, "The other object was null"); return; } 
    test = true; 
    foreach (FieldInfo fi in current.GetType().GetFields(BindingFlags.Instance)) { 
     if (test = false) { break; } 
     if (0 <= Array.IndexOf<string>(ignoredFields, fi.Name)) 
     { continue; } 
     lastField = fi; 
     object leftValue = fi.GetValue(current); 
     object rightValue = fi.GetValue(other); 
     if (object.ReferenceEquals(null, leftValue)) { 
      if (!object.ReferenceEquals(null, rightValue)) 
      { test = false; } 
     } 
     else if (object.ReferenceEquals(null, rightValue)) 
     { test = false; } 
     else { 
      if (!leftValue.Equals(rightValue)) 
      { test = false; } 
     } 
    } 
    Debug.Assert(test == expected, string.Format("field: {0}", lastField)); 
} 

यह पद्धति किसी नेस्टेड सदस्यों पर बराबर का सही कार्यान्वयन पर निर्भर करता है की तरह कुछ का उपयोग कर मेरी क्लोन कार्यान्वयन के सभी के लिए एक डिबग मोड की जांच को जोड़ने के लिए है, लेकिन मेरे मामले में कुछ भी की तरह वह यह है कि cloneable भी equatable है

2

वहाँ एक बहुत स्पष्ट समाधान है कि लगभग उतना ही काम नहीं ले करता है:

  1. एक द्वि में वस्तु को क्रमानुसार नारी प्रारूप।
  2. ऑब्जेक्ट क्लोन करें।
  3. क्लोन को बाइनरी प्रारूप में सीरियलाइज़ करें।
  4. बाइट्स की तुलना करें।

मानते हैं कि धारावाहिक कार्य करता है - और यह बेहतर है क्योंकि आप इसे क्लोन करने के लिए उपयोग कर रहे हैं - इसे बनाए रखना आसान होना चाहिए। वास्तव में, यह परिवर्तन से आपकी कक्षा की संरचना में पूरी तरह से encapsulated जाएगा।

1

मैं आमतौर पर दो वस्तुओं की गहराई से तुलना करने के लिए Equals() लागू करता हूं। आपको अपने उत्पादन कोड में इसकी आवश्यकता नहीं हो सकती है लेकिन यह अभी भी बाद में काम में आ सकती है और परीक्षण कोड बहुत साफ है।

0

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

public static class TestDeepClone 
    { 
     private static readonly List<long> objectIDs = new List<long>(); 
     private static readonly ObjectIDGenerator objectIdGenerator = new ObjectIDGenerator(); 

     public static bool DefaultCloneExclusionsCheck(Object obj) 
     { 
      return 
       obj is ValueType || 
       obj is string || 
       obj is Delegate || 
       obj is IEnumerable; 
     } 

     /// <summary> 
     /// Executes various assertions to ensure the validity of a deep copy for any object including its compositions 
     /// </summary> 
     /// <param name="original">The original object</param> 
     /// <param name="copy">The cloned object</param> 
     /// <param name="checkExclude">A predicate for any exclusions to be done, i.e not to expect IPolicy items to be cloned</param> 
     public static void AssertDeepClone(this Object original, Object copy, Predicate<object> checkExclude) 
     { 
      bool isKnown; 
      if (original == null) return; 
      if (copy == null) Assert.Fail("Copy is null while original is not", original, copy); 

      var id = objectIdGenerator.GetId(original, out isKnown); //Avoid checking the same object more than once 
      if (!objectIDs.Contains(id)) 
      { 
       objectIDs.Add(id); 
      } 
      else 
      { 
       return; 
      } 

      if (!checkExclude(original)) 
      { 
       Assert.That(ReferenceEquals(original, copy) == false); 
      } 

      Type type = original.GetType(); 
      PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); 
      FieldInfo[] fieldInfos = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); 

      foreach (PropertyInfo memberInfo in propertyInfos) 
      { 
       var getmethod = memberInfo.GetGetMethod(); 
       if (getmethod == null) continue; 
       var originalValue = getmethod.Invoke(original, new object[] { }); 
       var copyValue = getmethod.Invoke(copy, new object[] { }); 
       if (originalValue == null) continue; 
       if (!checkExclude(originalValue)) 
       { 
        Assert.That(ReferenceEquals(originalValue, copyValue) == false); 
       } 

       if (originalValue is IEnumerable && !(originalValue is string)) 
       { 
        var originalValueEnumerable = originalValue as IEnumerable; 
        var copyValueEnumerable = copyValue as IEnumerable; 
        if (copyValueEnumerable == null) Assert.Fail("Copy is null while original is not", new[] { original, copy }); 
        int count = 0; 
        List<object> items = copyValueEnumerable.Cast<object>().ToList(); 
        foreach (object o in originalValueEnumerable) 
        { 
         AssertDeepClone(o, items[count], checkExclude); 
         count++; 
        } 
       } 
       else 
       { 
        //Recurse over reference types to check deep clone success 
        if (!checkExclude(originalValue)) 
        { 
         AssertDeepClone(originalValue, copyValue, checkExclude); 
        } 

        if (originalValue is ValueType && !(originalValue is Guid)) 
        { 
         //check value of non reference type 
         Assert.That(originalValue.Equals(copyValue)); 
        } 
       } 

      } 

      foreach (FieldInfo fieldInfo in fieldInfos) 
      { 
       var originalValue = fieldInfo.GetValue(original); 
       var copyValue = fieldInfo.GetValue(copy); 
       if (originalValue == null) continue; 
       if (!checkExclude(originalValue)) 
       { 
        Assert.That(ReferenceEquals(originalValue, copyValue) == false); 
       } 

       if (originalValue is IEnumerable && !(originalValue is string)) 
       { 
        var originalValueEnumerable = originalValue as IEnumerable; 
        var copyValueEnumerable = copyValue as IEnumerable; 
        if (copyValueEnumerable == null) Assert.Fail("Copy is null while original is not", new[] { original, copy }); 
        int count = 0; 
        List<object> items = copyValueEnumerable.Cast<object>().ToList(); 
        foreach (object o in originalValueEnumerable) 
        { 
         AssertDeepClone(o, items[count], checkExclude); 
         count++; 
        } 
       } 
       else 
       { 
        //Recurse over reference types to check deep clone success 
        if (!checkExclude(originalValue)) 
        { 
         AssertDeepClone(originalValue, copyValue, checkExclude); 
        } 
        if (originalValue is ValueType && !(originalValue is Guid)) 
        { 
         //check value of non reference type 
         Assert.That(originalValue.Equals(copyValue)); 
        } 
       } 
      } 
     } 
    } 
संबंधित मुद्दे