依赖注入的问题 - 如何清理我的实现?

时间:2012-10-28 18:36:26

标签: c# .net unit-testing dependency-injection code-cleanup

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

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 个答案:

答案 0 :(得分:6)

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

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

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

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

DI的最佳实践是在应用程序的开头(composition root)使用它来获取外部实现,然后像没有DI那样执行实现。

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

答案 1 :(得分:4)

你做的是Service Locator(反)模式。它提供了隐藏的依赖注入(不清楚你的类需要什么依赖项)。您需要的是方法调用注入,它将使您的依赖项显式且易于模拟:

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

答案 2 :(得分:2)

让我试着回答你的要点:

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

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

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

答案 3 :(得分: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();
}