依赖注入:乌龟一路下来?

时间:2010-12-31 15:00:23

标签: unit-testing dependency-injection

所以我想知道单元测试在处理外部依赖关系方面是如何工作的。在这里和其他地方,我已经熟悉依赖注入,以及它如何允许我们测试代码的单元(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 ?我想,我可以再次使用依赖注入,但在某些时候某些单元需要实际创建外部依赖;那我该如何测试那个单位?

5 个答案:

答案 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。

相关问题