用于单元测试的重载构造函数

时间:2013-11-07 01:09:57

标签: c# unit-testing dependency-injection

依赖注入和单元测试的新手。

原因部分:

有一些dll:

  • MyApp.Services.Common.dll
  • MyApp.Services.ProductA.dll - >有ISomeDependency及其实现
  • MyApp.Services.ProductB.dll - >有IOtherDependency及其实现
  • MyApp.Presentation.WindowsService.dll

WindowsService仅引用Common.dll以便更轻松地进行测试,版本控制和部署。

问题是ProductA和B.dll中的任何依赖项都无法从WindowsService发送到Common dll,因为它需要WindowsService中的程序集引用到ProductA和B(不想要)

因此,在Common.dll中调用代码时,单元测试无法隔离依赖项。

因此,为了隔离依赖项,代码有一个重载的构造函数,它只公开依赖项以进行测试。

这可以吗?

见下面的例子:

单元测试将模拟依赖项并调用重载的构造函数,但实际代码调用默认构造函数

public class ClassUnderTest
{
  private ISomeDependency a;
  private IOtherDependency b;

  // constructor called by code
  public ClassUnderTest()
  {
     this.a = new SomeDependency();
     this.b = new OtherDependency();
  }
  public ClassUnderTest(ISomeDependency a, IOtherDependency b)
  {
    this.a = a;  
    this.b = b;
  }
}

4 个答案:

答案 0 :(得分:2)

我很惊讶没有人提到你可以使用构造函数链接。

public class ClassUnderTest
{
  readonly ISomeDependency a;
  readonly IOtherDependency b;

  public ClassUnderTest() : this(new SomeDependency(), new OtherDependency())
  {
  }

  public ClassUnderTest(ISomeDependency a, IOtherDependency b)
  {
     this.a = a;
     this.b = b;
  }
}

虽然我从根本上同意所有说过IOC容器是最佳方法的人。

答案 1 :(得分:1)

我建议你让IOC容器注入依赖项,而不是直接实例化你的具体类。如果这样做,您可以在两种情况下使用第二个构造函数(构造函数注入)。另一种方法是在类上定义公共属性并进行属性注入。

但是,如果您没有使用IOC容器,而是执行“手动依赖注入”,我认为当前模式没有任何问题。可以在代码中创建“钩子”以便于测试。

答案 2 :(得分:1)

使用依赖注入,最终必须注入依赖项。我更喜欢使用依赖注入容器来实现此目的。这是捕获:构成组件的对象需要具有对所有实际对象的程序集引用。

有几种方法可以做到这一点:

  • 将DI Container的配置代码放在Common.dll中,测试项目需要引用common来编写对象(common.dll有一些测试脚手架配置代码)
  • 测试项目引用了ProductA.dll和ProductB.dll,并且有一个DI容器配置来组装用于测试的对象,与Common.dll分开

真正的问题是你的构造函数。要正确测试,您需要让测试代码和生产代码使用相同的构造函数。这就是存在依赖注入容器的原因:处理为每个接口引用创建的对象的详细信息。

答案 3 :(得分:0)

  

但实际代码调用默认构造函数

在这种情况下,您的测试告诉您没有任何帮助您找出“真正的”代码在生产中爆炸的原因没有用,因为您实际上没有测试默认构造函数。

虽然我同意其他答案并敦促您使用IoC容器,但如果确实希望保留您当前拥有的模式,那么至少会暴露在默认情况下创建的依赖项{ {1}}作为公共属性,以便您可以测试它们:

ctor

也是依赖引用。

如果您将public class ClassUnderTest { readonly ISomeDependency a; readonly IOtherDependency b; public ISomeDependency A{Get{return a;}} public ISomeDependency B{Get{return b;}} // constructor called by code public ClassUnderTest() { this.a = new SomeDependency(); this.b = new OtherDependency(); } } [TestMethod] public void DefaultCTOR_CreatesDependencies() { var sut = new ClassUnderTest(); Assert.IsNotNull(sut.A,"sut didn't create SomeDependency A"); Assert.IsNotNull(sut.B,"sut didn't create OtherDependency B"); } ISomeDependency移动到他们自己的程序集中(实现),那么您将能够从IOtherDependencyProductA引用此dll ProductB个程序集以及Common

  • 这为您提供了松耦合,因为您的组装完全都是通过接口完成的。
  • 您会突然发现DI在放置注射器的位置方面变得更清晰(也更容易)
  • 您所要做的就是创建另一个引用所有内容的项目,它可以将ISomeDependency的请求解析为具体对象,并将这些对象提供给Common,{{ 1}}和ProductA 纯粹是通过接口,他们将不会更聪明:)