所以我想知道单元测试在处理外部依赖关系方面是如何工作的。在这里和其他地方,我已经熟悉依赖注入,以及它如何允许我们测试代码的单元(A)。但是,我对如何测试现在具有外部依赖性的其他单位(B和C)感到困惑,因此他们可以将它注入原始单位(A)。
例如,假设某个类 Foo 使用外部依赖...
class Foo
{
private ExternalDependency ed;
public int doSomethingWithExternalDependency() {...}
}
班级栏关闭 Foo ......
class Bar
{
public int doSomethingWithFoo
{
Foo f = new Foo();
int x = f.doSomethingWithExternalDependency();
// Do some more stuff ...
return result;
}
}
现在,我知道我可以使用依赖注入,以便我可以测试 Foo ,但是我如何测试 Bar ?我想,我可以再次使用依赖注入,但在某些时候某些单元需要实际创建外部依赖;那我该如何测试那个单位?
答案 0 :(得分:16)
您提供的示例不使用依赖注入。相反,Bar应该使用构造函数注入来获取Foo实例,但是注入具体的类是没有意义的。相反,你应该从Foo中提取一个接口(让我们称之为IFoo)并将其注入Bar:
public class Bar
{
private IFoo f;
public Bar(IFoo f)
{
this.f = f;
}
public int doSomethingWithFoo
{
int x = this.f.doSomethingWithExternalDependency();
// Do some more stuff ...
return result;
}
}
这使您始终解除消费者和依赖关系。
是的,仍然有一个地方你必须编写整个应用程序的对象图。我们称这个地方为组合根。它是应用程序基础架构组件,因此您无需对其进行单元测试。
在大多数情况下,您应该考虑为该部分使用 DI容器,然后应用Register Resolve Release pattern。
答案 1 :(得分:3)
为了使用依赖注入,你的类将依赖注入(有几种方法 - 构造函数注入,属性注入)并且不会自己实例化它们,就像你在你的例子。
此外,可以提取每个依赖项的接口以帮助实现可测试性,并使用接口而不是实现类型作为依赖项。
class Foo
{
private IExternalDependency ed;
public int doSomethingWithExternalDependency() {...}
public Foo(IExternalDependency extdep)
{
ed = extdep;
}
}
大多数人所做的是在测试时使用mocking框架来模拟依赖项。
您可以模拟被测试的类所依赖的任何对象(包括行为和返回值) - 将模拟作为其依赖项传递给类。
这允许您在不依赖于其(已实现的)依赖项的行为的情况下测试该类。
在某些情况下,您可能希望使用假货或存根而不是模拟框架。请参阅Martin Fowler关于差异的this article。
至于获取所有依赖项,一直向下 - 一个使用IoC container。这是系统中所有依赖项的注册表,并了解如何使用其依赖项实例化每个类。
答案 2 :(得分:3)
请记住单元测试和集成测试之间的区别。在前者中,依赖性将是mocked,其中它为测试消耗依赖性的类提供了预期的行为。在后者中,初始化了依赖项的实际实例,以查看整个事物是否端到端地工作。
答案 3 :(得分:1)
当您对一个类进行单元测试时,您应该模拟其依赖项,以便单独测试您的类 - 这与依赖注入无关。
关于Bar的问题的答案是:是的,你应该注入Foo。沿着DI路径前进后,您将在整个堆栈中使用它。如果你真的需要为每个doSomethingWithFoo调用一个新的Foo,你可能想要注入一个FooFactory(你可以模拟它用于测试目的),如果你想让一个Bar使用很多Foos。
答案 4 :(得分:0)
我想强调的是,在单元测试的情况下,您应该有两组独立的测试:一组用于Foo.doSomethingWithExternalDependency,另一组用于Bar.doSomethingWithFoo。在后一组中创建Foo的模拟实现,并且假设doSomethingWithExternalDependency正常工作,您只测试doSomethingWithFoo。您可以在单独的测试集中测试doSomethingWithExternalDependency。