请帮助我澄清以下设计方法:
在我们使用UnityContainer
的单元测试项目中。在每个测试的开始,我们正在解析一个对象,通过该容器进行测试:
IObjectToTest objectToTest = container.Resolve<IObjectToTest>();
在Unity配置中,我们配置了IObjectToTest
的具体实现。
在测试中,我们测试了这个接口IObjectToTest
objectToTest,尽管该对象的具体实例。
问题是:这种方法的优点和缺点是什么?为什么我们不能实例化新的ObjectToTest
(具体实现)并测试它?为什么我们使用Unity和界面?
答案 0 :(得分:3)
嗯,在这种方法中没有什么奇怪的,实际上它比你想象的更常见。当你想编写可测试的代码时,你会发现自己使用了大量的依赖注入。
好处显而易见,您可以轻松地交换具体实现而无需重新编写所有测试,您可以对实现进行存根并模拟它们以仅测试您对测试感兴趣的内容。因此,简单来说,您不仅仅测试通过容器测试(和注入)具体实现的接口。这是一种非常常见的方法,总是通过接口使用依赖注入是一种很好的做法。
答案 1 :(得分:1)
对界面的测试通常被认为是最佳实践。您测试接口而不是具体实现的原因是创建实际的“单元”测试而不是集成测试。
如果您测试具有被测对象的依赖类的具体实现,那么您不是真正测试单元,而是集成。最终,集成在生产代码中非常重要,但如果对各个单元进行彻底和正确的测试,您无需担心。
通常,您仍然会编写一个集成测试来验证完整的连接实现,但会更加关注单元测试。
在下面的示例中,我们测试IObjectUnderTest
的行为,而不考虑IBusinessLogicObject
的实现。我们通过使用Moq框架模拟依赖项来验证两个对象之间的协作。通过这样做,我们简单地验证了两个对象之间发生了必要的协作,并且不关心IBusinessLogicObject的内部。这些问题更适合我们为IBusinessLogicObject编写的单元测试。
测试示例
IUnityContainer container = new UnityContainer();
Mock<IBusinessLogicObject> businessLogicObject = new Mock<IBusinessLogicObject>();
container.RegisterInstance<IBusinessLogicObject>(businessLogicObject.Object);
businessLogicObject
.Setup(bl => bl.SomeMethod("some-stub-parameter"))
.Returns("some expected value")
IObjectUnderTest subject = container.Resolve<IObjectUnderTest>();
var emptyResult = subject.MethodToBeTested("some-stub-parameter", "another-value");
Assert.AreEqual(string.Empty, emptyResult);
var result = subject.MethodToBeTested("some-stub-parameter", "businessLogic");
Assert.AreEqual("some expected value", result);
实施例
public ObjectUnderTest : IObjectUnderTest
{
private readonly IBusinessLogicObject businessLogicObject;
public ObjectUnderTest(IBusinessLogicObject businessLogicObject)
{
businessLogicObject = businessLogicObject;
}
string IObjectUnderTest.MethodToBeTested(string businessLogicParam, string someOtherParam)
{
if ( someOtherParam == "businessLogic")
{
return businessLogicObject.SomeMethod(businessLogicParam);
}
return string.Empty;
}
}