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



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

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

public class BLLayer()
{
   public GetBLObject(string params)
   {
     using(DLayer dl = new DLayer())
     {  
        DataSet ds = dl.GetData(params);
        BL logic here....
     }
   }
}

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

使用接口包装数据层,然后创建一个工厂,该工厂将根据命令生成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....    
    }
  }
}

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

依赖注入只是属于称为"控制反转"的总括概念的众多模式之一。主要标准是在组件之间提供一个"接缝",以便您可以分离 简而言之,剥猫皮的方法不止一种。

依赖关系注入

本身有几个派生词:构造函数注入(通过构造函数传入的依赖关系)、属性注入(表示为读/写属性的依赖关系)和方法注入(依赖关系传递到方法中)。这些模式假定类"因修改而关闭",并公开其依赖项供使用者更改。旧代码很少以这种方式设计,系统范围的体系结构更改(例如迁移到构造函数注入和 IoC 容器)并不总是直截了当的。

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

在上面概述的示例中,测试模式"要测试的子类"将允许您引入接缝,而无需系统范围的重新架构。在模式中,您将对象创建调用(如"new DLayer()")移动到虚拟方法,然后创建主题的子类。

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

>如果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。为了使它工作,您可以创建一个类来处理仅从DLayer获取DataSet。例如:

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.
    }
}

一个警告:模拟DataSet(在 this 和 Paul Phillips 解决方案中都必须这样做)可能非常麻烦,因此测试这是可能的,但不一定很有趣。

最新更新