dependency-injection – 如何使用MOQ对象测试Ninject ConstructorArguments?

我最近一直在做我的第一个测试驱动开发项目,并且一直在学习Ninject和MOQ.这是我对这一切的第一次尝试.我发现TDD方法一直在发人深省,Ninject和MOQ一直很棒.我正在开发的项目并不特别适合Ninject,因为它是一个高度可配置的C#程序,旨在测试Web服务接口的使用.

我已将其分解为模块并在整个商店中都有接口,但我仍然发现在从Ninject内核获取服务的实现时我必须使用大量的构造函数参数.例如;

在我的Ninject模块中;

Bind<IDirEnum>().To<DirEnum>()

我的DirEnum课程;

public class DirEnum : IDirEnum
{
    public DirEnum(string filePath,string fileFilter,bool includeSubDirs)
    {
        ....

在我的Configurator类中(这是主入口点)将所有服务挂钩在一起;

class Configurator
{

    public ConfigureServices(string[] args)
    {
        ArgParser argParser = new ArgParser(args);
        IDirEnum dirEnum = kernel.Get<IDirEnum>(
            new ConstructorArgument("filePath",argParser.filePath),new ConstructorArgument("fileFilter",argParser.fileFilter),new ConstructorArgument("includeSubDirs",argParser.subDirs)
        );

filePath,fileFilter和includeSubDirs是程序的命令行选项.到现在为止还挺好.然而,作为一个尽职尽责的人,我有一个覆盖这段代码的测试.我想使用MOQ对象.我为我的测试创建了一个Ninject模块;

public class TestNinjectModule : NinjectModule
{
    internal IDirEnum mockDirEnum {set;get};
    Bind<IDirEnum>().ToConstant(mockDirEnum);
}

在我的测试中,我像这样使用它;

[TestMethod]
public void Test()
{
    // Arrange
    TestNinjectModule testmodule = new TestNinjectModule();
    Mock<IDirEnum> mockDirEnum = new Mock<IDirEnum>();
    testModule.mockDirEnum = mockDirEnum;
    // Act
    Configurator configurator = new Configurator();
    configurator.ConfigureServices();
    // Assert

    here lies my problem! How do I test what values were passed to the
    constructor arguments???

所以上面显示了我的问题.如何测试传递给模拟对象的ConstructorArguments的参数?我的猜测是Ninject在这种情况下分配ConstuctorArguments,因为Bind不需要它们?我可以使用MOQ对象测试它,还是需要手动编写实现DirEnum的模拟对象并接受并“记录”构造函数参数?

注:这段代码是’示例’代码,即我没有逐字复制我的代码,但我想我已经表达了足够的希望能够传达这些问题?如果您需要更多背景信息,请询问!

谢谢你的期待.要温柔,这是我的第一次;-)

吉姆

您设计应用程序的方式存在一些问题.首先,您直接在代码中调用Ninject内核.这称为 Service Locator patternit is considered an anti-pattern.它使您的应用程序测试更加困难,您已经体验过这一点.您正在尝试在单元测试中模拟Ninject容器,这会使事情变得非常复杂.

接下来,您将在DirEnum类型的构造函数中注入原始类型(string,bool).我喜欢MNrydengren在评论中陈述的内容:

take “compile-time” dependencies
through constructor parameters and
“run-time” dependencies through method
parameters

我很难猜出该类应该做什么,但是因为你将这些在运行时更改的变量注入到DirEnum构造函数中,所以最终会得到一个难以测试的应用程序.

有多种方法可以解决这个问题.记住的两个方法是使用方法注入和使用工厂.哪一个是可行的取决于你.

使用方法注入,您的Configurator类将如下所示:

class Configurator
{
    private readonly IDirEnum dirEnum;

    // Injecting IDirEnum through the constructor
    public Configurator(IDirEnum dirEnum)
    {
        this.dirEnum = dirEnum;
    }

    public ConfigureServices(string[] args)
    {
        var parser = new ArgParser(args);

        // Inject the arguments into a method
        this.dirEnum.SomeOperation(
            argParser.filePath
            argParser.fileFilter
            argParser.subDirs);
    }
}

使用工厂,您需要定义一个知道如何创建新IDirEnum类型的工厂:

interface IDirEnumFactory
{
    IDirEnum CreateDirEnum(string filePath,bool includeSubDirs);
}

您的Configuration类现在可以依赖于IDirEnumFactory接口:

class Configurator
{
    private readonly IDirEnumFactory dirFactory;

    // Injecting the factory through the constructor
    public Configurator(IDirEnumFactory dirFactory)
    {
        this.dirFactory = dirFactory;
    }

    public ConfigureServices(string[] args)
    {
        var parser = new ArgParser(args);

        // Creating a new IDirEnum using the factory
        var dirEnum = this.dirFactory.CreateDirEnum(
            parser.filePath
            parser.fileFilter
            parser.subDirs);
    }
}

请参阅两个示例中的依赖关系如何注入Configurator类.这称为Dependency Injection pattern,与Service Locator模式相反,Configurator通过调用Ninject内核来询问其依赖性.

现在,既然您的Configurator完全没有任何IoC容器,那么现在可以通过注入它所期望的依赖项的模拟版本来轻松地测试这个类.

剩下的就是在应用程序的顶部配置Ninject容器(在DI术语中:composition root).使用方法注入示例,您的容器配置将保持不变,在工厂示例中,您需要将Bind< IDirEnum>()更改为< DirEnum>()行,其内容如下:

public static void Bootstrap()
{
    kernel.Bind<IDirEnumFactory>().To<DirEnumFactory>();
}

当然,您需要创建DirEnumFactory:

class DirEnumFactory : IDirEnumFactory
{
    IDirEnum CreateDirEnum(string filePath,bool includeSubDirs)
    {
        return new DirEnum(filePath,fileFilter,includeSubDirs);
    }        
}

警告:请注意,工厂抽象在大多数情况下不是最好的设计,如here所述.

您需要做的最后一件事是创建一个新的Configurator实例.您可以按如下方式执行此操作:

public static Configurator CreateConfigurator()
{
    return kernel.Get<Configurator>();
}

public static void Main(string[] args)
{
    Bootstrap():
    var configurator = CreateConfigurator();

    configurator.ConfigureServices(args);
}

在这里我们称之为内核.虽然应该防止直接调用容器,但是在应用程序中始终至少有一个地方可以调用容器,因为它必须连接所有内容.但是,我们尝试最小化容器被直接调用的次数,因为它改进了其他东西 – 代码的可测试性.

看看我没有真正回答你的问题,但展示了一种非常有效地解决问题的方法.

您可能仍想测试DI配置.这是非常有效的IMO.我在我的应用程序中这样做.但为此,您通常不需要DI容器,或者即使您这样做,这并不意味着您的所有测试都应该依赖于容器.这种关系应仅存在于测试DI配置本身的测试中.这是一个测试:

[TestMethod]
public void DependencyConfiguration_IsConfiguredCorrectly()
{
    // Arrange
    Program.Bootstrap();

    // Act
    var configurator = Program.CreateConfigurator();

    // Assert
    Assert.IsNotNull(configurator);
}

此测试间接依赖于Ninject,当Ninject无法构造新的Configurator实例时,它将失败.当你保持你的构造函数不受任何逻辑的影响并且仅用于在私有字段中存储所采用的依赖项时,你可以运行它,而不会有调用数据库,Web服务或者其他任何东西的风险.

我希望这有帮助.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结