【问题标题】:Test events with nunit使用 nunit 测试事件
【发布时间】:2010-08-02 11:09:50
【问题描述】:

我刚开始使用 TDD,可以自己解决我遇到的大部分问题。但是现在我迷路了:如何检查是否触发了事件?我正在寻找类似Assert.RaiseAssert.Fire 的东西,但什么都没有。谷歌不是很有用,大部分点击都是像foo.myEvent += new EventHandler(bar); Assert.NotNull(foo.myEvent); 这样的建议,但这并不能证明什么。

谢谢!

【问题讨论】:

    标签: c# unit-testing events nunit c#-2.0


    【解决方案1】:

    可以通过订阅该事件并设置一个布尔值来检查是否触发了事件:

    var wasCalled = false;
    foo.NyEvent += (o,e) => wasCalled = true;
    
    ...
    
    Assert.IsTrue(wasCalled);
    

    由于请求 - 没有 lambda:

    var wasCalled = false;
    foo.NyEvent += delegate(o,e){ wasCalled = true;}
    
    ...
    
    Assert.IsTrue(wasCalled);
    

    【讨论】:

    • 这太老了...我知道...我很想知道...是否有可能在代码到达事件部分之前调用断言?我觉得好像代码很简单,这个值会在断言运行之前设置。
    • 只有当事件发生在另一个线程中时,才可以在设置值之前调用断言。在这种情况下,您需要将布尔值替换为 WaitHandle 并等待事件发出信号
    【解决方案2】:

    我更喜欢这样做:

    var wait = new AutoResetEvent(false);
    foo.MeEvent += (sender, eventArgs) => { wait.Set(); };
    Assert.IsTrue(wait.WaitOne(TimeSpan.FromSeconds(5)));
    

    优点:支持多线程场景(如果handler在不同线程中调用)

    【讨论】:

    • 我采用了类似的方法,但更喜欢ManualResetEvent
    【解决方案3】:

    如果您知道该事件将被同步触发:

    bool eventRaised = false;
    Customer customer = new Customer() { Name = "Carl" };
    customer.NameChanged += (sender, e) => { eventRaised = true; };
    
    customer.Name = "Sam";
    
    Assert.IsTrue(eventRaised);
    

    如果事件可以异步触发:

    ManualResetEvent eventRaised = new ManualResetEvent(false);
    Customer customer = new Customer() { Name = "Carl" };
    customer.NameChanged += (sender, e) => { eventRaised.Set(); };
    
    customer.Name = "Sam";
    
    Assert.IsTrue(eventRaised.WaitOne(TIMEOUT));
    

    但是,有人说应该避免测试异步行为。

    【讨论】:

      【解决方案4】:

      我最近不得不这样做,下面是我想出的。我没有按照其他帖子所说的那样做的原因是,我不喜欢变量保持状态并且必须在多个事件之间“手动”重置它的想法。

      下面是在MyTests 测试中测试的ClassUnderTestNameChanged 事件的代码:

      public class ClassUnderTest {
          private string name;
          public string Name {
              get { return this.name; }
              set {
                  if (value != this.name) {
                      this.name = value;
                      NameChanged(this, new PropertyChangedEventArgs("Name"));
                  }
              }
          }
      
          public event EventHandler<PropertyChangedEventArgs> NameChanged = delegate { };
      }
      
      [TestFixture]
      public class MyTests {
          [Test]
          public void Test_SameValue() {
              var t = new ClassUnderTest();
              var e = new EventHandlerCapture<PropertyChangedEventArgs>();
              t.NameChanged += e.Handler;
      
              Event.Assert(e, Event.IsNotRaised<PropertyChangedEventArgs>(), () => t.Name = null);
              t.Name = "test";
              Event.Assert(e, Event.IsNotRaised<PropertyChangedEventArgs>(), () => t.Name = "test");
          }
          [Test]
          public void Test_DifferentValue() {
              var t = new ClassUnderTest();
              var e = new EventHandlerCapture<PropertyChangedEventArgs>();
              t.NameChanged += e.Handler;
      
              Event.Assert(e, Event.IsPropertyChanged(t, "Name"), () => t.Name = "test");
              Event.Assert(e, Event.IsPropertyChanged(t, "Name"), () => t.Name = null);
          }
      }
      

      支持类如下。这些类可以与任何EventHandler&lt;TEventArgs&gt; 一起使用或扩展到其他代表。事件测试可以嵌套。

      /// <summary>Class to capture events</summary>
      public class EventHandlerCapture<TEventArgs> where TEventArgs : EventArgs {
          public EventHandlerCapture() {
              this.Reset();
          }
      
          public object Sender { get; private set; }
          public TEventArgs EventArgs { get; private set; }
          public bool WasRaised { get; private set; }
      
          public void Reset() {
              this.Sender = null;
              this.EventArgs = null;
              this.WasRaised = false;
          }
      
          public void Handler(object sender, TEventArgs e) {
              this.WasRaised = true;
              this.Sender = sender;
              this.EventArgs = e;
          }
      }
      
      /// <summary>Contains things that make tests simple</summary>
      public static class Event {
          public static void Assert<TEventArgs>(EventHandlerCapture<TEventArgs> capture, Action<EventHandlerCapture<TEventArgs>> test, Action code) where TEventArgs : EventArgs {
              capture.Reset();
              code();
              test(capture);
          }
          public static Action<EventHandlerCapture<TEventArgs>> IsNotRaised<TEventArgs>() where TEventArgs : EventArgs {
              return (EventHandlerCapture<TEventArgs> test) => {
                  NUnit.Framework.Assert.That(test.WasRaised, Is.False);
              };
          }
          public static Action<EventHandlerCapture<PropertyChangedEventArgs>> IsPropertyChanged(object sender, string name) {
              return (EventHandlerCapture<PropertyChangedEventArgs> test) => {
                  NUnit.Framework.Assert.That(test.WasRaised, Is.True);
                  NUnit.Framework.Assert.That(test.Sender, Is.SameAs(sender));
                  NUnit.Framework.Assert.That(test.EventArgs.PropertyName, Is.EqualTo(name));
              };
          }
      }
      

      【讨论】:

      • 不敢相信这没有得到更多的支持。这是一个很好的解决方案。 FluentAssertions 有一个很好的事件监控解决方案,可以很好地与 Nunit 配合使用。看起来很像
      【解决方案5】:

      使用 NUnit 和 Moq,您可以进行更强大的事件测试。

      用于监控事件触发器的Mock Class:

      public class AssertEvent { public virtual void Call(string obj) { } }
      Mock<AssertEvent> EventMock;
      AssertEvent Evt;
      

      事件触发器的设置:

      [SetUp]
      public void TestInit() {
          EventMock = new Mock<AssertEvent>();
          Evt= EventMock.Object;
      }
      

      在测试中使用模拟对象:

      [Test]
      public void TestMethod() {
          myObject.Event1 += (sender, args) => Evt.Call("Event1Label");
          myObject.Event2 += (sender, args) => Evt.Call("Event2Label");
          myObject.Event3 += (sender, args) => Evt.Call("Event3Label");        
      
          myObject.SomeEventTrigger();
      
          EventMock.Verify(m => m.Call("Event1Label"), Times.Exactly(1));
          EventMock.Verify(m => m.Call("Event2Label"), Times.Never());
          EventMock.Verify(m => m.Call("Event3Label"), Times.Between(1,3);
      
      }
      

      【讨论】:

        【解决方案6】:

        我自己并没有真正做到这一点,但也许您可以为您想要订阅的事件添加一个虚拟事件处理程序并让它更新一个本地布尔变量,以便在触发该方法后您可以检查该布尔值的状态以查看如果事件被触发了?

        类似:

        bool eventFired = false;
        foo.MyEvent += (s, e) => { eventFired = true };
        
        Assert.IsTrue(eventFired);
        

        【讨论】:

          【解决方案7】:

          @theburningmonk:一个“;”不见了。更正的版本是:

          bool eventFired = false;
          foo.MyEvent += (s, e) => { eventFired = true; };
          Assert.IsTrue(eventFired);
          

          干杯! ;-)

          【讨论】:

          • 好收获!我会更新我的答案,但没有多大意义,因为它实际上是 Dror 的答案的重复
          【解决方案8】:

          我只会将 FluentAssertions 与 Nunit 一起使用:https://fluentassertions.com/eventmonitoring/ 它工作得非常好。这是文档中的一个示例

          var subject = new EditCustomerViewModel();
          using (var monitoredSubject = subject.Monitor())
          {
              subject.Foo();
              monitoredSubject.Should().Raise("NameChangedEvent");
          }
          

          【讨论】:

            【解决方案9】:

            您可以添加自定义事件处理程序,例如,在测试用例类中增加一些整数字段。然后检查字段是否增加。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2010-10-03
              • 2014-08-14
              • 2011-10-20
              • 1970-01-01
              • 2010-12-13
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多