使用 AutoFixture 自定义创建具有复杂依赖性的类型
Customizing the creation of a type with complex dependencies with AutoFixture
所以我有以下 class,它在其构造函数中采用同一接口的三个不同实现作为依赖项:
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)
{
}
}
在单元测试之外使用此 class 时,我使用 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");
}
}
效果很好,但我现在遇到的问题是我想对这个 class 进行单元测试,我想用 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
AutoFixture 最初是作为测试驱动开发 (TDD) 的工具构建的,而 TDD 就是关于 反馈 的。本着 GOOS 的精神,您应该 听取您的测试 。如果测试很难写,你应该重新考虑你的 API 设计。 AutoFixture 倾向于放大这种反馈,所以我的第一反应是建议重新考虑设计。
具体依赖关系
听起来您需要依赖项是接口的特定实现。如果是这样的话,那么当前的设计方向是错误的,并且也违反了 Liskov Substitution Principle (LSP). You could, instead, consider refactoring to 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、一、二和许多.
- 没有参数基本上是一个陈述或公理。
- 一个参数表示一元运算。
- 两个参数表示二元运算。
- 两个以上的论点本质上只是表示众多。在数学和软件工程中,很少有三元运算。
因此,一旦你通过了两个参数,问题是三个参数是否不能推广到任意数量的参数?如果是这种情况,设计可能如下所示:
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 进行测试更容易的衍生好处。
FWIW,虽然我认为 是最佳答案,但这里有一个简单的方法来解决 AutoFixture 的问题,以防由于某种原因您无法更改设计:
fixture.Register(() =>
new MyTestClass(new ImpA(), new ImpB(), new ImpC()));
还有其他选择,但我相信 none 就像这个一样简单。
所以我有以下 class,它在其构造函数中采用同一接口的三个不同实现作为依赖项:
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)
{
}
}
在单元测试之外使用此 class 时,我使用 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");
}
}
效果很好,但我现在遇到的问题是我想对这个 class 进行单元测试,我想用 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
AutoFixture 最初是作为测试驱动开发 (TDD) 的工具构建的,而 TDD 就是关于 反馈 的。本着 GOOS 的精神,您应该 听取您的测试 。如果测试很难写,你应该重新考虑你的 API 设计。 AutoFixture 倾向于放大这种反馈,所以我的第一反应是建议重新考虑设计。
具体依赖关系
听起来您需要依赖项是接口的特定实现。如果是这样的话,那么当前的设计方向是错误的,并且也违反了 Liskov Substitution Principle (LSP). You could, instead, consider refactoring to 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、一、二和许多.
- 没有参数基本上是一个陈述或公理。
- 一个参数表示一元运算。
- 两个参数表示二元运算。
- 两个以上的论点本质上只是表示众多。在数学和软件工程中,很少有三元运算。
因此,一旦你通过了两个参数,问题是三个参数是否不能推广到任意数量的参数?如果是这种情况,设计可能如下所示:
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 进行测试更容易的衍生好处。
FWIW,虽然我认为
fixture.Register(() =>
new MyTestClass(new ImpA(), new ImpB(), new ImpC()));
还有其他选择,但我相信 none 就像这个一样简单。