请勿在单元测试中使用DI容器。在单元测试中,您试图独立测试一个类或模块,并且在这个领域中几乎没有使用DI容器的用处。
与集成测试不同,因为您想要测试系统中的组件如何集成和协同工作。在这种情况下,您经常使用生产DI配置,并替换一些服务为虚拟服务(例如
EmailService
),但尽可能接近真实情况。在这种情况下,您通常会使用容器来解析整个对象图。
希望在单元测试中也使用DI容器的愿望,往往源于无效的模式。例如,如果您尝试在每个测试中创建被测试类及其所有依赖项,那么您将得到大量重复的初始化代码,并且类下的小变化可能会影响整个系统并需要更改数十个单元测试。这显然会导致可维护性问题。
过去帮助我解决这个问题的一个模式是使用简单的SUT特定工厂方法。该方法集中了被测试类的创建,并最小化了当被测试类的依赖关系发生变化时需要进行的更改量。以下是这样的工厂方法的示例:
private ClassUnderTest CreateClassUnderTest(
ILogger logger = null,
IMailSender mailSender = null,
IEventPublisher publisher = null)
{
return new ClassUnderTest(
logger ?? new FakeLogger(),
mailSender ?? new FakeMailer(),
publisher ?? new FakePublisher());
}
工厂方法的参数复制了类的构造函数参数,但将它们全部变成了可选项。对于任何未由调用方提供的特定依赖项,都会注入一个新的默认虚拟实现。
通常这种方法非常有效,因为在大多数测试中,您只关心一个或两个依赖项。其他依赖可能是类的功能所必需的,但对于特定测试可能并不重要。因此,工厂方法允许您仅提供测试中感兴趣的依赖项,同时从测试方法中删除未使用的依赖项的噪音。以下是使用工厂方法的测试方法示例:
public void Doing_something_will_always_log_a_message()
{
var logger = new ListLogger();
ClassUnderTest sut = CreateClassUnderTest(logger: logger);
sut.DoSomething();
Assert.IsTrue(logger.Count > 0);
}
如果你有兴趣学习如何编写易读、可信和可维护的(
RTM)测试,Roy Osherove的书
The Art of Unit Testing (second edition)是一本很好的读物。这本书对我理解如何编写出色的单元测试有很大帮助。如果你想深入了解依赖注入及其相关模式,请考虑阅读
Dependency Injection Principles, Practices, and Patterns(我是其中的合著者)。