在xUnit.net中测试与NUnit类似的参数化


106

xUnit.net框架中是否有类似于NUnit的以下功能的方法?

[Test, TestCaseSource("CurrencySamples")]
public void Format_Currency(decimal value, string expected){}

static object[][] CurrencySamples = new object[][]
{
    new object[]{ 0m, "0,00"},
    new object[]{ 0.0004m, "0,00"},
    new object[]{ 5m, "5,00"},
    new object[]{ 5.1m, "5,10"},
    new object[]{ 5.12m, "5,12"},
    new object[]{ 5.1234m, "5,12"},
    new object[]{ 5.1250m, "5,13"}, // round
    new object[]{ 5.1299m, "5,13"}, // round
}

这将在NUnit GUI中生成8个单独的测试

[TestCase((string)null, Result = "1")]
[TestCase("", Result = "1")]
[TestCase(" ", Result = "1")]
[TestCase("1", Result = "2")]
[TestCase(" 1 ", Result = "2")]
public string IncrementDocNumber(string lastNum) { return "some"; }

这将生成5个单独的测试,并自动比较结果(Assert.Equal())。

[Test]
public void StateTest(
    [Values(1, 10)]
    int input,
    [Values(State.Initial, State.Rejected, State.Stopped)]
    DocumentType docType
){}

这将生成6个组合测试。无价。

几年前,我尝试并喜欢xUnit,但它缺少这些功能。没有他们就无法生存。有什么改变吗?


完整的指南,将复杂对象作为参数发送给单元测试中的
Iman Bahrampour

Answers:


138

xUnit提供了一种通过称为数据理论的东西来运行参数化测试的方法。这个概念与NUnit中的概念相同,但是开箱即用的功能并不完整。

这是一个例子:

[Theory]
[InlineData("Foo")]
[InlineData(9)]
[InlineData(true)]
public void Should_be_assigned_different_values(object value)
{
    Assert.NotNull(value);
}

在此示例中,xUnit 每次将指定值作为参数传递时,都会运行Should_format_the_currency_value_correctly一次测试InlineDataAttribute

数据理论是可扩展点,可用于创建运行参数化测试的新方法。完成此操作的方法是创建新属性,这些可以检查测试方法的自变量和返回值,并可以选择对它们进行操作。

您可以在AutoFixtureAutoDataInlineAutoData理论中找到有关如何扩展xUnit数据理论的很好的实际示例。


3
显然,不允许使用十进制文字作为属性参数。
Sergii Volchkov

1
@RubenBartelink找不到您的链接。请改用此处:blog.benhall.me.uk/2008/01/introduction-to-xunit-net-extensions
Ronnie Overby

9
您将需要xUnit.net:扩展(NuGet软件包),否则该[Theory]属性将不可用。
Daniel AA Pelsmaeker 2014年

4
这将是巨大的,如果最推荐的.NET单元测试框架有一些文件..
艾萨克·克雷曼

6
Google说您的答案是xUnit文档。
nathanchere 2014年

55

让我在这里再扔一个样本,以防万一可以节省一些时间。

[Theory]
[InlineData("goodnight moon", "moon", true)]
[InlineData("hello world", "hi", false)]
public void Contains(string input, string sub, bool expected)
{
    var actual = input.Contains(sub);
    Assert.Equal(expected, actual);
}

您是否忘了第二行的右括号?
cs0815 2015年

有用的,谢谢:)
Zeek2

21

根据您的第一个请求,您可以按照此处的示例进行操作。

您可以构造一个静态类,其中包含测试集合所需的数据

using System.Collections.Generic;

namespace PropertyDataDrivenTests
{
    public static class DemoPropertyDataSource
    {
        private static readonly List<object[]> _data = new List<object[]>
            {
                new object[] {1, true},
                new object[] {2, false},
                new object[] {-1, false},
                new object[] {0, false}
            };

        public static IEnumerable<object[]> TestData
        {
            get { return _data; }
        }
    }
}

然后,使用MemberData属性将测试定义为

public class TestFile1
{
    [Theory]
    [MemberData("TestData", MemberType = typeof(DemoPropertyDataSource))]
    public void SampleTest1(int number, bool expectedResult)
    {
        var sut = new CheckThisNumber(1);
        var result = sut.CheckIfEqual(number);
        Assert.Equal(result, expectedResult);
    }
}

或者,如果您使用的是C#6.0,

[Theory]
[MemberData(nameof(PropertyDataDrivenTests.TestData), MemberType = typeof(DemoPropertyDataSource))]

MemberDataAttribute的第一个参数允许您定义用作数据源的成员,因此在重用方面具有相当大的灵活性。


13

根据xUnit中的本文,您可以使用三个“参数化”选项:

  1. 内联数据
  2. 类数据
  3. 会员资料

InlineData示例

[Theory]
[InlineData(1, 2)]
[InlineData(-4, -6)]
[InlineData(2, 4)]
public void FooTest(int value1, int value2)
{
    Assert.True(value1 + value2 < 7)
}

ClassData示例

public class BarTestData : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] { 1, 2 };
        yield return new object[] { -4, -6 };
        yield return new object[] { 2, 4 };
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}


[Theory]
[ClassData(typeof(BarTestData))]
public void BarTest(int value1, int value2)
{
    Assert.True(value1 + value2 < 7)
}

MemberData示例

[Theory]
[MemberData(nameof(BazTestData))]
public void BazTest(int value1, int value2)
{
    Assert.True(value1 + value2 < 7)
}

public static IEnumerable<object[]> BazTestData => new List<object[]>
    {
        new object[] { 1, 2 },
        new object[] { -4, -6 },
        new object[] { 2, 4 },
    };

12

我找到了一个库,该库产生与NUnit [Values]属性相同的功能,称为Xunit.Combinatorial

它允许您指定参数级别的值:

[Theory, CombinatorialData]
public void CheckValidAge([CombinatorialValues(5, 18, 21, 25)] int age, 
    bool friendlyOfficer)
{
    // This will run with all combinations:
    // 5  true
    // 18 true
    // 21 true
    // 25 true
    // 5  false
    // 18 false
    // 21 false
    // 25 false
}

或者,您可以隐式地计算出涵盖所有可能组合的最少调用次数:

[Theory, PairwiseData]
public void CheckValidAge(bool p1, bool p2, bool p3)
{
    // Pairwise generates these 4 test cases:
    // false false false
    // false true  true
    // true  false true
    // true  true  false
}

6

我接受了这里的所有答案,并另外利用了XUnit的TheoryData<,>泛型类型,使我在测试中为“ MemberData”属性提供简单,易于阅读的类型安全的数据定义,如下例所示:

/// must be public & static for MemberDataAttr to use
public static TheoryData<int, bool, string> DataForTest1 = new TheoryData<int, bool, string> {
    { 1, true, "First" },
    { 2, false, "Second" },
    { 3, true, "Third" }
};

[Theory(DisplayName = "My First Test"), MemberData(nameof(DataForTest1))]
public void Test1(int valA, bool valB, string valC)
{
    Debug.WriteLine($"Running {nameof(Test1)} with values: {valA}, {valB} & {valC} ");
}

从测试浏览器观察到的三个测试运行“我的第一个测试”


NB使用VS2017(15.3.3),C#7和XUnit 2.2.0 for .NET Core


好可爱
布雷特·罗伯里
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.