不要在单元测试中使用 DI 容器。在单元测试中,您尝试单独测试一个类或模块,而在该领域几乎没有使用 DI 容器。
集成测试的情况有所不同,因为您想测试系统中的组件如何集成和协同工作。在这种情况下,您经常使用您的生产 DI 配置并将您的一些服务替换为虚假服务(例如 EmailService),但尽可能接近真实的东西。在这种情况下,您通常会使用 Container 来解析整个对象图。
在单元测试中使用 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()
{
// Arrange
var logger = new ListLogger();
ClassUnderTest sut = CreateClassUnderTest(logger: logger);
// Act
sut.DoSomething();
// Arrange
Assert.IsTrue(logger.Count > 0);
}
如果您有兴趣学习如何编写可读、可信赖和可维护 (RTM) 测试,Roy Osherove 的书The Art of Unit Testing (second edition) 是一本极好的读物。这极大地帮助了我理解编写出色的单元测试。如果您有兴趣深入了解依赖注入及其相关模式,请考虑阅读Dependency Injection Principles, Practices, and Patterns(我与人合着)。