【问题标题】:Overload constructor for the purpose of unit testing用于单元测试的重载构造函数
【发布时间】:2013-11-07 01:09:57
【问题描述】:

对依赖注入和单元测试有点新意。

为什么部分:

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

【问题讨论】:

    标签: c# unit-testing dependency-injection


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

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

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

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

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

        【讨论】:

          【解决方案4】:

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

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

          虽然我同意其他答案并敦促您使用 IoC 容器,但如果您真的想要保留您当前拥有的模式,那么至少公开在默认 @ 中创建的依赖项987654321@ 作为公共属性,以便您可以测试它们:

          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 程序集中引用此 dll从你的Common

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-03-23
            • 2017-09-15
            • 2018-09-14
            • 2010-09-26
            • 2015-06-29
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多