【问题标题】:How to use AutoData in unit tests to supply N objects of a type in test arguments?如何在单元测试中使用 AutoData 在测试参数中提供 N 个类型的对象?
【发布时间】:2014-01-17 21:06:19
【问题描述】:

我在 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 正是我需要的数字?

【问题讨论】:

  • 您有一个带有 非常具体 个参数的 SUT 方法,但您希望您的测试方法为您提供 一般数量 个实例.这说明了 SUT 的 API 什么?
  • 好吧,我实际上希望我的测试方法也为我提供非常具体数量的参数。通常,我可能会回答您的问题,说我的 SUT 的 API 应该更改为具有类型为 CallingBird[] 的单个属性,但这是在建模父对象恰好具有相同类型的 4 个子属性的东西,每个子属性具有不同的值.我不知道如何设计它并满足正好有 4 个 CallingBirds。
  • 因此,您拥有需要填充的 Receive 方法。它有一个非常特殊的签名。现在您想使用[AutoData] 为您提供它的值。这意味着您也必须为测试方法提出适当的签名。您如何对这样的方法签名进行建模以适应您想要做的事情?
  • 啊。只需请求 4 个CallingBird 实例,每个实例作为它们自己的测试方法参数,加上Recipient SUT。这是你在暗示的吗?完全有道理。我想我在上面的示例中稍微提炼了一些情况,但我想象需要更多数量的实例(10+),以及在测试方法中可能会变得多么笨拙。但是,我想这说明了 SUT 的方法签名中的一个潜在问题,如果没有,那么我应该能够在我的测试方法中容忍与我在我的 SUT 方法中相同的签名长度,对吧?跨度>
  • 这正是我的意思 :) 很抱歉含糊其辞,但如果我能让您自己找到解决方案,我真的很高兴 :) 现在寻找替代方案(即将推出)!

标签: c# arrays unit-testing autofixture


【解决方案1】:

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】:

到目前为止,这是基于 Mark Seemann 的 cmets 提出的答案。如果这不是他所暗示的,我会酌情修改...

看来我可能有点想多了。如果我的 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 中一样在测试方法中请求数组以节省空间,但这可能是以显示明确意图为代价的。

【讨论】:

  • “我应该能够在我的测试方法中容忍至少与我在我的 SUT 方法中相同数量的参数。”是的;这个!
【解决方案3】:

如果您只想将内容输入到函数中而您不在乎它是什么,请使用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 个参数,显然最好的一般建议是听您的测试。但是,如果我的测试和我同意不同意,我可能会编写一个本地程序集(当心共享)[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 @>

[<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 @>

【讨论】:

  • 这不能为我编译 (fixture.Do( sut.Receive);) - 你能帮忙解释一下语法吗?之前没用过Do或者Get,看起来挺有意思的。
  • 啊哈!我想通了:fixture.Do&lt;CallingBird, CallingBird, CallingBird, CallingBird&gt;(sut.Receive); 看起来当前的实现最多允许指定 4 种类型,所以这不适用于更多的参数,是吗?
  • @Lumirris 抱歉,对 C# 的限制感到生疏,因为 F# 大步向前(它通过大约 fixture.Do&lt;Tuple&lt;CallingBird, CallingBird, CallingBird, CallingBird&gt;&gt;( x =&gt; sut.Receive( x.Item1, x.Item2, x.Item3, x.Item4 ) ) 向 AF 询问 Tuple`4。您的消歧建议是最简洁的方法C#(无论如何都是 v5 :D)。顺便说一句,F# 方法也可以干净地处理 >4 args。
  • Eric Lippert 的哪个系列?还是只是对他作品的一般指针?
  • @Lumirris 我的记忆力和/或 google-fu 无法回答您的问题。我想我记得一个 5-ish 部分系列,其中介绍了 C# 中类型推断的限制。我看到的只是this category。我现在不确定它实际上是C# in Depth 还是Real World Functional Programming in F# and C#。底线是,由于很多原因,与 F# 相比,C# 中的类型推断不值得这个术语(显然还有其他诸如 Haskell 等)
【解决方案4】:

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

[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());
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-02
    • 1970-01-01
    • 2010-10-27
    • 2011-02-10
    • 2013-12-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多