xUnit和测试的多个数据记录

xUnit and multiple data records for a test

我是单元测试的新手,有以下代码:

public class PowerOf
{
    public int CalcPowerOf(int @base, int exponent) {
        if (@base == 0) { return 0; }
        if (exponent == 0) { return 1; }
        return @base * CalcPowerOf(@base, exponent - 1);
    }
}

我首先为它编写的单元测试(使用 xUnit)是这个,但我不太确定它是否是正确的方法,或者我是否应该使用其他模式? 我想知道这是否是将多组数据传递到“单元测试”的正确用法 - 因为我没有在 xUnit 的文档上看到任何文档或参考示例?

    [Fact]
    public void PowerOfTest() {
        foreach(var td in PowerOfTestData()) {
           Assert.Equal(expected, CalcPowerOf(@base, exponent));
        }
    }

    public class TestData {
      int Base {get;set;}
      int Exponent {get;set;}
      int ExpectedResult {get;set;}
    }

    public List<TestData> PowerOfTestData() {
        yield return new TestData { Base = 0, Exponent = 0, TestData = 0 };
        yield return new TestData { Base = 0, Exponent = 1, TestData = 0 };
        yield return new TestData { Base = 2, Exponent = 0, TestData = 1 };
        yield return new TestData { Base = 2, Exponent = 1, TestData = 2 };
        yield return new TestData { Base = 5, Exponent = 2, TestData = 25 };
    }

最好在 xUnit 中使用一个专门的构造,称为 Theory,它处理所谓的 "Data Driven Tests"。 用 Theory 属性装饰你的测试方法,然后确保 return a static "member" 输入参数和预期结果,就像你已经对 TestData class。请参阅下面的示例,并参考 xUnit 文档:"Writing your first theory".

因此,我会像下面这样重构您的代码。首先用属性 TheoryMemberData 装饰测试,并向测试“@base”、"exponent" 和 "expectedResult" 添加参数——正如你在 [=16= 中所做的那样] class。 xUnit 不允许您使用 TestData class,它只接受一个 IEnumerable<object> 并要求它是静态的,但是 foreach 循环构造的好处是所有测试都是运行 分开。 对于每个具有特定数据集的运行,您将获得绿色或红色标志!

public class PowerOfTests
{
    [Theory]
    [MemberData(nameof(PowerOfTestData))]
    public void PowerOfTest(int @base, int exponent, int expected) {
        Assert.Equal(expected, CalcPowerOf(@base, exponent));
    }

    public static IEnumerable<object[]> PowerOfTestData() {
        yield return new object[] { 0, 0, 0 };
        yield return new object[] { 0, 1, 0 };
        yield return new object[] { 2, 0, 1 };
        yield return new object[] { 2, 1, 2 };
        yield return new object[] { 2, 2, 4 };
        yield return new object[] { 5, 2, 25 };
        yield return new object[] { 5, 3, 125 };
        yield return new object[] { 5, 4, 625 };
    }
}

您正在使用 class 成员来定义您的数据,这在您的情况下是错误的。当在 运行 时间指定值时(可能循环从 1 到 MAX 的值),您可以使用这种方法,这不是您的情况(您有硬编码数据)。我认为这种方法更好:

[Theory]
[InlineData(0, 0, 0)]
[InlineData(0, 1, 0)]
[InlineData(2, 0, 1)]
[InlineData(2, 1, 2)]
[InlineData(2, 2, 4)]
[InlineData(5, 2, 25)]
[InlineData(5, 3, 125)]
[InlineData(5, 4, 625)]
public void PowerOfTest(int @base, int exponent, int expected) 
{
   var result = CalcPowerOf(@base,exponent);
   Assert.Equal(expected, result);
}

通过这种方式,您可以在大型 class.

中进行更具可读性的测试

对于不使用object[]的测试方法的强类型参数列表,您也可以使用TheoryData。它为最多 10 个参数定义了几个通用重载。由于您的方法有 3 个整数输入值,@baseexponentexpected,您可以使用 TheoryData<int, int, int> 类型的 属性。然后,使用 TheoryMemberData(nameof(PropertyName) 属性注释您的 PowerOfTest 方法:

class PowerOfTests
{
    TheoryData<int, int, int> PowerOfTestData => new TheoryData<int, int, int>
    {
       { 0, 0, 0 },
       { 0, 1, 0 },
       { 2, 0, 1 },
       { 2, 1, 2 },
       { 5, 2, 25 }
    };
     
    [Theory]
    [MemberData(nameof(PowerOfTestData)] 
    public void PowerOfTest(int @base, int exponent, int expected) 
    {
        Assert.Equal(expected, CalcPowerOf(@base, exponent));
    }
}

我可以用以下方式初始化 TheoryData<int, int, int> 的原因:

{ 
    { param1, param2, param3 }, 
    ... 
}

syntax(called a collection initializer) 是因为它实现了 IEnumerable 并定义了一个 Add<int, int, int>(int, int, int) 方法,该方法接受三个整数参数( <int, int, int> 泛型重载 TheoryData).

这也使得通过继承 TheoryData:

将测试数据放在单独的 class 中成为可能
class PowerOfTestDataClass : TheoryData<int, int, int>
{
    public PowerOfTestDataClass()
    {
       Add(0, 0, 0);
       Add(0, 1, 0);
       Add(2, 0, 1);
       Add(2, 1, 2);
       Add(5, 2, 25);
    }
}

现在用 ClassData 属性及其参数注释 PowerOfTest() 方法而不是 MemberData,作为 PowerOfTestDataClass 的类型:

[Theory]
[ClassData(typeof(PowerOfTestDataClass)] 
public void PowerOfTest(int @base, int exponent, int expected) 
{
    Assert.Equal(expected, CalcPowerOf(@base, exponent));
}

拥有强类型参数列表的优点是您始终可以确保参数具有正确的类型和正确的长度。虽然 IEnumerable<object[]> 中的对象数组也可以工作,但它将允许任何类型和任何长度。

参考:https://andrewlock.net/creating-strongly-typed-xunit-theory-test-data-with-theorydata/