我在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 正是我需要的数字?
答案 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个参数。
[<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());
}