Unit testing 依赖注入:海龟们一路下来?

Unit testing 依赖注入:海龟们一路下来?,unit-testing,dependency-injection,Unit Testing,Dependency Injection,所以我想知道单元测试在处理外部依赖方面是如何工作的。在这里和其他地方,我已经熟悉了依赖注入,以及它如何允许我们测试代码单元。然而,我对如何测试其他单元(B和C)感到困惑,这些单元现在具有外部依赖性,因此它们可以将其注入到原始单元(A)中 例如,假设某个类Foo使用外部依赖项 class Foo { private ExternalDependency ed; public int doSomethingWithExternalDependency() {...} } 而clas

所以我想知道单元测试在处理外部依赖方面是如何工作的。在这里和其他地方,我已经熟悉了依赖注入,以及它如何允许我们测试代码单元。然而,我对如何测试其他单元(B和C)感到困惑,这些单元现在具有外部依赖性,因此它们可以将其注入到原始单元(A)中

例如,假设某个类Foo使用外部依赖项

class Foo
{
    private ExternalDependency ed;
    public int doSomethingWithExternalDependency() {...}
}
而classBar使用offFoo

class Bar
{
    public int doSomethingWithFoo
    {
        Foo f = new Foo();
        int x = f.doSomethingWithExternalDependency();
        // Do some more stuff ...
        return result;
    }
}

现在,我知道我可以使用依赖注入来测试Foo,但是如何测试Bar?我想,我可以再次使用依赖注入,但在某个点上,某些单元需要实际创建外部依赖;那么我该如何测试这个单元呢?

当您对一个类进行单元测试时,您应该模拟它的依赖关系,以孤立地测试您的类——这与依赖关系注入无关


你关于酒吧的问题的答案是:是的,你应该注射Foo。一旦沿着DI路径走下去,您将在整个堆栈中使用它。如果您真的需要为每个doSomethingWithFoo调用注入一个新的Foo,那么您可能需要注入一个FooFactory(然后可以为测试目的对其进行模拟),如果您想要一个单条来使用多个Foo。

为了使用依赖项注入,您的类将把它们的依赖项注入其中(有几种方法可以做到这一点——构造函数注入、属性注入),并且不会像您在示例中那样自己实例化它们

此外,可以提取每个依赖项的接口,以帮助提高可测试性,并使用接口而不是实现类型作为依赖项

class Foo
{
    private IExternalDependency ed;
    public int doSomethingWithExternalDependency() {...}

    public Foo(IExternalDependency extdep)
    {
      ed = extdep;
    }
}

大多数人所做的是在测试时使用一个框架来模拟依赖关系

您可以模拟被测试类所依赖的任何对象(包括行为和返回值)——将模拟作为其依赖项传递给类

这允许您测试类,而不依赖其(已实现)依赖项的行为

在某些情况下,您可能希望使用赝品或存根,而不是模拟框架



至于获取所有依赖项,始终使用。这是系统中所有依赖项的注册表,了解如何实例化每个类及其依赖项。

您提供的示例不使用依赖项注入。相反,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容器< /强>这部分,然后应用.

记住单元测试和集成测试之间的区别。前者依赖于它提供预期的行为,以测试消耗依赖性的类。然后,对依赖项的实际实例进行初始化,以查看整个过程是否端到端工作。

我想强调的是,在单元测试的情况下,您应该有两个单独的测试集:一个用于Foo.doSomethingWithExternalDependency,另一个用于Bar.doSomethingWithFoo。在后一个测试集中,创建模拟实现假设doSomethingWithExternalDependency工作正常,则只测试doSomethingWithFoo。在单独的测试集中测试doSomethingWithExternalDependency。

+1仅标题:)但是,你可能想用你要问的语言来标记它。@Phrogz我看不出语言在这里有多重要。@Phrogz也许@guy是少数几个不太关心语言而只关心想法和概念的开发人员之一。你说得对,语言基本上不重要,除了理解范围规则和“公共”和类所传达的其他概念。Ruby的答案可能与C或F#的答案不同。小心,在某些地方,这被认为是欺负依赖关系。我不会创建一个接口来注入一些东西。由于测试或生产代码必须注入另一个实现,我会在对多态性有强烈需求之后创建一个接口。否则,不必要的接口会使代码更难理解。你这样做不是为了“注入”。您这样做是为了在代码中引入接缝;将紧密耦合的事物解耦。但是,尽管如此,我同意不必要的接口会使代码更难理解,这就是为什么我们应该对每个接口进行充分的思考: