如何在单元测试中使用AutoData在测试参数中提供类型的N个对象?

时间:2014-01-17 21:06:19

标签: c# arrays unit-testing autofixture

我在xUnit单元测试中使用AutoData。我偶尔需要为我的测试提供特定数量的对象。考虑以下课程:

public class Recipient
{
    public void Receive(
        CallingBird bird1,
        CallingBird bird2,
        CallingBird bird3, 
        CallingBird bird4
        )
    {
        this.Bird1 = bird1;
        this.Bird2 = bird2;
        this.Bird3 = bird3;
        this.Bird4 = bird4;
    }

    public CallingBird Bird1 { get; private set; }
    public CallingBird Bird2 { get; private set; }
    public CallingBird Bird3 { get; private set; }
    public CallingBird Bird4 { get; private set; }
}

如果没有AutoData,我可能会写一个这样的测试:

[Fact]
public void All_Birds_Are_Populated()
{
    var bird1 = new CallingBird();
    var bird2 = new CallingBird();
    var bird3 = new CallingBird();
    var bird4 = new CallingBird();
    var sut = new Recipient();

    sut.Receive(bird1, bird2, bird3, bird4);

    Assert.NotNull(sut.Bird1);
    Assert.NotNull(sut.Bird2);
    Assert.NotNull(sut.Bird3);
    Assert.NotNull(sut.Bird4);
}

在这种情况下使用AutoData,我一直在寻找一个我需要的对象数组数组,以便获得足够多的不同实例(假设我需要不同的实例),如下所示:

[Theory, Autodata]
public void All_Birds_Are_Populated(CallingBird[][] birds, Recipient sut)
{
        sut.Receive(birds[0][0], birds[0][1], birds[0][2] ,birds[1][0]);

        Assert.NotNull(sut.Bird1);
        Assert.NotNull(sut.Bird2);
        Assert.NotNull(sut.Bird3);
        Assert.NotNull(sut.Bird4);
    }
}

当您从AutoData请求数组时,它会为您提供其中3个对象的数组。所以,如果我需要4个东西,我可以要求2个数组,或者一个数组数组(如图所示),在这个例子中比要求两个数组更浪费。它有效,但我经常要求提供比我需要的更多的实例。想象一下,计数越高,创建对象越昂贵等情况。

您能否建议一种更简洁的方式来请求某种类型的 N 对象作为单元测试参数,其中 N 正是我需要的数字?

4 个答案:

答案 0 :(得分:5)

以下是基于Mark Seemann评论的建议答案。如果不是他暗示的话,我会酌情对此进行修改......

似乎我可能已经过度思考了一些事情。如果我的SUT方法需要4 CallingBird个实例,那么我可以简单地在单元测试签名中的单独参数中询问这些实例,如下所示:

[Theory, Autodata]
public void All_Birds_Are_Populated(
    CallingBird bird1,
    CallingBird bird2,
    CallingBird bird3,
    CallingBird bird4,
    Recipient sut)
{
    sut.Receive(bird1, bird2, bird3, bird4);

    Assert.NotNull(sut.Bird1);
    Assert.NotNull(sut.Bird2);
    Assert.NotNull(sut.Bird3);
    Assert.NotNull(sut.Bird4);
}

如果参数列表太长,那么它可能是在我的SUT方法签名中识别代码气味。如果它不是代码味道,那么我应该能够容忍测试方法中至少与我在SUT方法中相同数量的参数。

我想我可以在测试方法中请求数组,就像在OP中节省空间一样,但这可能是以显示明确意图为代价的。

答案 1 :(得分:5)

Lumirris'own answer是最好的答案,因为它解释了通过编写单元测试提供的学习和反馈机会

但是,我想提供一个替代方案,仅为了完整性,但我不认为这应该是接受的答案

使用AutoFixture,您可以要求Generator<T>,这是一个通过提供无限(延迟评估)元素序列来实现IEnumerable<T>的类。它使您能够获取有限的,已知数量的元素:

[Theory, Autodata]
public void All_Birds_Are_Populated(
    Generator<CallingBird> g,
    Recipient sut)
{
    var birds = g.Take(4).ToList();

    sut.Receive(birds[0], birds[1], birds[2], birds[3]);

    Assert.NotNull(sut.Bird1);
    Assert.NotNull(sut.Bird2);
    Assert.NotNull(sut.Bird3);
    Assert.NotNull(sut.Bird4);
}

答案 2 :(得分:4)

如果您只是想要将某些内容输入某个功能而您不关心它是什么,请使用Do(或Get): -

[Theory, AutoData]
public void All_Birds_Are_Populated( Recipient sut, IFixture fixture)
{
    // C# requires lots of disambiguation. Go read Eric Lippert/Jon Skeet/Tomas Petricek :)
    fixture.Do<CallingBird,CallingBird,CallingBird,CallingBird>( sut.Receive );

    Assert.NotNull( sut.Bird1);
    Assert.NotNull( sut.Bird2);
    Assert.NotNull( sut.Bird3);
    Assert.NotNull( sut.Bird4);
}

编辑:如果你需要5个参数,显然最好的一般建议是听你的测试。但是,如果我的测试和我同意不同意,我可能会编写一个本地的汇编(Beware The Share)[http://www.amazon.com/Things-Every-Software-Architect-Should/dp/059652269X]扩展方法Do,它可以合成5个参数。

I personally wouldn't run into either of these problems (having to over-specify types, not having tuples first class) with my toolchain - 我会说:

[<Theory;AutoData>]
let ``All birds are populated`` (sut:Recipient) (fixture:IFixture) =
    sut.Receive |> fixture.Do

    test <@ sut.Bird1 <> null && sut.Bird2 <> null && sut.Bird3 <> null && sut.Bird4 <> null @>

OR

[<Theory;AutoData>]
let ``All birds are populated`` (sut:Recipient) args =
    sut.Receive args

    test <@ sut.Bird1 <> null && sut.Bird2 <> null && sut.Bird3 <> null && sut.Bird4 <> null @>

答案 3 :(得分:0)

在搜索如何使用 AutoData 设置项目数组而不是在测试方法中使用多个单独的参数后,我在这里找到了自己。按照上面马克西曼的回答,我改变了这种方法......

[Theory, AutoData]
public void Test_Using_Separate_Parameters(SomeObject object1, SomeObject object2, SomeObject object3)
{
    var objects = new[] {object1, object2, object3};

    _mockProvider
        .Setup(x => x.ReturnSomeArray())
        .Returns(objects);
}

改为...

[Theory, AutoData]
public void Test_Using_Generator(Generator<SomeObject> objectGenerator)
{
    _mockProvider
        .Setup(x => x.ReturnSomeArray())
        .Returns(objectGenerator.Take(3).ToArray());
}