2010-04-02 13 views
12

का उपयोग करके सी # में उठाया गया है, मैं परीक्षण करना चाहता हूं कि एक निश्चित संपत्ति (या अधिक सामान्य रूप से, कुछ कोड निष्पादित करना) मेरे ऑब्जेक्ट पर एक निश्चित घटना उठाती है। उस संबंध में मेरी समस्या Unit testing that an event is raised in C# के समान है, लेकिन मुझे इन परीक्षणों में से बहुत कुछ चाहिए और मुझे बॉयलरप्लेट से नफरत है। इसलिए मैं प्रतिबिंब का उपयोग कर एक और सामान्य समाधान की तलाश में हूं।यूनिट परीक्षण जो प्रतिबिंब

आदर्श रूप में, मैं कुछ इस तरह करना चाहते हैं:

[TestMethod] 
public void TestWidth() { 
    MyClass myObject = new MyClass(); 
    AssertRaisesEvent(() => { myObject.Width = 42; }, myObject, "WidthChanged"); 
} 

AssertRaisesEvent के कार्यान्वयन के लिए, मैं इस दूर आ गए:

private void AssertRaisesEvent(Action action, object obj, string eventName) 
{ 
    EventInfo eventInfo = obj.GetType().GetEvent(eventName); 
    int raisedCount = 0; 
    Action incrementer =() => { ++raisedCount; }; 
    Delegate handler = /* what goes here? */; 

    eventInfo.AddEventHandler(obj, handler); 
    action.Invoke(); 
    eventInfo.RemoveEventHandler(obj, handler); 

    Assert.AreEqual(1, raisedCount); 
} 

आप देख सकते हैं, मेरी समस्या इस घटना के लिए उचित प्रकार के Delegate बनाने में निहित है। प्रतिनिधि को incrementer को छोड़कर कुछ भी नहीं करना चाहिए।

सी # में सभी वाक्य रचनात्मक सिरप के कारण, मेरी धारणा कि प्रतिनिधि और घटनाएं वास्तव में कैसे काम करती हैं, थोड़ा सा आलसी है। यह पहली बार है जब मैं प्रतिबिंब में डबिल हूं। गायब हिस्सा क्या है?

उत्तर

7

मैं हाल ही में वस्तुओं है कि सिंक्रोनस और एसिंक्रोनस दोनों घटनाओं को प्रकाशित करने के लिए इकाई परीक्षण घटना दृश्यों पर ब्लॉग पोस्ट की एक श्रृंखला लिखा था। पद एक इकाई परीक्षण दृष्टिकोण और ढांचे का वर्णन करते हैं, और परीक्षण के साथ पूर्ण स्रोत कोड प्रदान करते हैं।

मैं "इवेंट मॉनीटर" के कार्यान्वयन का वर्णन करता हूं जो घटना अनुक्रमांक इकाई परीक्षणों को लिखने की अनुमति देता है और अधिक स्पष्ट रूप से लिखा जाता है यानी सभी गन्दा बॉयलरप्लेट कोड से छुटकारा पाता है।

घटना मेरे आलेख में वर्णित की निगरानी का उपयोग करना, परीक्षण इसलिए की तरह लिखा जा सकता है:

var publisher = new PropertyChangedEventPublisher(); 

Action test =() => 
{ 
    publisher.X = 1; 
    publisher.Y = 2; 
}; 

var expectedSequence = new[] { "X", "Y" }; 

EventMonitor.Assert(publisher, test, expectedSequence); 

और मूल प्रश्न में मामले के लिए:

var publisher = new AsyncEventPublisher(); 

Action test =() => 
{ 
    publisher.RaiseA(); 
    publisher.RaiseB(); 
    publisher.RaiseC(); 
}; 

var expectedSequence = new[] { "EventA", "EventB", "EventC" }; 

EventMonitor.Assert(publisher, test, expectedSequence); 

या एक प्रकार है कि INotifyPropertyChanged लागू करता है के लिए :

MyClass myObject = new MyClass(); 
EventMonitor.Assert(myObject,() => { myObject.Width = 42; }, "Width"); 

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

वहाँ पदों मुद्दों और दृष्टिकोण, और स्रोत कोड का वर्णन भी में विस्तार का एक बहुत कुछ है:

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

+0

Slick! अगर वह सवाल का जवाब नहीं देता है, तो कुछ भी नहीं करता है। – Thomas

+0

हाय थॉमस, मूल प्रश्न को फिर से पढ़ने के बाद अद्यतन कोड उदाहरण। मैंने EventMonitor के एपीआई को थोड़ा और अधिक pithy बनाने के लिए tweaked। चीयर्स। :) –

+0

वास्तव में बहुत अच्छा है, लेकिन यह आपके ब्लॉग में दावा किए गए "मनमानी घटनाओं" को संभालने के करीब भी नहीं आता है।शायद जंगली में सभी घटनाओं में से 99% एमएस की सिफारिश का पालन करते हैं, इसलिए सामान्यता की कमी अभ्यास में एक बड़ी समस्या होनी चाहिए। –

5

लैम्बडास के साथ आप इसे बहुत कम कोड के साथ कर सकते हैं। बस घटना में एक लैम्ब्डा असाइन करें, और हैंडलर में एक मान सेट करें। प्रतिबिंब के लिए कोई ज़रूरत नहीं है और आप दृढ़ता से लाभ

[TestFixture] 
public class TestClass 
{ 
    [Test] 
    public void TestEventRaised() 
    { 
     // arrange 
     var called = false; 

     var test = new ObjectUnderTest(); 
     test.WidthChanged += (sender, args) => called = true; 

     // act 
     test.Width = 42; 

     // assert 
     Assert.IsTrue(called); 
    } 

    private class ObjectUnderTest 
    { 
     private int _width; 
     public event EventHandler WidthChanged; 

     public int Width 
     { 
      get { return _width; } 
      set 
      { 
       _width = value; OnWidthChanged(); 
      } 
     } 

     private void OnWidthChanged() 
     { 
      var handler = WidthChanged; 
      if (handler != null) 
       handler(this, EventArgs.Empty); 
     } 
    } 
} 
+0

हाँ, मुझे पता है। लेकिन यह अभी भी 4 लाइनें है जहां 1 पर्याप्त होना चाहिए। मैं बस यह देखने की कोशिश कर रहा हूं कि मैं इसे आम तौर पर कर सकता हूं या नहीं। – Thomas

+2

+1 यह एक कम सराहनीय समाधान है। इसे पढ़ना, समझना और डीबग करना आसान है। यह जटिल कोड (एमएसआईएल, प्रतिबिंब, आदि) पर निर्भरता से बचाता है, और कस्टम इवेंट हैंडलर, या जो भी हो, के सभी किनारे के मामलों को कवर कर सकता है। कम से कम मतदान किया जाना चाहिए। – HodlDwon

2

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

डिज़ाइन समय पर, गिनती पकड़ने के लिए एक कक्षा बनाएं, इसे कॉलकॉन्टर कहें।

AssertRaisesEvent में:

  • , अपने CallCounterclass का एक उदाहरण बनाने में एक जोरदार चर आपके द्वारा लिखा गया यह ध्यान में रखते हुए
  • अपने काउंटर कक्षा में शून्य
  • construct a DynamicMethod को गिनती प्रारंभ

    new DynamicMethod(string.Empty, typeof(void), parameter types extracted from the eventInfo, typeof(CallCounter))

  • डायनेमी प्राप्त करें cMethod के MethodBuilder और क्षेत्र

    • ldarg.0 (इस सूचक) बढ़ाने के लिए opcodes जोड़ने के लिए reflection.Emit का उपयोग
    • ldc_I4_1 (एक निरंतर एक)
    • ldarg.0 (इस सूचक)
    • ldfld (गिनती के वर्तमान मूल्य पढ़ें)
    • जोड़ने
    • stfld (अद्यतन गिनती सदस्य चर में वापस डाल)
  • कॉल the two-parameter overload of CreateDelegate, पहला पैरामीटर EventInfo से लिया गया ईवेंट प्रकार है, दूसरा पैरामीटर कॉलकॉन्टर
  • का परिणाम है जिसके परिणामस्वरूप प्रतिनिधि को eventInfo में पास किया गया है।AddEventHandler (आपको यह मिल गया है) अब आप टेस्ट केस निष्पादित करने के लिए तैयार हैं (आपको यह मिल गया है)।
  • आखिर में सामान्य तरीके से गिनती पढ़ें।

एकमात्र चरण मैं 100% सुनिश्चित नहीं हूं कि आप EventInfo से पैरामीटर प्रकार प्राप्त कर रहे हैं। आप EventHandlerType property और फिर उपयोग करते हैं? खैर, उस पृष्ठ पर एक उदाहरण दिखा रहा है कि आप प्रतिनिधि के आमंत्रण विधि के लिए केवल MethodInfo को पकड़ते हैं (मुझे लगता है कि नाम "Invoke" मानक में कहीं भी गारंटीकृत है) और फिर GetParameters और फिर सभी पैरामीटर टाइप मानों को खींचें, जांचें कि रास्ते में कोई रेफ/आउट पैरामीटर नहीं हैं।

+0

मुझे यकीन है कि मार्क ग्रेवेल मेथडबिल्डर से बचने के लिए अभिव्यक्ति पेड़ों का उपयोग करने के लिए एक तरीके से आएंगे, लेकिन यह एक ही विचार होगा। और सही अभिव्यक्ति प्राप्त करना कॉम्पाइल, जब टीडीलेगेट संकलन-समय पर ज्ञात नहीं है, तो यह गैर-तुच्छ है। –

+0

यदि यह वास्तव में ऐसा करने का एकमात्र तरीका है, तो मुझे लगता है कि मैं बॉयलरप्लेट के लिए बसूंगा ... – Thomas

1

कैसे इस बारे में:

private void AssertRaisesEvent(Action action, object obj, string eventName) 
    { 
     EventInfo eventInfo = obj.GetType().GetEvent(eventName); 
     int raisedCount = 0; 
     EventHandler handler = new EventHandler((sender, eventArgs) => { ++raisedCount; }); 
     eventInfo.AddEventHandler(obj, handler); 
     action.Invoke(); 
     eventInfo.RemoveEventHandler(obj, handler); 

     Assert.AreEqual(1, raisedCount); 
    } 
+0

यह केवल तभी काम करता है जब घोषित ईवेंट 'EventHandler' प्रकार का है, उदाहरण के लिए,' PropertyChangedEvent' या यहां तक ​​कि एक कस्टम घटना प्रकार। – Thomas

संबंधित मुद्दे