可测试代码:在构造函数中附加事件处理程序

时间:2013-12-20 08:05:03

标签: c# unit-testing tdd

至于我的理解,写作(单元)可测试代码的一部分,构造函数不应该在构造函数中做真正的工作而只分配字段。到目前为止,这非常有效。但我遇到了一个问题,我不确定解决问题的最佳方法是什么。请参阅下面的代码示例。

class SomeClass
{
    private IClassWithEvent classWithEvent;

    public SomeClass(IClassWithEvent classWithEvent)
    {
        this.classWithEvent = classWithEvent;

        // (1) attach event handler in ctor.
        this.classWithEvent.Event += OnEvent;
    }

    public void ActivateEventHandling()
    {
        // (2) attach event handler in method
        this.classWithEvent.Event += OnEvent;
    }

    private void OnEvent(object sender, EventArgs args)
    {
    }
}

对我来说选项(1)听起来不错,但构造函数应该只分配字段。选项(2)感觉有点“太多”。

感谢任何帮助。

4 个答案:

答案 0 :(得分:4)

单元测试最多会测试SomeClass。因此,您通常会模拟classWithEvent。在ctor中使用classWithEvent的某种注入是很好的。 正如托马斯·韦勒所说,布线是现场分配。

选项2实际上是糟糕的恕我直言。好像你忽略了对ActivateEventHandling的调用,你最终得到了一个不正确的初始化类,需要传输需求的知识,在评论或其他方面调用ActivateEventHandling,这使得类更难使用,可能因为你已经调用了ActivateEventHandling并对其进行了测试,但是没有通知用户省略了激活但没有在{{1}时测试过您的课程没被叫,对吗? :)

编辑:这里可能有其他方法值得一提

根据范例,最好避免在课堂上布线事件。我需要对Stephen Byrne的答案进行相对评论。

布线可视为背景知识。 single responsibility principle表示课程应该只执行一项任务。此外,如果一个类不依赖于其他类,则可以使用更多功能。一个非常松散耦合的系统将提供许多具有事件和处理程序的类,并且不知道其他类。

然后环境负责将所有类连接在一起,以便与处理程序正确连接事件。 环境将创建上下文,其中类以有意义的方式彼此交互。 因此,在这种情况下,一个类不知道它将被绑定到谁,它实际上并不关心。如果它需要一个值,它会询问它,它要求的人不应该知道它。在这种情况下,甚至不会将一个接口注入到ctor中以避免依赖。这个概念类似于大脑中的神经元,因为它们也向环境发出信息,并期望答案不知道相邻的神经元。

但是我认为依赖于接口,如果它是通过依赖注入容器的某种方式注入另一个范例而不是错误。

在启动时连接所有类的环境的非常重要的任务可能导致运行时错误(通过功能和集成测试的非常好的测试覆盖来减轻这些,这对于大型项目来说可能是一项艰巨的任务)并且它如果您需要在启动时手动连接数十个类和可能数百个事件,那会非常烦人。 虽然我同意在环境中而不是在类本身中进行布线可能很好,但对于大规模代码来说这是不实际的。

Ralf Westphal(clean code developer initiative (sorry german only)的创始人之一)编写了一个软件,该软件在一个名为“基于事件的组件”的概念中自动执行布线(不一定是由他自己创造的)。它使用命名约定和与反射的签名匹配来将事件和处理程序绑定在一起。

答案 1 :(得分:2)

接线事件字段分配(因为委托只不过是指向方法的简单引用变量)。

所以选项(1)没问题。

答案 2 :(得分:1)

构造函数的要点不是“赋值字段”。它是建立对象的不变量,i。即在其一生中永远不会改变的东西。

因此,如果在其他类的方法中,你依赖于总是订阅某个对象,那么最好在构造函数中进行。

另一方面,如果订阅来去(这里可能不是这种情况),您可以将此代码移动到另一种方法。

答案 3 :(得分:0)

单一责任原则规定应避免接线。您的班级不应该关心如何或从哪里接收数据。将OnEvent方法重命名为更有意义的方法并将其公之于众是有意义的。

然后其他一些类(bootstrapper,configurator,无论如何)应该负责接线。您的班级应该只对新数据进入时发生的事情负责。

伪代码:

public interface IEventProvider //your IClassWithEvent
{
   Event MyEvent...
}

public class EventResponder : IEventResponder
{
   public void OnEvent(object sender, EventArgs args){...}
}

public class Boostrapper
{
   public void WireEvent(IEventProvider eventProvider, IEventResponder eventResponder)
  {
      eventProvider>event += eventResponder.OnEvent;
  }
}

注意,上面是伪代码,只是为了描述这个想法。

你的引导程序实际上是如何实现的取决于许多事情。它可以是您的“主要”方法,也可以是您的global.asax,或者您实际配置和准备应用程序的任何内容。

这个想法是,无论负责准备运行应用程序,都应该编写它,而不是类本身,因为它们应该尽可能地用于单一目的,并且不应该过多地关注它们的使用方式和位置