我倾向于认为只应该测试公共接口,以此涵盖私有过程的测试。然而,昨天出现了一个有趣的问题 - 是否应该测试事件处理程序?我的直觉是逻辑应该存储在由处理程序本身调用的自包含过程中,但这是主观的,并且很可能会导致测试私有而不是公共的过程。我是否应该对事件处理程序进行单元测试,如果是,最佳实践是什么?
我倾向于认为只应该测试公共接口,以此涵盖私有过程的测试。然而,昨天出现了一个有趣的问题 - 是否应该测试事件处理程序?我的直觉是逻辑应该存储在由处理程序本身调用的自包含过程中,但这是主观的,并且很可能会导致测试私有而不是公共的过程。我是否应该对事件处理程序进行单元测试,如果是,最佳实践是什么?
我不会说对于单元测试事件处理程序的做法是“错误”的。个人而言,我会遵循“测试可能出错的部分”的哲学,而不会单元测试事件处理程序。
我一直看到事件代码中一个经常出错的地方,这是单元测试无法捕捉到的——“On”方法只是:
if (MyEventHandler != null)
MyEventHandler(this, e);
这里存在一个竞态条件; 在进行空值检查之前,应该将MyEventHandler分配给一个变量。
第二个常见错误是人们将null传递给"e"事件数据参数; 可以进行测试。
如果你没有Cwalina和Abrams的《框架设计指南第2版》的副本,请立即购买。它会告诉你如何每次正确编写事件代码,如何正确编写Dispose模式以及许多其他内容。
我认为事件处理程序应该进行测试。如果您按照通常的模式执行以下操作:
public event EventHandler MyEvent;
protected void OnMyEvent()
{
// Raise MyEvent here
}
因此,对于MyEvent的测试实际上是OnMyEvent测试的一部分,因为您将进行的唯一“测试”是验证事件是否正确触发。
通常,测试事件意味着订阅它,并执行应该(或不应该)引发它的操作。
protected EventHandlerList Events {
get {
if (events == null) {
events = new EventHandlerList(this);
}
return events;
}
}
...
public event EventHandler Click {
add {
Events.AddHandler(EventClick, value);
}
remove {
Events.RemoveHandler(EventClick, value);
}
}
OnClick
,它可能与您习惯看到的非常不同...protected virtual void OnClick(EventArgs e) {
Contract.Requires(e != null);
EventHandler handler = (EventHandler)Events[EventClick];
if (handler != null) handler(this, e);
}
关于这里的代码,Contract.Requires(e != null);
, 不用担心,那是他们的合同管理框架,但要注意它从 EventHandlerList
中提取处理程序,然后如果该处理程序不为 null
,则会触发它。
还有一件值得注意的事情是,你可能不需要以完全相同的方式实现事件,但由微软发布的编程指南实际上在这个指南的第二部分中提到了 TrueWill 指出的竞态条件。你可以在 这里 找到那个指南。这其实是一份非常好的 Microsoft 指南。
现在,回到你的观点,我认为事件应该经过测试,下面是我过去使用过的机制...
private ManualResetEvent _eventRaised = new ManualResetEvent(false);
[TestMethod]
public void TestSomething()
{
_eventRaised.Reset();
// hook up the event to the target being tested
// NOW, in the event handler, issue _eventRaised.Set();
// do something to raise the event
_eventRaised.WaitOne();
}
我看了你的问题,不知道你是在问处理程序的主体还是处理程序是否正确绑定以处理事件。
正如你所说,处理程序的主体应该简单地调用另一个方法,如果它是公共的,你已经测试过了。
通常情况下,我不会对事件处理程序的连接进行单元测试,除非它们将在运行时更改,因为我很可能会在开发人员和集成测试中捕获到未绑定/解绑运行时、未连接的事件处理程序,而本应该连接。