C# 结合单元测试(模拟)和依赖注入框架

C# 结合单元测试(模拟)和依赖注入框架,c#,unit-testing,dependency-injection,mocking,ninject,C#,Unit Testing,Dependency Injection,Mocking,Ninject,可能重复: 我想我在理解单元测试和/或依赖注入的工作方式方面确实存在问题。我使用NUnit和Rhino Mock进行单元测试,并将Ninject作为依赖项激励框架。总的来说,我认为这两种方法都很合适——但不知怎么的,它似乎变得更复杂,更难理解 (我将试着做一个好的例子,保持它干净和简单。这是关于我,骑自行车) 1.)无DI/单元测试: 如果不知道DI和单元测试,我的代码会是这样的——我很高兴: public class Person { public void Travel()

可能重复:

我想我在理解单元测试和/或依赖注入的工作方式方面确实存在问题。我使用NUnit和Rhino Mock进行单元测试,并将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");
    }
}
要骑自行车,我只需要:
newperson().Travel()

2.)带DI:
我不想要紧耦合,所以我需要一个接口和一个模块!我会有一些开销,但只要代码易于阅读和理解就可以了。我将只传递修改后的Person类的代码,Bike类保持不变:

public class Person
{
    IKernel kernel = new StandardKernel(new TransportationModule());
    public void Travel()
    {
        ITransportation transportation = kernel.Get<ITransportation>();
        transportation.Ride();
    }
}
这一次,我需要通过自行车:
新人(新自行车()).Travel()

4.)与DI合作并准备单元测试
全班三人一组。考虑到单元测试(没有DI)可以在不修改的情况下完成这项工作,但我需要调用
newperson(kernel.Get())。通过这一点,我感觉我失去了DI的好处——个人舱可以称之为旅行,而不需要任何耦合,也不需要知道交通是什么类型的。另外,我认为这个表单缺乏示例2的可读性

是这样做的吗?或者还有其他更优雅的方法来实现依赖注入和单元测试(和模拟)的可能性吗


(回头看,这个例子似乎真的很糟糕-每个人都应该知道他现在乘坐的是什么类型的运输设备…

一般来说,我尽量避免在单元测试中使用IoC容器-只使用模拟和存根传递依赖项

您的问题从场景2开始:这是而不是DI-这是最重要的。对于真正的依赖项注入,您需要传入依赖项,最好是通过构造函数注入

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

我甚至认为,如果您需要一个IoC容器,您的测试可能不够细粒度,或者您有太多的依赖项。在后一种情况下,可能需要进行一些重构,以便从正在使用的两个或多个依赖项中形成聚合类(当然,只有在存在语义连接的情况下)。这最终会将依赖项的数量降低到您满意的程度。对于每个人来说,最大数量是不同的,我个人努力做到最多4个,至少我一方面可以数一数,嘲笑也不是太大的负担

反对在单元测试中使用IoC容器的最后一个也是至关重要的论点是行为测试:如果您不能完全控制依赖项,您如何确保被测类的行为符合您的要求


可以说,您可以通过使用为某些操作设置标志的类型删除所有依赖项来实现这一点,但这是一项巨大的工作。使用RhinoMocks或Moq之类的模拟框架来验证是否使用您指定的参数调用了某些方法要容易得多。为此,您需要模拟要验证调用的依赖项,而IoC容器在这方面帮不了您。

您有些事情弄糊涂了

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

因此,当测试3号时,您会:

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

// setup exceptations on your mock

var person = new Person(transportationMock);
ITransportation-transportationMock=MockRepository.GenerateStricktMock();
//在模拟计算机上设置例外项
var人员=新人员(transportationMock);
DI框架仅在生产代码中构建对象树时才需要。在测试代码中,您可以完全控制要测试的内容。在对类进行单元测试时,您模拟了所有依赖项

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

完全隔离测试类的思想是,您可以控制每个代码路径。您可以使依赖项返回正确或不正确的值,甚至可以让它抛出异常。如果一切正常,并且仅仅通过单元测试就获得了很好的代码覆盖率,那么您只需要进行几个更大的测试,以确保DI连接正确

编写可测试代码的关键是将对象创建与业务逻辑分离

我的2美分

尽管第2点是依赖倒置原则(DIP)的一个示例,但它使用服务位置模式,而不是依赖注入

您的第3点演示了依赖项注入,在构建Person期间,IoC容器会将依赖项(ITransportation)注入构造函数

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

然后,这将建立Person及其依赖项(即为ITransportation配置的具体类),并将其注入Person(显然,在单元测试中,您的IoC将为模拟/存根ITransportation配置)

最后,是依赖关系t
ITransportation transportationMock = MockRepository.GenerateStricktMock<ITransportation>();

// setup exceptations on your mock

var person = new Person(transportationMock);