2011-06-26 283 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上执行,而是在依赖性上的罕见情况之一。这种方法应该优先于反射。这个答案应该被认为是最好的答案。 –