2

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

为什么部分:

有一些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

4 回答 4

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 容器是最好的方法的观点。

于 2013-11-07T14:17:26.587 回答
1

使用依赖注入,最终必须注入依赖。为此,我更喜欢使用依赖注入容器。这里有一个问题:进行组合的对象需要对所有实际对象具有程序集引用。

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

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

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

于 2013-11-07T01:23:37.103 回答
1

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

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

于 2013-11-07T01:18:10.353 回答
0

但真正的代码调用默认构造函数

在这种情况下,您的测试不会告诉您任何有用的信息来帮助您弄清楚为什么“真实”代码在生产中会爆炸,因为您实际上并没有测试默认构造函数。

虽然我同意其他答案并敦促您使用 IoC 容器,但如果您真的想保留当前拥有的模式,那么至少将在默认值中创建的依赖项公开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");
}

还要重新依赖引用。

如果您移动ISomeDependencyIOtherDependency进入他们自己的程序集(而不是实现),那么您将能够从ProductAProductB程序集以及您的程序集中引用此 dllCommon

  • 这为您提供了松散耦合,因为您的组件间仅通过接口进行。
  • 您会突然发现 DI 在放置喷油器的位置方面变得更加清晰(也更容易)
  • 您所要做的就是创建另一个引用所有内容的项目,它可以将请求解析为ISomeDependency具体对象,并将这些对象提供给CommonProductA并且ProductB 纯粹通过 interface,它们不会更聪明:)
于 2013-11-07T14:10:06.410 回答