代码之家  ›  专栏  ›  技术社区  ›  Rob Cooper

测试来自对象的事件

  •  2
  • Rob Cooper  · 技术社区  · 17 年前

    我一直在努力深入TDD。目前保持简单,有很多 Debug.Asserts 在控制台应用程序中。

    我想完成的部分测试是确保从对象引发事件的次数正确,因为客户端代码将依赖于这些事件。

    所以,我开始思考如何测试这些事件,以及如何追踪它们。所以我想出了一个 监视器 “模式”(如果你能这么称呼它的话)。这基本上是一个在构造函数中接受被测类型对象的类。

    然后将事件连接到监视器,创建代理,在事件发生时对其进行计数和记录。

    然后,我回到我的测试代码并执行以下操作:

        bool Test()
        {
            MyObject mo = new MyObject();
            MyMonitor mon = new MyMonitor(mo);
    
            // Do some tests that should cause the object to raise events..
    
            return mon.EventCount == expectedCount;
        }
    

    这工作得很好,当我故意破坏我的代码时,测试按预期失败了,但是 我想知道,这是不是太多的“自由形式”测试代码(即没有支持测试的代码)?


    其他思考

    • 你测试事件吗?
    • 怎样 你测试事件吗?
    • 你认为上面有什么漏洞/需要改进的地方吗?

    感谢您的所有意见! ^_^

    7 回复  |  直到 17 年前
        1
  •  4
  •   Mendelt    17 年前

    你能做的最简单的事情就是向事件订阅一个匿名方法或lambda,并在其中递增一个测试本地的计数器。根本不需要使用额外的类。

    我发现这不会使你的代码非常可读,所以我也做了同样的事情。我在几个项目中编写了监控对象。通常它们比你的显示器更通用。它们只是公开了您可以订阅事件的公共方法,并计算了它们被调用的次数。这样,您就可以为不同的事件重用监视对象。

    大致如下:

    MyObject subjectUnderTest = new MyObject();
    EventMonitor monitor = new Monitor();
    subjectUnderTest.Event += monitor.EventCatcher;
    
    // testcode;
    
    Assert.Equal( 1, monitor.EventsFired );
    

    问题在于,它并不是真正通用的。您只能测试监视的事件。EventCatcher()可以订阅。我通常不处理带参数的事件,所以这没问题,我只使用标准的void EventCatcher,即对象发送方和ViewModel参数。您可以通过将正确类型的lambda订阅到事件中并在lambda中调用EventCatcher来使其更通用。这使得你的测试有点难以阅读。您还可以使用泛型使EventCatcher方法与泛型EventHandler一起工作。

    您可能需要注意,最终您将希望能够准确存储以什么顺序和用什么参数调用的事件。你的事件监视器很容易失控。


    我找到了另一种方法,这种方法可能对具有更复杂断言的测试有意义。

    与其创建自己的监视器,不如让你选择的模拟框架为你创建它,你只需为处理事件的类创建一个接口。大致如下:

    public interface IEventHandlerStub
    {
        event EventHandler<T> Event(object sender, T arguments);
    }
    

    然后,您可以在测试中模拟此接口。Rhino Mocks这样做:

    var eventHandlerStub = MockRepository.GenerateStub<IEventHandlerStub>();
    myObject.Event += eventHandlerStub.Event;
    
    // Run your code
    
    eventHandlerStub.AssertWasCalled(x => x.Event(null, null));
    

    对于这样一个简单的测试,它可能有点过头了,但如果你想断言关于参数的事情,例如,你可以使用你的模拟框架的灵活性。

    另一方面 Rob和我正在开发一个通用的事件测试监视器类,这可能会使其中一些更容易。如果人们有兴趣使用这样的东西,我想听听你的意见。

        2
  •  2
  •   Tim Lloyd    16 年前

    我最近写了一系列关于发布同步和异步事件的对象的单元测试事件序列的博客文章。这些帖子描述了一种单元测试方法和框架,并提供了完整的测试源代码。

    我描述了一个“事件监视器”的实现,其概念与你所描述的和之前一些回答这个问题的人提到的类似。这绝对是朝着正确的方向发展的,因为在没有某种监控模式的情况下编写大量测试会导致大量混乱的样板代码。

    使用我文章中描述的事件监视器,测试可以这样编写:

    AsyncEventPublisher publisher = new AsyncEventPublisher();
    
    Action test = () =>
    {
        publisher.RaiseA();
        publisher.RaiseB();
        publisher.RaiseC();
    };
    
    var expectedSequence = new[] { "EventA", "EventB", "EventC" };
    
    EventMonitor.Assert(test, publisher, expectedSequence, TimeoutMS);
    

    EventMonitor完成所有繁重的工作,并将运行测试(test),并断言事件是按照预期的顺序(expectedSequence)引发的。它还可以在测试失败时打印出漂亮的诊断消息。与所讨论的一些方法不同的是,它不仅会计数,还会断言遵循了指定的确切顺序。此外,您不需要勾选任何事件。所有这些都由事件监视器处理。因此,测试非常干净。

    在描述问题和方法的帖子中有很多细节,还有源代码:

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

        3
  •  1
  •   Szymon Rozga    17 年前

    一个需要改进的漏洞/空间是,您的监视器只计算引发的事件数量。理想情况下,人们可以指定应该引发哪些事件、多少次、以何种顺序,甚至可能指定在引发每个事件时(对象发送者,nvarchar e)对应该是什么样子。

        4
  •  1
  •   Ilja Preuß    17 年前

    我通常使用Mock对象来测试事件——当我在Java中工作时,我使用EasyMock(你选择的语言应该也有类似的东西):

    FooListener listener = createMock(FooListener.class);
    expect(listener.someEvent());
    replay(listener);
    myObject.addListener(listener);
    myObject.doSomethingThatFiresEvent();
    verify(listener);
    

    在我看来,你所做的事情听起来更像是一个Stub——也就是说,监听器/观察者不知道应该多久调用一次,而只是计算调用次数,然后测试根据该计数进行断言。这也是一个合理的解决方案,你更喜欢哪一个主要是个人偏好的问题,而且你的语言和可用工具可能会使哪种解决方案更容易。

    看一看 this article on the topic .

        5
  •  0
  •   Steven A. Lowe    17 年前

    在我看来很好——因为它有效;-)

        6
  •  0
  •   Patrick Desjardins    17 年前

    我做测试赛。我做这件事的方法很简单。

    private int counterEvent;
    
    [Test]
    public void abcTest()
    {
        counterEvent = 0;
        //1- Initialize object to test
        //2- Set the event to test (abcTest_event for example)
        //Assert the object incremented (counterEvent for example)
    }
    
    private void abcTest_event(object sender)
    {
        counterEvent++;
    }
    
        7
  •  0
  •   kpollock    17 年前

    在我的测试类中,我只是将事件处理程序与声明的对象的事件挂钩。…还是我错过了什么??

    推荐文章