【问题标题】:xUnit and multiple data records for a test用于测试的 xUnit 和多个数据记录
【发布时间】:2017-02-26 10:18:01
【问题描述】:

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

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

【问题讨论】:

    标签: c# unit-testing xunit


    【解决方案1】:

    最好在 xUnit 中使用一个专门的构造,称为 Theory,它处理所谓的“数据驱动测试”。 使用Theory 属性装饰您的测试方法,然后确保返回带有输入参数和预期结果的static“成员”,就像您对TestData 类所做的那样。请参阅下面的示例,并参考 xUnit 文档:"Writing your first theory"

    因此,我将重构您的代码,如下所示。首先使用属性TheoryMemberData 装饰测试,并向测试“@base”、“exponent”和“expectedResult”添加参数——就像你在TestData 类中所做的那样。 xUnit 不允许您使用 TestData 类,它只接受 IEnumerable&lt;object&gt; 并要求它是静态的,但 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 };
        }
    }
    

    【讨论】:

    • 我可以永远不要使用特殊类吗?
    • 你可以,但你总是必须返回一个 object[] 的 IEnumerable - 正如 xUnit 文档中提到的那样。
    【解决方案2】:

    您正在使用类成员来定义您的数据,这在您的情况下是错误的。当在运行时指定值(可能循环从 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);
    }
    

    这样你就可以在一个大类中进行更易读的测试。

    【讨论】:

    • 谢谢。将另一个标记为答案,因为第一个 - 我更喜欢注释较少的版本。
    【解决方案3】:

    对于不使用object[]的测试方法的强类型参数列表,也可以使用TheoryData。它为最多 10 个参数定义了几个通用重载。由于您的方法有 3 个整数输入值 @baseexponentexpected,因此您可以使用 TheoryData&lt;int, int, int&gt; 类型的属性。然后,使用 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&lt;int, int, int&gt; 的原因是:

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

    语法(称为collection initializer)是因为它实现了IEnumerable并定义了一个Add&lt;int, int, int&gt;(int, int, int)方法,该方法接受三个整数参数(&lt;int, int, int&gt;泛型重载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);
        }
    }
    

    现在代替MemberData,使用ClassData 属性及其参数注释PowerOfTest() 方法,并将其参数作为PowerOfTestDataClass 的类型:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-18
      • 2018-05-16
      • 1970-01-01
      • 2021-12-24
      • 2015-10-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多