我只是想知道在内核中重新连接绑定的最佳实践是什么。
我有一个带有内核的类和一个带有默认生产绑定的私有类模块。
对于测试,我想覆盖这些绑定,以便可以交换我的 Test Doubles / Mocks 对象。
做
MyClass.Kernel.Load(new InlineModule(m=> m.Bind<IDepend>().To<TestDoubleDepend>()))
覆盖 IDepend 的任何现有绑定?
我只是想知道在内核中重新连接绑定的最佳实践是什么。
我有一个带有内核的类和一个带有默认生产绑定的私有类模块。
对于测试,我想覆盖这些绑定,以便可以交换我的 Test Doubles / Mocks 对象。
做
MyClass.Kernel.Load(new InlineModule(m=> m.Bind<IDepend>().To<TestDoubleDepend>()))
覆盖 IDepend 的任何现有绑定?
我尝试尽可能少地直接在我的代码中使用 DI 内核,而不是依赖于构造函数注入(或某些情况下的属性,例如Attribute
类)。但是,我必须使用抽象层,以便设置 DI 内核对象,使其在单元测试中可模拟。
例如:
public interface IDependencyResolver : IDisposable
{
T GetImplementationOf<T>();
}
public static class DependencyResolver
{
private static IDependencyResolver s_resolver;
public static T GetImplementationOf<T>()
{
return s_resolver.GetImplementationOf<T>();
}
public static void RegisterResolver( IDependencyResolver resolver )
{
s_resolver = resolver;
}
public static void DisposeResolver()
{
s_resolver.Dispose();
}
}
使用这样的模式,您可以IDependencyResolver
通过调用RegisterResolver
模拟或假实现来设置 from 单元测试,该实现返回您想要的任何对象,而无需连接完整的模块。如果您将来选择切换到不同的 IoC 容器,它还具有从特定 IoC 容器中抽象代码的第二个好处。
当然,您还想根据需要添加其他方法IDependencyResolver
,我只是在此处包括基础知识作为示例。是的,这将要求您围绕 Ninject 内核编写一个超级简单的包装器,该内核也实现IDependencyResolver
了。
您想要这样做的原因是您的单元测试实际上应该只测试一件事,并且通过使用您的实际 IoC 容器,您实际上锻炼的不仅仅是一个被测类,这可能会导致您的测试出现误报脆弱并且(更重要的是)随着时间的推移动摇开发人员对其准确性的信心。这可能导致测试冷漠和放弃,因为测试可能会失败,但软件仍然可以正常工作(“别担心,总是失败,这没什么大不了的”)。
我只是希望这样的事情有效
var kernel = new StandardKernel(new ProductionModule(config));
kernel.Rebind<ITimer>().To<TimerImpl>().InSingletonScope();
其中 ProductionModule 是我的生产绑定,我通过在特定测试用例中调用 Rebind 来覆盖。我对我重新绑定的几个项目调用重新绑定。
优点:如果有人向生产模块添加新绑定,我会继承它们,这样它就不会以这种方式破坏,这很好。这一切都在 Java 中的 Guice 中工作......希望它也能在这里工作。
我倾向于做的是有一个单独的测试项目完成它自己的绑定——我当然假设我们正在谈论某种单元测试。测试项目使用自己的内核并将测试项目中的模块加载到该内核中。项目中的测试在 CI 构建期间执行,并通过从构建脚本执行的完整构建执行,尽管测试从未部署到生产中。
我意识到您的项目/解决方案设置可能不允许这种组织,但从我所看到的情况来看,它似乎很典型。
Peter Mayer 的方法对单元测试很有用,但是恕我直言,使用构造函数/属性注入手动注入 Mock 不是更容易吗?
在我看来,对测试项目使用特定绑定对其他类型的测试(集成、功能)更有用,但即使在这种情况下,您也肯定需要根据测试更改绑定。
我的方法是 kronhrbaugh 和 Hamish Smith 的某种混合,创建一个“依赖解析器”,您可以在其中注册和取消注册要使用的模块。
对于我正在进行的项目,我为每个环境(测试、开发、阶段、生产等)创建了单独的模块。这些模块定义了绑定。
因为 dev、stage 和 production 都使用许多相同的绑定,所以我创建了一个它们都派生自的通用模块。然后每个都添加环境特定的绑定。
我也有一个 KernelFactory,当传递一个环境令牌时,它会使用适当的模块来处理一个 IKernel。
这允许我切换我的环境令牌,这反过来会自动更改我的所有绑定。
但如果这是用于单元测试,我同意上面的评论,即允许手动绑定的简单构造函数是可行的方法,因为它使 Ninject 远离您的测试。
我会向 MyClass 添加一个接受模块的构造函数。
这不会在生产中使用,但会在测试中使用。
在测试代码中,我将传递一个定义所需测试替身的模块。