【发布时间】:2012-08-21 19:05:23
【问题描述】:
我有一个 IoC 容器在解析一些接口时做一些复杂的对象构造。我想在我的单元/集成测试中使用这些接口的实现。使用 IoC 容器在测试中解析这些接口有什么问题吗?还是应该在这种情况下手动构建实例?
【问题讨论】:
标签: unit-testing integration-testing ioc-container
我有一个 IoC 容器在解析一些接口时做一些复杂的对象构造。我想在我的单元/集成测试中使用这些接口的实现。使用 IoC 容器在测试中解析这些接口有什么问题吗?还是应该在这种情况下手动构建实例?
【问题讨论】:
标签: unit-testing integration-testing ioc-container
当我们对一个类进行单元测试时,我们关心的是'这个类是否按照我们的意愿去做'。 我们的出发点是一个完全构造的实例;我们如何到达那里不是单元测试问题,尽管它可能被视为集成测试问题。
假设我们有,
A
A(IB b, IC c)
B : IB
B(ID d)
C : IC
D : ID
其中:IB 是B 的接口,IC 是C 的接口,ID 是D 的接口。
当对A进行单元测试时,B使用ID这个事实应该没有实际意义(如果不是,那么我们应该看看我们的接口。让A访问IB.D.xxx()不好),所有我们需要做的是为A 提供IB 和IC 的一些实现(模拟/存根)。
就A 的单元测试而言,A 实例是手动创建的还是容器创建的并不重要。无论哪种方式,我们都会得到相同的对象。
只要我们将模拟作为第一级依赖项传递,那么在使用容器而不是手动创建对象时,就没有保存。仅当我们使用 IOC 创建对象图时才会进行保存,但如果我们这样做,那么我们将进入集成测试。这不一定是坏事,但我们需要明确我们的目标。
当对上述内容进行单元测试时,我们会为其创建单元测试
D
C
B (passing in a mocked/stubbed ID)
A (passing in mocked/stubbed IC and IB)
在对A 进行单元测试时,我们不需要将来自D 的正确答案通过B 传递到A。
我们所关心的是 A 中的逻辑按预期工作,例如,A 使用参数 y 和 z 调用 IB.x() 并返回结果IB.x()。如果我们正在检查我们是否得到了正确的答案(比如说,一个取决于D 中的逻辑的答案),那么我们已经通过了单元测试并进入了集成测试。
底线
被测单元是由 IOC 容器创建还是由手动创建并不重要只要我们正确隔离了被测单元。如果我们使用容器来创建对象图,那么我们进行集成测试的可能性很大(和/或存在类之间耦合过多的问题 - A 调用 IB.D.xxx())
模拟集成测试
警告:以下部分内容取决于所使用的 IOC 容器。使用 Unity 时,最后一次注册“获胜”。我不知道这是否适用于其他人。
在我们所讨论的极简主义系统中
A
A (IB b)
B : IB
B 是我们的“叶子”。它与外界对话(例如,从网络流中读取)。
在集成测试时,我们想模拟这个。
对于实时系统,我们使用CreateContainerCore() 设置 ServiceLocator。
这包括注册IB 的“实时”实现。
在执行需要 IB 模拟版本的集成测试时,我们使用 CreateContainerWithMockedExternalDependencies() 设置容器,该容器包装 CreateContainerCore() 并为 IB 注册一个模拟对象。
下面的代码被大大简化了,但形状可以根据需要扩展到尽可能多的类和依赖项。在实践中,我们有一个基础测试类来帮助设置服务定位器、模拟/存根类、访问模拟以进行验证和其他内部维护(例如,每个测试都不需要显式设置 ServiceLocator 提供程序)
[TestClass]
public class IocIntegrationSetupFixture {
[TestMethod]
public void MockedB() {
ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(CreateContainerWithMockedExternalDependencies()));
var a = ServiceLocator.Current.GetInstance<A>();
Assert.AreEqual("Mocked B", a.GetMessage());
}
[TestMethod]
public void LiveB() {
ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(CreateContainerCore()));
var a = ServiceLocator.Current.GetInstance<A>();
Assert.AreEqual("Live B", a.GetMessage());
}
private IUnityContainer CreateContainerCore() {
var container = new UnityContainer();
container.RegisterType<IB, B>(new ContainerControlledLifetimeManager());
return container;
}
private IUnityContainer CreateContainerWithMockedExternalDependencies() {
var container = CreateContainerCore();
var mockedB = new Mock<IB>();
mockedB.SetupGet(mk => mk.Message).Returns("Mocked B");
container.RegisterInstance<IB>(mockedB.Object);
return container;
}
public class A {
private IB _b;
public A(IB b) {
_b = b;
}
public string GetMessage() {
return _b.Message;
}
}
public interface IB {
string Message { get; }
}
private class B : IB {
public string Message {
get { return "Live B"; }
}
}
}
【讨论】: