简体   繁体   English

用于测试的 xUnit 和多个数据记录

[英]xUnit and multiple data records for a test

I'm fairly new to Unit Testing and have the following code:我对单元测试相当陌生,并且有以下代码:

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

The unit test (with xUnit) I wrote for it first was this one, but I'm not quite sure if it's the right approach, or if I should use another pattern?我首先为它编写的单元测试(使用 xUnit)就是这个,但我不太确定这是否是正确的方法,或者我是否应该使用另一种模式? What I wanted to know is whether this is the correct usage for passing multiple sets of data into a "unit test" - as I didn't see any docs or reference examples on xUnit's docs?我想知道的是,这是否是将多组数据传递到“单元测试”的正确用法——因为我在 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 };
    }

You'd be better of using a specialised construct in xUnit, called a Theory , that handles so called "Data Driven Tests".您最好在 xUnit 中使用一个专门的构造,称为Theory ,它处理所谓的“数据驱动测试”。 Decorate your testmethod with the Theory attribute and then make sure to return a static "member" with input parameters and the expected result as you already kind of did with the TestData class.Theory属性装饰你的测试方法,然后确保返回一个带有输入参数和预期结果的static “成员”,就像你在 TestData 类中所做的那样。 See the example below, and ref to the xUnit documentation: "Writing your first theory" .请参见下面的示例,并参考 xUnit 文档: “Writing your first theory”

I would thus refactor your code like below.因此,我会像下面那样重构您的代码。 Firstly decorating the test with the attributes Theory and MemberData and adding parameters to your test "@base", "exponent" and "expectedResult" - as you had in your TestData class.首先用属性TheoryMemberData装饰测试,并在测试中添加参数“@base”、“exponent”和“expectedResult”——就像在TestData类中一样。 xUnit won't allow you to use the TestData class, it only accepts an IEnumerable<object> and requires it to be static, but the benefit to a foreach loop construct is that all the tests are ran seperately. xUnit不允许您使用 TestData 类,它只接受一个IEnumerable<object>并要求它是静态的,但 foreach 循环构造的好处是所有测试都单独运行。 And for each run with a specific data set you'll get a green or red flag!对于使用特定数据集的每次运行,您将得到一个绿色或红色标志!

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 };
    }
}

You're using a class member to define your data which is wrong in your case.您正在使用类成员来定义在您的情况下是错误的数据。 You use this approach when the values are specified at run-time (maybe looping through values from 1 to MAX) which is not your case (You have hard-coded data).当在运行时指定值(可能循环从 1 到 MAX 的值)时,您使用这种方法,这不是您的情况(您有硬编码数据)。 I think this approach is better:我认为这种方法更好:

[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, exponent);
   Assert.Equal(expected, result);
}

This way you have a more readable test in a large class.通过这种方式,您可以在大班中获得更具可读性的测试。

For a strongly-typed parameter list to the test method without using object[] , you can also use TheoryData .对于不使用object[]的测试方法的强类型参数列表,您还可以使用TheoryData It defines several generic overloads for up to 10 parameters.它为最多 10 个参数定义了几个通用重载。 Since your method has 3 integer input values, @base , exponent and expected , you can use a property of type TheoryData<int, int, int> .由于您的方法有 3 个整数输入值@baseexponentexpected ,因此您可以使用TheoryData<int, int, int>类型的属性。 Then, annotate your PowerOfTest class with the Theory and MemberData(nameof(PropertyName) attribute:然后,使用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));
    }
}

The reason I can initialize TheoryData<int, int, int> with the:我可以初始化TheoryData<int, int, int>的原因是:

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

syntax(called a collection initializer ) is because it implements IEnumerable and defines an Add<int, int, int>(int, int, int) method that takes in three integer parameters( the <int, int, int> generic overload of TheoryData ).语法(称为集合初始值设定项)是因为它实现了IEnumerable并定义了一个Add<int, int, int>(int, int, int)方法,该方法接受三个整数参数( TheoryData<int, int, int>泛型重载)。

This also makes it possible to have the test data in a separate class by inheriting from TheoryData :这也使得通过继承TheoryData将测试数据放在一个单独的类中成为TheoryData

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

Now instead of MemberData , annotate the PowerOfTest() method with the ClassData attribute and its parameter as PowerOfTestDataClass 's type:现在,而不是MemberData ,用ClassData属性和它的参数为PowerOfTestDataClass的类型注释PowerOfTest()方法:

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

The advantage of having a strongly-typed typed parameter list is you can always ensure that the arguments will have the right type and the right length.拥有强类型类型参数列表的优点是您始终可以确保参数具有正确的类型和正确的长度。 While the object array in IEnumerable<object[]> also works, it will allow any type and any length.虽然IEnumerable<object[]>的对象数组也可以工作,但它允许任何类型和任何长度。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM