我已将其分解为模块并在整个商店中都有接口,但我仍然发现在从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的模拟对象并接受并“记录”构造函数参数?
注:这段代码是’示例’代码,即我没有逐字复制我的代码,但我想我已经表达了足够的希望能够传达这些问题?如果您需要更多背景信息,请询问!
谢谢你的期待.要温柔,这是我的第一次;-)
吉姆
接下来,您将在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服务或者其他任何东西的风险.
我希望这有帮助.