3

我正在使用依赖注入来模拟类,以便对依赖于它们的其他类进行单元测试:

class Foo : IFoo
{
    // production code
}

class MockFoo : IFoo
{
    // mock so other classes that depend on Foo can be unit tested
}

class Bar
{
    public DoSomething()
    {
        var barValue = 20;
        // use dependency injection to get Foo instance.
        DependencyInjection.Instance.Foo.ExampleMethod(barValue);
    }
}

然而,设置我的依赖注入类变得笨重、复杂和复杂:

public class DependencyInjection
{
    public Setup()
    {
        this.Foo = new Foo();
        this.Bar = new Bar("example constructor string");
        this.Bat = new Bat(123,234);
        // for every single class in my application!
    }
}

(为清楚起见进行了简化,但您可以想象具有数十个类及其构造函数的实际实现)。

还有一些其他问题:

  • DependencyInjection 及其每个类实例都是在我的应用程序中传递的巨大全局变量。
  • 我正在同时初始化我的所有课程。
  • 我必须为我为单元测试编写的每个类提供一个接口——我允许单元测试来决定我的程序的设计(如果没有这个约束,我编写的大多数类都是没有接口的具体实现)。

关于如何解决这些问题的建议将不胜感激!

4

4 回答 4

6

使用 DI 并不意味着您应该突然停止解耦代码。绝不应该使用全局变量来传递您的实现。

当您创建一个需要解决某个部分功能的类时,您应该通过构造函数传递所有“外部”依赖项:

class Bar
{
    private readonly IFoo _foo;
    public Bar(IFoo foo)
    {
        _foo = foo;
    }

    public DoSomething()
    {
        _foo.ExampleMethod(20)
    }
}

DI 的最佳实践是在应用程序的开头(组合根)使用它来获取外部实现,然后像没有 DI 一样传递实现。

底线是:你不需要为了测试而注入 - 只需在你的测试方法中模拟它,你就完成了。仅将 DI 用于您希望可配置的应用程序的重要块(例如,选择具体的数据层)。IFooBar

于 2012-10-28T18:42:58.460 回答
4

您所做的是服务定位器(反)模式。它提供隐藏的依赖注入(不清楚你的类需要什么依赖)。您需要的是方法调用注入,这将使您的依赖关系明确且易于模拟:

class Bar
{
    public DoSomething(IFoo foo)
    {
        var barValue = 20;
        // use dependency injection to get Foo instance.       
    }
}

另一种类型的依赖注入是构造函数注入(当IFoo通过构造函数参数提供依赖时)和属性注入(通过公共属性提供依赖)。

现在从 API 中可以清楚地看出Bar需要IFoo依赖才能完成它的工作。您可以轻松地模拟此依赖项(使用Moq的示例):

Mock<IFoo> fooMock = new Mock<IFoo>();
// setup mock
Bar bar = new Bar();
bar.DoSomething(fooMock.Object);

如何IFoo在运行时注入实现?您可以使用一些依赖注入框架,例如Ninject

于 2012-10-28T18:41:06.873 回答
2

让我试着回答你的要点:

依赖注入是一个概念。你不需要为它创建一个类。我建议为构造函数注入设计你的类。如果您想使用 DI 框架或实现自己的框架,通常的模式是有一个两步 API:注册您的依赖项并在应用程序启动时解决它们(或者更好地由 Groo 在组合根处解释)。

依赖注入通常会在启动时初始化类。但是如果您的对象生命周期不同,您可以定义生命周期或使用工厂。

在进行单元测试时,您不需要为您的类定义一个接口,而只需要为它的依赖项定义一个接口。这是这种方法的最大好处之一。在编写仅依赖于接口的类时,您可以非常轻松地为这些依赖项创建测试替身(模拟、存根等)。也有很多框架可以做到这一点,但是您也可以自由地使用您自己的这些接口的测试实现。

于 2012-10-28T19:04:47.097 回答
0

对于单元测试,我通常会重写构造函数以获取依赖项的实例。这样,我的单元测试就可以创建模拟对象并将其传入。

private IFoo foo;

public Bar()
{
    // Production code uses the real thing
    this.foo = new Foo();
}

public Bar(IFoo foo)
{
    // Test code uses a passed-in object, likely a mock
    this.foo = foo;
}

public DoSomething()
{
    foo.DoSomething();
}
于 2012-10-28T18:42:34.233 回答