2010-04-29 10 views
9

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

  • वंचितता IFoo है।
  • आश्रित वर्ग आईबार है।
  • आईबार को बार के रूप में लागू किया गया है।
  • बार IFoo पर एक नया (System.Threading.Tasks।) कार्य में प्रारंभ() को कॉल() को बार उदाहरण पर प्रारंभ() कहा जाता है।

यूनिट टेस्ट (MOQ): बार के रूप में Ibar की

[Test] 
    public void StartBar_ShouldCallStartOnAllFoo_WhenFoosExist() 
    { 
     //ARRANGE 

     //Create a foo, and setup expectation 
     var mockFoo0 = new Mock<IFoo>(); 
     mockFoo0.Setup(foo => foo.Start()); 

     var mockFoo1 = new Mock<IFoo>(); 
     mockFoo1.Setup(foo => foo.Start()); 


     //Add mockobjects to a collection 
     var foos = new List<IFoo> 
         { 
          mockFoo0.Object, 
          mockFoo1.Object 
         }; 

     IBar sutBar = new Bar(foos); 

     //ACT 
     sutBar.Start(); //Should call mockFoo.Start() 

     //ASSERT 
     mockFoo0.VerifyAll(); 
     mockFoo1.VerifyAll(); 
    } 

कार्यान्वयन:

class Bar : IBar 
    { 
     private IEnumerable<IFoo> Foos { get; set; } 

     public Bar(IEnumerable<IFoo> foos) 
     { 
      Foos = foos; 
     } 

     public void Start() 
     { 
      foreach(var foo in Foos) 
      { 
       Task.Factory.StartNew(
        () => 
         { 
          foo.Start(); 
         }); 
      } 
     } 
    } 

Moq अपवाद:

*Moq.MockVerificationException : The following setups were not matched: 
IFoo foo => foo.Start() (StartBar_ShouldCallStartOnAllFoo_WhenFoosExist() in 
FooBarTests.cs: line 19)* 
+2

क्या कोई विशेष कारण है कि 'IFoo' के सरल मॉक कार्यान्वयन को न लिखें और इसके बजाय इसका उपयोग करें? –

उत्तर

7

@dpurrington & @StevenH: हम अपने कोड

sut.Start(); 
Thread.Sleep(TimeSpan.FromSeconds(1)); 

में सामान इस तरह की डाल शुरू और हम "इकाई" परीक्षण के हजारों है तो हमारे परीक्षणों के बजाय सेकंड के मिनट में चलने लगते हैं। यदि आपके पास उदाहरण के लिए 1000 यूनिट परीक्षण थे, तो आपके परीक्षणों को 5 सेकंड से कम समय में चलाने में मुश्किल होगी यदि कोई थ्रेड स्लीप के साथ टेस्ट कोड बेस चला गया हो और उसे खराब कर दे।

मेरा सुझाव है कि यह खराब अभ्यास है, जब तक हम स्पष्ट रूप से एकीकरण परीक्षण नहीं कर रहे हैं।

मेरा सुझाव System.CoreEx.dll से System.Concurrency.IScheduler इंटरफ़ेस का उपयोग करना होगा और टास्कपूलशेड्यूलर कार्यान्वयन इंजेक्ट करेगा।

यह है कि यह कैसे लागू किया जाना चाहिए

using System.Collections.Generic; 
using System.Concurrency; 
using Moq; 
using NUnit.Framework; 

namespace StackOverflowScratchPad 
{ 
    public interface IBar 
    { 
     void Start(IEnumerable<IFoo> foos); 
    } 

    public interface IFoo 
    { 
     void Start(); 
    } 

    public class Bar : IBar 
    { 
     private readonly IScheduler _scheduler; 

     public Bar(IScheduler scheduler) 
     { 
      _scheduler = scheduler; 
     } 

     public void Start(IEnumerable<IFoo> foos) 
     { 
      foreach (var foo in foos) 
      { 
       var foo1 = foo; //Save to local copy, as to not access modified closure. 
       _scheduler.Schedule(foo1.Start); 
      } 
     } 
    } 

    [TestFixture] 
    public class MyTestClass 
    { 
     [Test] 
     public void StartBar_ShouldCallStartOnAllFoo_WhenFoosExist() 
     { 
      //ARRANGE 
      TestScheduler scheduler = new TestScheduler(); 
      IBar sutBar = new Bar(scheduler); 

      //Create a foo, and setup expectation 
      var mockFoo0 = new Mock<IFoo>(); 
      mockFoo0.Setup(foo => foo.Start()); 

      var mockFoo1 = new Mock<IFoo>(); 
      mockFoo1.Setup(foo => foo.Start()); 

      //Add mockobjects to a collection 
      var foos = new List<IFoo> 
         { 
          mockFoo0.Object, 
          mockFoo1.Object 
         }; 

      //ACT 
      sutBar.Start(foos); //Should call mockFoo.Start() 
      scheduler.Run(); 

      //ASSERT 
      mockFoo0.VerifyAll(); 
      mockFoo1.VerifyAll(); 
     } 
    } 
} 

यह अब किसी भी Thread.Sleep बिना पूरी रफ्तार से चलाने के लिए परीक्षण की अनुमति देता है के लिए मेरे सुझाव है।

ध्यान दें कि अनुबंधों को बार कन्स्ट्रक्टर (निर्भरता इंजेक्शन के लिए) में एक आईएसड्यूलर स्वीकार करने के लिए संशोधित किया गया है और आईनेमेरेबल अब आईबीआर को पास कर दिया गया है। स्टार्ट विधि। मुझे उम्मीद है कि यह समझ में आता है कि मैंने इन परिवर्तन क्यों किए।

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

+0

मैं अपने समाधान के बारे में आपके शुरुआती बिंदु से सहमत हूं। मुझे लगता है कि मैं शेड्यूलर की बजाय घटनाओं का उपयोग करना चुनता, लेकिन किसी भी तरह से, मेरे उत्तर से बेहतर। – dpurrington

+0

दुर्भाग्यवश यह अब काम नहीं करता है, क्योंकि [System.Threading.Tasks.TaskScheduler] पर सभी विधियां (http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.aspx) मुहरबंद हैं । –

+2

@ रिचर्ड: ध्यान दें कि टाइप इंजेक्शन वाला इंटरफ़ेस आईएसड्यूलर है जो टेस्टशेड्यूलर लागू करता है। मैं टास्कशेड्यूलर पर विधियों को ओवरराइड करने की कोशिश नहीं कर रहा हूं, मैं इस तथ्य का लाभ उठा रहा हूं कि टास्कशेड्यूलर और टेस्टशेड्यूलर दोनों आईएसड्यूलर इंटरफ़ेस को कार्यान्वित करते हैं और जैसे कि विचलित होते हैं। –

0

आपका परीक्षण बहुत ज्यादा कार्यान्वयन विस्तार का उपयोग करता है, IEnumerable<IFoo> प्रकार। जब भी मुझे IENumerable के साथ परीक्षण शुरू करना है, यह हमेशा कुछ घर्षण बनाता है।

0

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

मैंने हाल ही में कुछ परीक्षण लिखे हैं कि समानांतर कार्यों को निष्पादित करने के लिए उचित रूप से प्रतीक्षा करें और मैंने सोचा कि मैं अपना समाधान साझा करूंगा। मुझे एहसास है कि यह एक पुरानी पोस्ट है, लेकिन मैंने सोचा कि यह समाधान की तलाश करने वालों के लिए मूल्य प्रदान करेगा।

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

class Bar : IBar 
{ 
    private IEnumerable<IFoo> Foos { get; set; } 
    internal CountdownEvent FooCountdown; 

    public Bar(IEnumerable<IFoo> foos) 
    { 
     Foos = foos; 
    } 

    public void Start() 
    { 
     FooCountdown = new CountdownEvent(foo.Count); 

     foreach(var foo in Foos) 
     { 
      Task.Factory.StartNew(() => 
      { 
       foo.Start(); 

       // once a worker method completes, we signal the countdown 
       FooCountdown.Signal(); 
      }); 
     } 
    } 
} 

CountdownEvent वस्तुओं काम कर रहे हैं और आप पूरा करने के लिए प्रतीक्षा करने की आवश्यकता है (जैसे जब हम इकाई परीक्षण में एक ज़ोर प्रयास करने के लिए प्रतीक्षा करें)। कन्स्ट्रक्टर इसे संसाधित करने के संकेतों से पहले कितनी बार संकेतित किया जाना चाहिए, जो प्रसंस्करण पूरा हो रहा है।

काउंटडाउनएवेंट के लिए आंतरिक एक्सेस संशोधक का उपयोग करने का कारण यह है कि जब मैं यूनिट परीक्षणों तक पहुंच की आवश्यकता होती है तो मैं आम तौर पर गुणों और विधियों को आंतरिक रूप से सेट करता हूं। मैं फिर परीक्षण की Properties\AssemblyInfo.cs फ़ाइल के तहत असेंबली में एक नई असेंबली विशेषता जोड़ता हूं ताकि आंतरिक परीक्षण परियोजना के संपर्क में आ सकें।

[assembly: InternalsVisibleTo("FooNamespace.UnitTests")] 

इस उदाहरण में, FooCountdown अगर वहाँ Foos में 3 foo वस्तुओं रहे हैं 3 बार संकेत किए जाने की प्रतीक्षा करेगा।

अब यह है कि आप फूकाउंटडाउन को प्रसंस्करण पूरा करने के संकेत के लिए प्रतीक्षा करते हैं ताकि आप अपने जीवन के साथ आगे बढ़ सकें और थ्रेड पर सीपीयू चक्र बर्बाद कर सकें। नींद()।

[Test] 
public void StartBar_ShouldCallStartOnAllFoo_WhenFoosExist() 
{ 
    //ARRANGE 

    var mockFoo0 = new Mock<IFoo>(); 
    mockFoo0.Setup(foo => foo.Start()); 

    var mockFoo1 = new Mock<IFoo>(); 
    mockFoo1.Setup(foo => foo.Start()); 


    //Add mockobjects to a collection 
    var foos = new List<IFoo> { mockFoo0.Object, mockFoo1.Object }; 

    IBar sutBar = new Bar(foos); 

    //ACT 
    sutBar.Start(); //Should call mockFoo.Start() 
    sutBar.FooCountdown.Wait(); // this blocks until all parallel tasks in sutBar complete 

    //ASSERT 
    mockFoo0.VerifyAll(); 
    mockFoo1.VerifyAll(); 
} 
संबंधित मुद्दे