从对象测试事件

时间:2008-12-02 14:26:52

标签: unit-testing events testing tdd

我一直在尝试更多地使用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 个答案:

答案 0 :(得分:4)

您可以做的最简单的事情是为事件订阅一个匿名方法或lambda,并在其中增加一个对您测试的本地计数器。根本不需要额外的课程。

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

这样的事情:

MyObject subjectUnderTest = new MyObject();
EventMonitor monitor = new Monitor();
subjectUnderTest.Event += monitor.EventCatcher;

// testcode;

Assert.Equal( 1, monitor.EventsFired );

这个问题在于它并不是真正的通用。您只能测试monitor.EventCatcher()可以订阅的事件。我通常不用参数做事件,所以没问题,我只有标准的void EventCatcher(对象发送者,EventArgs参数)。您可以通过为事件订阅正确类型的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和我正在研究一个通用的事件测试监视器类,可能会使其中的一些变得更容易。如果人们有兴趣使用这样的东西,我想听听你的意见。

答案 1 :(得分:2)

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

我描述了一个“事件监视器”的实现,其概念与您描述的内容类似,并且此问题的一些先前的回答者已经提到过。绝对是正确的方向,因为在没有某种监视器模式的情况下编写大量测试会导致许多混乱的样板代码。

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

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执行所有繁重工作并将运行测试(测试)并断言事件以预期的顺序(expectedSequence)引发。它还会在测试失败时输出很好的诊断消息。与所讨论的一些方法的不同之处在于它不仅仅是计数,它将断言已遵循指定的确切序列。此外,您不需要挂钩任何事件。这一切都由事件监视器处理。因此测试非常干净。

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

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

答案 2 :(得分:1)

一个洞/改进空间是您的监视器仅计算引发的事件数。理想情况下,人们可以指定应该引发哪些事件的期望,多少次,按什么顺序,甚至可以指出每个事件被引发时(对象发送者,EventArgs e)对应该是什么样的。

答案 3 :(得分:1)

我通常使用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

答案 4 :(得分:0)

对我来说很好 - 因为它有效; - )

答案 5 :(得分:0)

我做测试活动。我这样做很简单。

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++;
}

答案 6 :(得分:0)

在我的测试类中,我只是将事件处理程序挂钩到声明对象的事件......或者我在这里遗漏了什么?