我正在使用依赖注入来模拟类,以便对依赖于它们的其他类进行单元测试:
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!
}
}
(为了清晰起见,简化了,但你可以想象几十个类及其构造函数的实际实现)。
还有许多其他问题:
如何解决这些问题的建议将不胜感激!
答案 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();
}