2012-02-22 86 views
7

可能重复:
Using IoC for Unit Testing结合单元测试(嘲讽)和Dependecy注入框架

我想我有一个问题理解的方式单元测试和/或依赖注入是加工。我正在使用NUnit和Rhino Mocks进行单元测试,并将Ninject用作依赖方式框架。总的来说,我认为虽然这两种方法都适合完美 - 但不知何故,它似​​乎变得更加复杂和难以理解。 (我会试着做一个很好的例子,保持它的干净和容易,这是关于我,骑自行车)。

1)无DI /单元测试:
不知道DI和单元测试的,我的代码看起来会是这样的 - 我会很高兴:

public class Person 
{ 
    public void Travel() 
    { 
     Bike bike = new Bike(); 
     bike.Ride(); 
    } 
} 

public class Bike 
{ 
    public void Ride() 
    { 
     Console.WriteLine("Riding a Bike"); 
    } 
} 

要骑我自行车我只是需要:随着DI new Person().Travel();

2):
我不希望这样的紧耦合,所以我需要一个接口和一个NinjectModule!我会有一些开销,但只要代码易于阅读和理解,就没有问题。我只是通过代码修改的Person类,自行车类是不变的:

public class Person 
{ 
    IKernel kernel = new StandardKernel(new TransportationModule()); 
    public void Travel() 
    { 
     ITransportation transportation = kernel.Get<ITransportation>(); 
     transportation.Ride(); 
    } 
} 

我仍然可以骑我的自行车刚:new Person().Travel();

3)考虑到单元测试(不DI):
为了能够检查乘车方法是否正确调用,我需要一个模拟。据我所知,通常有两种注入接口的方法:构造注入注入注入。我选择构造器注入我的例子:

public class Person 
{ 
    ITransportation transportation; 

    public person(ITransportation transportation) 
    { 
     this.transportation = transportation; 
    } 

    public void Travel() 
    { 
     transportation.Ride(); 
    } 
} 

这一次,我会NEET通过自行车:new Person(new Bike()).Travel();

4)随着DI和单元测试
类准备3.考虑单元测试(无DI)将不做修改地完成这项工作,但我需要致电new Person(kernel.Get<ITransportation>());。通过这一点,感觉就像我放弃了DI的好处 - 人员类可以称之为旅行,不需要任何耦合,并且需要知道交通是什么类。另外,我认为这种形式缺少很多示例2的可读性。

这是怎么完成的?还是有其他 - 更优雅的方式实现依赖注入和单元测试(和模拟)的可能性?

(回想起来,这个例子看起来很糟糕 - 每个人都应该知道他目前骑着什么样的运输工具......)

+0

我想你可能会对依赖注入(DI)和控制反转(IoC)的定义感到困惑。你是第三个数字*实现DI(你已经将依赖关系移出构造函数),第二个数字是使用IoC(Ninject)容器来解决依赖关系。 – 2012-02-22 14:13:58

+0

zapthedingbat是对的。你在第三个例子中做DI,但不要使用DI容器,这很好。 DI容器在进行DI时是可选的。 – Steven 2012-02-22 14:18:22

+1

示例“2.”不使用DI,而是一个“服务定位器”... – 2012-08-24 13:11:17

回答

10

通常我会尽量避免使用IoC容器进行单元测试 - 只需使用mock和存根传递依赖关系即可。

您的问题始于情景2:这是而不是 DI - 这是service locator (anti-)pattern。对于真正的依赖关系注入您需要传递您的依赖关系,最好通过构造函数注入。

场景3看起来不错,这是DI,通常也是如何启用测试你的类在隔离 - 传递你需要的依赖关系。我很少发现需要使用完整的DI容器进行单元测试,因为每个受测试的类都只有一些依赖关系,每个依赖关系都可以被存根或模拟以执行测试。

我甚至会争辩说,如果你需要需要一个IoC容器,你的测试可能不够精细或者你有太多的依赖关系。在后一种情况下,一些重构可能是为了从您正在使用的两个或更多的依赖关系中形成聚合类(只有当然存在任何语义连接时)。这将最终将依赖关系的数量降低到您所熟悉的级别。对于每个人来说,这个最大数量是不同的,我个人努力争取最多有4个,至少我可以一方面指望他们,而嘲弄并不是太大的负担。

对在单元测试中使用IoC容器最后一个和关键的参数是行为测试:你怎么能确定被测类的行为,你想它,如果你是不是在你的依赖的完全控制的方式吗?

可以通过将所有依赖关系设置为某些操作的标志类型来实现这一点,但这是一项很大的努力。使用像RhinoMocks或Moq这样的模拟框架更容易,通过验证某些方法是使用指定的参数调用的。为此,您需要嘲笑要验证呼叫的依赖关系,但IoC容器无法帮助您。

4

你有些困惑。

实现3比2更好,因为您不需要在单元测试中设置DI框架。

因此测试号3时,你会怎么做:

ITransportation transportationMock = MockRepository.GenerateStricktMock<ITransportation>(); 

// setup exceptations on your mock 

var person = new Person(transportationMock); 

的DI框架的东西在生产代码构造的对象树时唯一需要。在您的测试代码中,您可以完全控制要测试的内容。当单元测试一个类时,你嘲笑所有的依赖关系。

如果您还想做一些集成测试,您可以将真正的自行车传递给您的人员并进行测试。

完全隔离测试类的想法是您可以控制每个代码路径。您可以使依赖项返回正确或不正确的值,或者甚至可以让它引发异常。如果一切正常,并且只需通过单元测试即可获得良好的代码覆盖范围,则只需进行几次较大的测试即可确保DI连线正确。

编写可测试代码的关键是将对象创建从业务逻辑中分离出来。

3

我2美分...

虽然2点是依赖倒置原则(DIP)的一个例子,它使用了服务定位模式,而不是依赖注入。

您的观点3说明依赖注入,其中IoC容器将在构建Person期间将依赖关系(ITransportation)注入构造函数。

在您的真实应用程序和单元测试中,您希望使用IoC容器来构建人员(即不直接新人员)。如果您的单元测试框架支持,请使用服务定位器模式(kernel.Get<Person>();)或DI(例如Setter)。

那么这将建立个人和它的依赖(即用于ITransportation的配置的具体类),并注入到这一点的人,以及(显然,在单元测试你的IoC将被配置为嘲笑/存根ITransportation)

最后,它是您想要模拟的依赖项,即ITransportation,以便您可以测试Person的Transport()方法。由于Bike没有依赖关系,因此可以直接/独立地进行单元测试(除非在Bike中添加了依赖关系,否则不需要模拟来测试Bike.Ride())。