gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 08:21:31 25 4
gpt4 key购买 nike

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

我已将其分解为模块并在整个商店中都有接口(interface),但我仍然发现在从 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 不需要它们?我可以使用最小起订量对象对此进行测试,还是需要手动编写一个实现 DirEnum 并接受和“记录”构造函数参数的模拟对象?

注意这段代码是“示例”代码,即我没有逐字复制我的代码,但我认为我已经表达了足够的希望传达问题?如果您需要更多上下文,请询问!

感谢您的关注。温柔点,这是我第一次 ;-)

吉姆

最佳答案

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

接下来,您将在 string 的构造函数中注入(inject)原始类型( boolDirEnum )类型。我喜欢 MNrydengren 在评论中的表述:

take "compile-time" dependencies through constructor parameters and "run-time" dependencies through method parameters



我很难猜出该类应该做什么,但是由于您将这些在运行时发生变化的变量注入(inject)到 DirEnum 中。构造函数,你最终会得到一个难以测试的应用程序。

有多种方法可以解决此问题。想到的两个是方法注入(inject)的使用和工厂的使用。哪一个是可行的取决于你。

使用方法注入(inject),您的 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, string fileFilter,
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);
}
}

查看两个示例中的依赖项是如何注入(inject)到 Configurator 中的。类(class)。这称为 Dependency Injection pattern ,与服务定位器模式相反,其中 Configurator通过调用 Ninject 内核来请求它的依赖关系。

现在,由于您的 Configurator完全不受任何 IoC 容器的影响,您现在可以通过注入(inject)它期望的依赖项的模拟版本来轻松测试这个类。

剩下的就是在应用程序的顶部配置 Ninject 容器(在 DI 术语中: composition root)。对于方法注入(inject)示例,您的容器配置将保持不变,对于工厂示例,您需要替换 Bind<IDirEnum>().To<DirEnum>()符合如下内容:

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

当然,您需要创建 DirEnumFactory :

class DirEnumFactory : IDirEnumFactory
{
IDirEnum CreateDirEnum(string filePath, string fileFilter,
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 配置。这是非常有效的国际海事组织。我在我的应用程序中这样做。但是为此,您通常不需要 DI 容器,或者即使您需要,这并不意味着您的所有测试都应该依赖于容器。这种关系应该只存在于测试 DI 配置本身的测试中。这是一个测试:

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

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

// Assert
Assert.IsNotNull(configurator);
}

此测试间接依赖于 Ninject,当 Ninject 无法构造新的 Configurator 时,测试将失败。实例。当你保持你的构造函数没有任何逻辑并且只使用它来存储私有(private)字段中的依赖项时,你可以运行它,而没有调用数据库、Web 服务或其他任何东西的风险。

我希望这有帮助。

关于dependency-injection - 如何使用 MOQ 对象测试 Ninject ConstructorArguments?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6270176/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com