【问题标题】:Xunit Test for odd numbers奇数的 Xunit 测试
【发布时间】:2020-06-27 12:12:04
【问题描述】:

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

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

【问题讨论】:

    标签: c# xunit


    【解决方案1】:

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

    你可以像这样使用 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();
        }
    }
    

    【讨论】:

      【解决方案2】:

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

      您想改为测试一个单独的类。

      第二,你会在哪里传递预期的结果?

      您可以找到一种方法将多个数据传递给您的测试,就像 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/

      【讨论】:

        【解决方案3】:

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

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

        例子:

        项目中的一些类 - 假设 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),
        • 我们为项目中的每个类创建一个测试项目中的类 (Sort.cs - SortTest.cs)
        • 我们添加对项目的引用
        • 我们在构造函数中创建一个我们将要测试的类,
        • 我们定义了我们想要的代码的所有要求,我们只是以方法的形式编写它们,
        • 方法正在运行代码以检查我们方法的给定属性。

        最后还有几句话:

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

        【讨论】:

          【解决方案4】:

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

          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&lt;&gt; 助手:

          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&lt;object[]&gt;)模型。 Andrew Lock 有几个很棒的 blog posts 关于这个话题。您也可以在xUnit samples on github 中看到这一点。

          【讨论】:

            猜你喜欢
            • 2016-05-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-12-10
            • 2019-01-03
            • 2021-04-20
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多