使用AutoFixture

时间:2017-08-04 17:17:48

标签: c# unit-testing 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来做,这引出了我的问题,如何创建 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

2 个答案:

答案 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()));

还有其他选择,但没有一个像这个一样简单,我相信。