奇数的 Xunit 测试

Xunit Test for odd numbers

我正在努力提高我的单元测试技能,我想知道是否有人可以告诉我编写测试“给定整数数组的排序奇数”测试的最佳方法。目前我正在使用参数将数据传递到测试中,但我也会验证输出是否正确。

    [Theory]
    [InlineData(1,2,3,4)]
    public void SortArrayOddNumber(params int[] number)
    {
        var s = number.Where(n => (n % 2) == 0).OrderBy(n=>n);
    }

您需要将预期输出作为参数传递。从最基本的角度来看,单元测试应该是这样的:

  • 输入数据,最好是测试方法需要的所有参数,
  • 单元测试方法的名称应该是描述性的,并准确说明它在测试什么,
  • 应该将测试方法的结果 return 与预期值进行比较。

示例:

项目中的一些 class - 假设 MyLib 库项目:

Sort.cs

using System.Linq;

namespace MyLib 
{
    public class Sort
    {
        public int[] SortArrayOddNumber(int[] arrayToSort)
        {
            return arrayToSort
                .Where(n => (n % 2) == 0)
                .OrderBy(n => n)
                .ToArray();
        }
    }
}

测试项目MyLib.Tests:

SortTest.cs

using Xunit;

namespace MyLib.Tests
{
    public class SortTest
    {
        // System under test
        private readonly Sort sut;

        public SortTest()
        {
            sut = new Sort();
        }

        [Theory]
        [InlineData(new[] { 1, 2, 3, 4}, new[] { 2, 4 })]
        public void SortArrayOddNumber_should_return_array_with_only_even_numbers_sorded_inc(
            int[] arrayToSort,
            int[] expectedResult)
        {
            // Arrange
            // well... nothing to do before running our method we are testing...

            // Act
            var result = sut.SortArrayOddNumber(arrayToSort);

            // Assert
            Assert.Equal(expected: expectedResult, actual: result);
        }
    }
}

所以:

  • 我们有方法要测试,
  • 我们为每个要测试的项目创建 *.Tests 项目 (A - A.Tests),
  • 我们为项目中的每个 class 创建一个 class 在测试项目中 (Sort.cs - SortTest.cs)
  • 我们添加对项目的引用
  • 我们在构造函数中创建一个 class 我们将进行测试,
  • 我们定义了我们想要的代码的所有要求,我们只是以方法的形式编写它们,
  • 方法是 运行 检查我们方法的给定 属性 的代码。

最后的几点评论:

  • 测试方法的命名方式有多种。总的来说,我花了很多时间试图挑选最好的一个,但我会说它应该只满足 1 个条件:reading the name should get You an answer what does this method check。也许第二个:stick to the selected convention
  • 您应该从不计算预期结果。您在那里或在您正在测试的代码中犯错误的可能性是一样的。您应该事先知道输入是什么以及您想从该方法中获得什么。所以你不应该通过重新计算来检查结果,而是以一种不同的、“更安全”的方式。

您应该将输入数组和预期数组作为测试中的参数传递。

您可以像这样使用 InlineData :

    [Theory]
    [InlineData(new[] { 0, 1, 2, 3, 4 }, new[] { 0, 2, 4 })]
    [InlineData(new[] { 0, 1, 3, 4 }, new[] { 0, 4 })]
    public void Should_return_odd_numbers_in_ascending_order(int[] input, int[] expectedSortedArray)
    {
        var result = input.SortOddNumbers();

        result.Should().BeEquivalentTo(expectedSortedArray, opt => opt.WithStrictOrdering());
    }

或者您可以像这样使用 MemberData :

    public static IEnumerable<object[]> Data => new List<object[]>
    {
       new object[] {new[] {0, 1, 2, 3, 4}, new[] {0, 2, 4}},
       new object[] {new[] {0, 1, 3, 4}, new[] {0, 4}}
    };

    [Theory, MemberData(nameof(Data))]
    public void Should_return_odd_numbers_in_ascending_order(int[] input, int[] expectedSortedArray)
    {
        var result = input.SortOddNumbers();

        result.Should().BeEquivalentTo(expectedSortedArray, opt => opt.WithStrictOrdering());
    }

和 SortOddNumbers 扩展方法:

public static class NumberExtensions
{
    public static int[] SortOddNumbers(this int[] input)
    {
        return input.Where(i => (i % 2) == 0).OrderBy(i => i).ToArray();
    }
}

首先,您正在编写测试内部的逻辑。

您想测试一个单独的 class。

其次,你会把预期的结果传到哪里?

你可以找到一种方法将多个数据传递给你的测试,就像 Arsalan 在他的回答中说的那样,或者你的断言应该检查所有数字都是奇数并且它们是按升序排列的。

你可以查看 FluentAssertions 库,然后像这样编写你的测试:

// The class/method you are testing
public class MyLogic
{
  public int[] SortOddNumbers(int[] input)
  {
    return input.Where(n => (n%2) == 1).OrderBy(n => n).ToArray();
  }
}

测试:

public class MyLogic.Tests
{

  [Theory]
  [InlineData(6,1,2,3,10,9,8)]
  public void SortOddNumbers_ShouldReturn_OddNumbersInAscendingOrder(params int[] input)
  {
    // Arrange
    var logic = new MyLogic();
    // Act
    var actual = logic.SortOddNumbers(input);
    // Assert
    actual.Should().OnlyContain(x => x % 2 == 1);
    actual.Should().BeInAscendingOrder(x => x);
  }
}

编辑:要找到有关如何将数据传递给测试的多种方法(和灵感),这里有一篇 Andrew Lock 的优秀文章:https://andrewlock.net/creating-parameterised-tests-in-xunit-with-inlinedata-classdata-and-memberdata/

你并不是真的在写单元测试。您正在测试中编写一些代码并希望对其进行验证。通常,您会在其他地方尝试验证一种方法。假设您这样做:

public static Numbers
{
   public static IEnumerable<int> FilterAndSortOdds(IEnumerable<int> source) 
       => source.Where(i => (i%2) != 0).OrderBy(i => i);
}

然后您将验证从您的测试中调用该方法。例如,一个简单的 Fact 可能如下所示:

public class TestClass
{

    [Fact]
    public void CanSortAndFilterOddNumbers() 
    { 
       var source = new[] { 1, 2, 9, 4, 5 };
       var expected = new[] { 1, 5, 9 };
    
       var actual = Numbers.FilterAndSortOdds(source).ToArray();
       Assert.Equal(expected, actual);
    } 

}

现在如果你想把它变成一个理论,你有几个选择。您可以使用 InlineData(通过创建新数组)

public class TestClass
{
    [Theory]
    [InlineData(new[] { 1, 2, 9, 4, 5 }, new[] { 1, 5, 9 })] 
    [InlineData(new[] { 2, 1 }, new[] { 1 })] 
    [InlineData(new[] { 9, 7, 5, 3, 1 }, new[] { 1, 3, 5, 7, 9 })] 
    public void CanSortAndFilterOddNumbers(int[] source, int[] expected) 
    { 
       var actual = Numbers.FilterAndSortOdds(source).ToArray();
       Assert.Equal(expected, actual);
    } 

您也可以使用 MemberData:

public class TestClass
{
    [Theory]
    [MemberData(nameof(PassingData)] 
    public void CanSortAndFilterOddNumbers(int[] source, int[] expected) 
    { 
       var actual = Numbers.FilterAndSortOdds(source).ToArray();
       Assert.Equal(expected, actual);
    } 

    public static IEnumerable<object[]> PassingData => new List<object[]> {
         /* Test 1 */
         new object[] { new[] { 1, 2, 9, 4, 5 }, new[] { 1, 5, 9 } }, 
        /* Test 2 */
         new object[] { new[] { 2, 1 }, new[] { 1 } }, 
        /* Test 3 */
         new object[] { new[] { 9, 7, 5, 3, 1 }, new[] { 1, 3, 5, 7, 9 } }
   };
}

对于更强类型的东西,我们可以使用 ClassDataTheoryData<> 助手:

public class TestClass
{
    [Theory]
    [ClassData(nameof(PassingOdds)] 
    public void CanSortAndFilterOddNumbers(IEnumerable<int> source, int[] expected) 
    { 
       var actual = Numbers.FilterAndSortOdds(source).ToArray();
       Assert.Equal(expected, actual);
    } 
}

public class PassingOdds : 
    TheoryData</*arg1*/IEnumerable<int>, /*arg2*/int[]>
{
    public PassingOdds()
    {
         /* Test 1*/
         Add(new[] { 1, 2, 9, 4, 5 }, new[] { 1, 5, 9 });
         /* Test 2 */
         Add(new List<int> { 2, 1 }, new[] { 1 });
         /* Test 3 */
         Add(new[] { 9, 7, 5, 3, 1 }, new[] { 1, 3, 5, 7, 9 });
    } 
} 

ClassData 也可以用于类似于 MemberData 的非通用(基于 IEnumerable<object[]> 的)模型。 Andrew Lock 有一对很棒的 blog posts on the topic. You can also see this in the xUnit samples on github .