2017-08-04 79 views
2

所以我有以下的类,它需要在其构造函数中,相同的接口依赖的三种不同的实现方式:自定义复杂的依赖关系的类型的创建与AutoFixture

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来做到这一点,这引起了我的兴趣我的问题,我怎么创建MyTestClassIMyTestInterface,这三个具体实现的实例,即ImplementationAImplementationB,并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

它看起来像你可以从'ISpecimenBuilder'继承,并使用AutoFixture创建每个特定的接口实现:[见这个答案](https://stackoverflow.com/questions/26149618/autofixture-customizations-provide-constructor-参数)和[这个答案](https://stackoverflow.com/questions/16819470/autofixture-automoq-supply-a-known-value-for-one-constructor-parameter/16954699#16954699) –

回答

2

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处理这个问题。

然而,三个对象是进一步反思的理由。在软件设计中,我的经验是,当涉及到相同参数的基数时,只有四个有用集合:无,一个,两个许多

  • 没有参数基本上是一个陈述或公理。
  • 一个参数表示一个一元操作。
  • 两个参数表示二进制操作。
  • 超过两个参数基本上只是表示多个。在数学和软件工程中,几乎没有三元操作。

因此,一旦你通过两个参数,问题是三个参数是否不推广到任何数量的参数?如果是这样的话,一个设计可能不是这个样子:

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更容易测试的优点。

+0

好点。我在那里做的是策略设计模式的实现,IMyTestInterface的三个实现是根据某些条件调用的不同规则。我最终选择的是混凝土依赖思想的一个版本。这是不是违背了依赖倒置原则? – lukegf

+0

@lukegf它可能,但我没有所有的细节。你能重构一个责任链吗? –

+0

可能。我不太了解责任链。只需阅读它,它看起来可能,但战略模式更适合我所要做的。 – lukegf

1

FWIW,而我相信my first answer是最好的答案,这里是解决与AutoFixture这个问题的简单方法,如果由于某种原因,你不能改变的设计:

fixture.Register(() => 
    new MyTestClass(new ImpA(), new ImpB(), new ImpC())); 

还有其他选项也是如此,但我相信没有一个像这个那么简单。

+0

我已经重新设计了我的设计以使用混凝土依赖关系。另外,我已经想出了一种让AutoFixture与我的原创设计一起工作的方法,虽然上面的建议确实更简单。 – lukegf