在解析一些接口时,我有一个 IoC 容器在做一些复杂的对象构造。我想在我的单元/集成测试中使用这些接口的实现。在使用 IoC 容器的测试中解决这些接口有什么问题吗,或者在这种情况下是否应该手动构建实例?
1 回答
当我们对一个类进行单元测试时,我们关心的是“类是否按照我们的意愿去做”。我们的出发点是一个完全构造的实例;我们如何到达那里不是单元测试问题,尽管它可能被认为是集成测试问题。
说我们有,
A
A(IB b, IC c)
B : IB
B(ID d)
C : IC
D : ID
其中:
IB
是 的接口B
,
IC
是 的接口C
,
ID
是 的接口D
。
在进行单元测试A
时,B
usesID
应该是没有实际意义的(如果不是,那么我们应该查看我们的接口。拥有A
访问权限IB.D.xxx()
并不好),我们需要做的就是提供A
一些实现(模拟/存根)IB
and 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 中的逻辑是否按预期工作,例如,使用参数y和zA
调用并返回 的结果IB.x()
IB.x()
。如果我们正在检查我们是否得到了正确的答案(例如,一个取决于逻辑的答案D
),那么我们已经通过了单元测试并进入了集成测试。
底线 只要我们正确隔离了被测单元,被测
单元是由 IOC 容器创建还是由人工创建都没有关系。如果我们使用容器来创建对象图,那么我们进行集成测试的可能性很大(和/或存在类之间过多耦合的问题 -调用)A
IB.D.xxx()
模拟集成测试
警告:以下部分内容取决于使用的 IOC 容器。使用 Unity 时,最后一次注册“获胜”。我不知道这是否适用于其他人。
在所讨论的极简主义系统中,我们有
A
A (IB b)
B : IB
B
是我们的“叶子”。它与外界对话(例如,从网络流中读取)。在集成测试时,我们想模拟它。
对于实时系统,我们使用CreateContainerCore()
. 这包括注册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"; }
}
}
}