如何在没有 DI 的情况下在紧密耦合的业务数据层上启用单元测试
本文关键字:业务 数据 单元测试 启用 耦合 DI 情况下 | 更新日期: 2023-09-27 18:36:14
我正在处理一个较旧的 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 解决方案中都必须这样做)可能非常麻烦,因此测试这是可能的,但不一定很有趣。