所以我有以下类,它在构造函数中有三个与依赖项相同的接口的不同实现:
public class MyTestClass : ISomeInterface<string>
{
// Constructor
public MyTestClass([Named("ImplementationA")] IMyTestInterface implementationA,
[Named("ImplementationB")] IMyTestInterface implementationB,
[Named("ImplementationC")] IMyTestInterface implementationC)
{
// Some logic here
}
public void MethodA(string)
{
}
}
在单元测试之外使用这个类时,我用Ninject注入了相关的依赖项,即我有这样的东西:
public class MyNinjectModule : NinjectModule
{
public override void Load()
{
this.Bind<IMyTestInterface>().To<ImplementationA>().InRequestScope().Named("ImplementationA");
this.Bind<IMyTestInterface>().To<ImplementationB>().InRequestScope().Named("ImplementationB");
this.Bind<IMyTestInterface>().To<ImplementationC>().InRequestScope().Named("ImplementationC");
}
}
哪个工作正常,但我现在遇到的问题是我想要对这个类进行单元测试,我想用AutoFixture来做,这引出了我的问题,如何创建 MyTestClass的实例 IMyTestInterface 的三个具体实现,即 ImplementationA , ImplementationB 和 ImplementationC ?如果我只是做这样的事情:
private ISomeInterface<string> testInstance;
private Fixture fixture;
[TestInitialize]
public void SetUp()
{
this.fixture = new Fixture();
this.fixture.Customize(new AutoMoqCustomization());
this.testInstance = this.fixture.Create<MyTestClass>();
}
AutoFixture会创建一个 MyTestClass 的实例,但是有一些 IMyTestInterface 的随机实现,这不是我想要的依赖项。我一直在网上寻找答案,到目前为止我发现的唯一一件与我需要的东西相似的东西,但不完全是this
答案 0 :(得分:1)
AutoFixture最初是作为测试驱动开发(TDD)的工具而构建的,TDD完全是关于反馈。本着GOOS的精神,你应该倾听你的测试。如果测试难以编写,则应重新考虑API设计。 AutoFixture倾向于放大这种反馈,所以我的第一反应是建议重新考虑设计。
听起来您需要依赖项作为接口的特定实现。如果是这种情况,那么当前的设计正在向错误的方向拉,并且还违反了Liskov Substitution Principle(LSP)。相反,您可以考虑重构为Concrete Dependencies:
public class MyTestClass : ISomeInterface<string>
{
// Constructor
public MyTestClass(ImplementationA implementationA,
ImplementationB implementationB,
ImplementationC implementationC)
{
// Some logic here
}
public void MethodA(string)
{
}
}
这清楚地表明MyTestClass
依赖于这三个具体的类,而不是模糊真正的依赖。
它还具有将设计与特定DI容器分离的附加好处。
另一个选择是遵守LSP,并允许 IMyTestInterface
的任何实现。如果您这样做,则不再需要[Named]
属性:
public class MyTestClass : ISomeInterface<string>
{
// Constructor
public MyTestClass(IMyTestInterface implementationA,
IMyTestInterface implementationB,
IMyTestInterface implementationC)
{
// Some logic here
}
public void MethodA(string)
{
}
}
这样的设计可能会产生一个问题:如何区分每个依赖项?我的大部分Role Hints article series都会处理这个问题。
然而,有三个对象是进一步反思的理由。在软件设计中,我的经验是,当涉及相同参数的基数时,只有四个有用的集合: none,one,two 和 many 。因此,一旦你过去两个论点,问题在于三个论点是否不能推广到任何数量的论点?如果是这种情况,设计可能会是这样的:
public class MyTestClass : ISomeInterface<string>
{
// Constructor
public MyTestClass(IEnumerable<IMyTestInterface> deps)
{
// Some logic here
}
public void MethodA(string)
{
}
}
或者,如果您可以从IMyTestInterface
创建Composite,您甚至可以将其缩小为以下内容:
public class MyTestClass : ISomeInterface<string>
{
// Constructor
public MyTestClass(IMyTestInterface dep)
{
// Some logic here
}
public void MethodA(string)
{
}
}
所有这些选项都使设计更加清晰,并且还应具有使用AutoFixture更容易测试的衍生优势。
答案 1 :(得分:1)
FWIW,虽然我认为my first answer是最好的答案,但这是解决AutoFixture问题的简单方法,如果由于某种原因您无法更改设计:
fixture.Register(() =>
new MyTestClass(new ImpA(), new ImpB(), new ImpC()));
还有其他选择,但没有一个像这个一样简单,我相信。