2011-06-26 142 views
4

我想測試該類ARegisterEventHandlers()方法將其方法之一註冊爲EventHandler類別B上的事件。我怎樣才能做到這一點?如果有問題,我正在使用moq。如何測試事件是否包含事件處理程序?

  • 我不認爲有一種方法來檢查外部類的事件處理程序委託(請糾正我,如果我錯了)。
  • 如果我可以觸發事件,然後斷言我的回調被調用,但如果我嘲笑A類的接口(並設置了回調的期望),那麼我會失去執行RegisterEventHandlers() ,這是我首先測試的方法。
  • 嘲笑B類的事件將是最好的選擇,但我不明白我必須截取哪種方法才能做到這一點。有沒有辦法爲事件設置模擬,並攔截+=方法調用?

有沒有一個乾淨的解決方案呢?

回答

2

您可以獲取聲明事件的類外的事件的調用列表 - 但它涉及反射。下面是一個代碼示例,顯示如何確定哪些方法(在目標實例a)被添加到事件b。事件致電a.RegisterEventHandlers()。將以下代碼粘貼到代碼文件中並添加到窗體或控制檯項目中:Test test = new Test();測試運行();

using System; 
using System.Reflection; 
using System.Diagnostics; 
using System.Collections.Generic; 

    public class A 
    { 
     B m_b = new B(); 

     public void RegisterEventHandlers() 
     { 
     m_b.TheEvent += new EventHandler(Handler_TheEvent); 
     m_b.TheEvent += new EventHandler(AnotherHandler_TheEvent); 
     } 

     public A() 
     { 
     m_b.TheEvent += new EventHandler(InitialHandler_TheEvent); 
     } 

     void InitialHandler_TheEvent(object sender, EventArgs e) 
     { } 

     void Handler_TheEvent(object sender, EventArgs e) 
     { } 

     void AnotherHandler_TheEvent(object sender, EventArgs e) 
     { } 
    } 

    public class B 
    { 
     public event EventHandler TheEvent; 
     //{ 
     // //Note that if we declared TheEvent without the add/remove methods, the 
     // //following would still generated internally and the underlying member 
     // //(here m_theEvent) can be accessed via Reflection. The automatically 
     // //generated version has a private field with the same name as the event 
     // //(i.e. "TheEvent") 

     // add { m_theEvent += value; } 
     // remove { m_theEvent -= value; } 
     //} 
     //EventHandler m_theEvent; //"TheEvent" if we don't implement add/remove 


     //The following shows how the event can be invoked using the underlying multicast delegate. 
     //We use this knowledge when invoking via reflection (of course, normally we just write 
     //if (TheEvent != null) TheEvent(this, EventArgs.Empty) 
     public void ExampleInvokeTheEvent() 
     { 
     Delegate[] dels = TheEvent.GetInvocationList(); 
     foreach (Delegate del in dels) 
     { 
      MethodInfo method = del.Method; 
      //This does the same as ThisEvent(this, EventArgs.Empty) for a single registered target 
      method.Invoke(this, new object[] { EventArgs.Empty }); 
     } 
     } 
    } 


    public class Test 
    { 
     List<Delegate> FindRegisteredDelegates(A instanceRegisteringEvents, B instanceWithEventHandler, string sEventName) 
     { 
     A a = instanceRegisteringEvents; 
     B b = instanceWithEventHandler; 

     //Lets assume that we know that we are looking for a private instance field with name sEventName ("TheEvent"), 
     //i.e the event handler does not implement add/remove. 
     //(otherwise we would need more reflection to determine what we are looking for) 
     BindingFlags filter = BindingFlags.Instance | BindingFlags.NonPublic; 

     //Lets assume that TheEvent does not implement the add and remove methods, in which case 
     //the name of the relevant field is just the same as the event itself 
     string sName = sEventName; //("TheEvent") 

     FieldInfo fieldTheEvent = b.GetType().GetField(sName, filter); 

     //The field that we get has type EventHandler and can be invoked as in ExampleInvokeTheEvent 
     EventHandler eh = (EventHandler)fieldTheEvent.GetValue(b); 

     //If the event handler is null then nobody has registered with it yet (just return an empty list) 
     if (eh == null) return new List<Delegate>(); 


     List<Delegate> dels = new List<Delegate>(eh.GetInvocationList()); 

     //Only return those elements in the invokation list whose target is a. 
     return dels.FindAll(delegate(Delegate del) { return Object.ReferenceEquals(del.Target, a); }); 
     } 

     public void Run() 
     { 
     A a = new A(); 

     //We would need to check the set of delegates returned before we call this 

     //Lets assume we know how to find the all instances of B that A has registered with 
     //For know, lets assume there is just one in the field m_b of A. 
     FieldInfo fieldB = a.GetType().GetField("m_b", BindingFlags.Instance | BindingFlags.NonPublic); 
     B b = (B)fieldB.GetValue(a); 

     //Now we can find out how many times a.RegisterEventHandlers is registered with b 
     List<Delegate> delsBefore = FindRegisteredDelegates(a, b, "TheEvent"); 

     a.RegisterEventHandlers(); 

     List<Delegate> delsAfter = FindRegisteredDelegates(a, b, "TheEvent"); 

     List<Delegate> delsAdded = new List<Delegate>(); 
     foreach (Delegate delAfter in delsAfter) 
     { 
      bool inBefore = false; 
      foreach (Delegate delBefore in delsBefore) 
      { 
       if ((delBefore.Method == delAfter.Method) 
        && (Object.ReferenceEquals(delBefore.Target, delAfter.Target))) 
       { 
        //NOTE: The check for Object.ReferenceEquals(delBefore.Target, delAfter.Target) above is not necessary 
        //  here since we defined FindRegisteredDelegates to only return those for which .Taget == a) 

        inBefore = true; 
        break; 
       } 
      } 
      if (!inBefore) delsAdded.Add(delAfter); 
     } 

     Debug.WriteLine("Handlers added to b.TheEvent in a.RegisterEventHandlers:"); 
     foreach (Delegate del in delsAdded) 
     { 
      Debug.WriteLine(del.Method.Name); 
     } 


     } 
    } 




0

我不太瞭解單元測試,但也許this link可以給你一些想法。請注意,關鍵字virtual也適用於此。

3

當嘲諷B,聲明該事件處理程序是這樣的:

public class B : IB 
{ 
    public int EventsRegistered; 
    public event EventHandler Junk 
    { 
    add 
    { 
     this.EventsRegistered++; 
    } 
    remove 
    { 
     this.EventsRegistered--; 
    } 
    } 
} 

我不能肯定,起訂量允許這一點,但我敢肯定,你可以創建自己的模擬類。

0

我不認爲moq有這種能力 - 如果您準備購買工具,我建議您使用Typemock Isolator,它可以驗證對象上的任何方法被調用 - 包括事件處理程序 - 有look at link

3

你是正確的,你不能從類外部訪問事件委託,這是C#語言中的限制。

測試這個最直接的方法是模擬B類,然後提升它的事件,然後觀察所引發的事件的副作用。這與你正在尋找的略有不同,但它展示了類的A行爲而不是它的實現(這是你的測試應該努力去做的)。

爲了使這個工作,B類必須是可嘲弄的,它公開的事件也必須是虛擬的。如果他們沒有被宣佈爲虛擬的,Moq不能攔截事件。或者,如果B是一個接口,確保事件在那裏被聲明。

public interface IEventProvider 
{ 
    event EventHandler OnEvent; 
} 

public class Example 
{ 
    public Example(IEventProvider e) 
    { 
     e.OnEvent += PerformWork; 
    } 

    private void PerformWork(object sender, EventArgs e) 
    { 
     // perform work 

     // event has an impact on this class that can be observed 
     // from the outside. this is just an example... 
     VisibleSideEffect = true; 
    } 

    public bool VisibleSideEffect 
    { 
     get; set; 
    } 
} 

[TestClass] 
public class ExampleFixture 
{ 
    [TestMethod] 
    public void DemonstrateThatTheClassRespondsToEvents() 
    { 
     var eventProvider = new Mock<IEventProvider>().Object; 
     var subject = new Example(eventProvider.Object); 

     Mock.Get(eventProvider) 
      .Raise(e => e.OnEvent += null, EventArgs.Empty); 

     Assert.IsTrue(subject.VisibleSideEffect, 
         "the visible side effect of the event was not raised."); 
    } 
} 

如果你真的需要測試實施,還有其他的機制,如手卷Test Spy/Test Double,或者基於反射的戰略,以獲得代表名單。我的希望是你應該更關心類A的事件處理邏輯,而不是它的事件處理程序分配。畢竟,如果A類對事件沒有反應,並且對它做了某些事情,那麼分配就不重要了。

+0

這是在測試的_Act_部分不是在SUT上執行,而是在依賴性上的罕見情況之一。這種方法應該優先於反射。這個答案應該被認爲是最好的答案。 –