如何在没有DI的情况下在紧密耦合的业务\数据层上启用单元测试?

时间:2012-06-01 22:06:51

标签: c# unit-testing dependency-injection

我正在开发一个较旧的3层设计项目,添加的任何新功能都需要进行单元测试。

问题是业务层/数据层紧密耦合,如下面的示例所示。 BL只是新闻数据层对象...所以几乎不可能以这种方式进行模拟。我们没有实现任何依赖注入,因此无法进行构造函数注入。那么,修改结构的最佳方法是什么,以便可以在不使用DI的情况下模拟数据层?

public class BLLayer()
{

   public GetBLObject(string params)
   {
     using(DLayer dl = new DLayer())
     {  
        DataSet ds = dl.GetData(params);

        BL logic here....

     }
   }
}

3 个答案:

答案 0 :(得分:5)

您不排除构造函数注入本身,您只是没有设置IOC容器。没关系,你不需要。你可以做穷人的依赖注入并仍然保持构造函数注入。

使用接口包装DataLayer,然后创建一个工厂,根据命令生成IDataLayer个对象。将此字段作为字段添加到您要注入的对象中,将所有new替换为对工厂的调用。现在你可以注入你的假货进行测试,如下所示:

interface IDataLayer { ... }
interface IDataLayerFactory 
{
   IDataLayer Create();
}    

public class BLLayer()
{
  private IDataLayerFactory _factory;

   // present a default constructor for your average consumer
  ctor() : this(new RealFactoryImpl()) {} 

  // but also expose an injectable constructor for tests
  ctor(IDataLayerFactory factory)
  { 
    _factory = factory;
  }  

  public GetBLObject(string params)
  {
    using(DLayer dl = _factory.Create())  // replace the "new"
    {  
      //BL logic here....    
    }
  }
}

不要忘记在实际代码中使用实际工厂的默认值。

答案 1 :(得分:3)

依赖注入只是属于“控制反转”这一概念的众多模式之一。主要标准是在组件之间提供“接缝”,以便您可以分开。简而言之,不止一种方法可以给猫皮肤。

依赖注入本身有几个派生:构造函数注入(通过构造函数传入的依赖项),属性注入(表示为读/写属性的依赖项)和方法注入(依赖项传递给方法)。这些模式假设类“关闭以进行修改”并暴露其依赖性以供消费者更改。遗留代码很少以这种方式设计,系统范围的体系结构更改(例如转移到构造函数注入和IoC容器)并不总是直截了当。

其他模式涉及在测试下将对象的分辨率和/或构造与对象分离。像工厂这样的简单四种模式可以创造奇迹。服务定位器就像一个全局对象工厂,虽然我不是这种模式的忠实粉丝,但它可以用来解耦依赖关系。

在上面概述的示例中,测试模式“Subclass to Test”允许您在没有系统范围的重新架构的情况下引入接缝。在模式中,将对象创建调用(如“new DLayer()”)移动到虚拟方法,然后创建主题的子类。

Micheal Feather的“使用传统代码”有一个模式和技术目录,您可以使用这些模式和技术将遗留代码置于允许您转向DI的状态。

答案 2 :(得分:2)

如果DLayer仅用于GetBLObject方法,我会在方法调用中注入工厂。类似于:(基于@PaulPhillips示例)

public GetBLObject(string params, IDataLayerFactory dataLayerFactory)
   {
       using(DLayer dl = dataLayerFactory.Create())  // replace the "new"
       {  
            //BL logic here....    
       }
   }

但是,您真正想要在业务层中使用的内容似乎是DataSet。另一种方法是让GetBLObject取代DataSet方法调用中的string param。为了完成这项工作,您可以创建一个只处理来自DataSet的{​​{1}}的类。例如:

DLayer

一个警告:模拟public class CallingBusinesslayerCode { public void CallingBusinessLayer() { // It doesn't show from your code what is returned // so here I assume that it is void. new BLLayer().GetBLObject(new BreakingDLayerDependency().GetData("param")); } } public class BreakingDLayerDependency { public DataSet GetData(string param) { using (DLayer dl = new DLayer()) //you can of course still do ctor injection here in stead of the new DLayer() { return dl.GetData(param); } } } public class BLLayer { public void GetBLObject(DataSet ds) { // Business Logic using ds here. } } (你必须同时使用这个和Paul Phillips解决方案)可能真的很麻烦,所以测试这个是可能的,但不一定很有趣。