65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 XUnit 进行单元测试

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (5投票s)

2017年1月30日

CPOL

4分钟阅读

viewsIcon

23762

downloadIcon

165

使用XUnit.net开发单元测试,使用Fact和Theory形式的测试,包括正常路径测试和预期抛出异常的测试。

引言

我使用过许多单元测试框架,例如MSTest、NUnit和MbUnit,并且发现所有这些都足以进行单元测试。我个人比较偏好XUnit,主要是因为我觉得它在编写参数化测试方面更简洁一些,而且我倾向于在XUnit中编写更独立的测试,减少对属性的使用。

在这个提示中,我将介绍一些基本功能,同时测试一些非常简单的代码。在实践中,我使用流畅断言Moq之类的模拟框架,但这不在本文的讨论范围之内。附加的Visual Studio解决方案使用了XUnit.net 2.1和Visual Studio运行器,允许你在Visual Studio中调试/运行测试。

算盘

要测试的类是一个算盘,它提供简单的加、减、乘、除运算。这是一个简单的算盘,只适用于正数,并且不保存状态。你可以简单地配置算盘的大小,作为它可以返回的最大值(`ResultMax`),以及它可以用于计算的最大值(`ValueMax`)。

    public class Abacus
    {
        public readonly int ValueMax;
        public readonly int ResultMax;

        public Abacus(int valueMax, int resultMax)
        {
            ValueMax = valueMax;
            ResultMax = resultMax;
        }
        public int Add(int x, int y)
        {
            ValidateValue(x);
            ValidateValue(y);
            ValidateResult(x + y);
            return x + y;
        }

        public int Subtract(int x, int y)
        {
            ValidateValue(x);
            ValidateValue(y);
            ValidateResult(x - y);
            return x - y;
        }

        public int Multiply(int x, int y)
        {
            ValidateValue(x);
            ValidateValue(y);
            ValidateResult(x * y);
            return x * y;
        }

        public int Divide(int x, int y)
        {
            ValidateValue(x);
            ValidateValue(y);
            ValidateResult(x / y);
            return x / y;
        }

        void ValidateValue(int value)
        {
            if (value <= 0) 
            throw new ValidationException("Value must be greater than 0.");
            if (value > ValueMax) throw new ValidationException
            (String.Format("Value must be less than or equal to {0}.", ValueMax));
        }

        void ValidateResult(long result)
        {
            if (result <= 0) 
            throw new ValidationException("Result must be greater than 0.");
            if (result > ResultMax) throw new ValidationException
            (String.Format("Result must be less than or equal to {0}.", ResultMax));
        }
    }

这段代码可以在附加下载中的`MyLibrary`项目中找到。

事实

设置`XUnit`测试最简单的方法是用`Fact`属性来注释一个方法。`Fact`是一种总是应该成功的测试。以下是几个测试算盘加法运算的测试。

    public class AbacusAddTests
    {
        [Fact]
        public void CanAddOnePlusOne()
        {
            // arrange
            Abacus abacus = new Abacus(2, 4);

            // act/assert
            Assert.Equal(2, abacus.Add(1, 1));
        }

        [Fact]
        public void CanAddToResultsLimit()
        {
            // arrange
            Abacus abacus = new Abacus(2, 4);

            // act/assert
            Assert.Equal(4, abacus.Add(2, 2));
        }
    }

`Assert.Equal`方法(与NUnit等的`Assert.AreEqual`相反)用于测试测试结果。所有测试的代码(我们这里只关注加法测试)都可以在附加下载的`XUnitTests`项目中找到。

有效的理论

XUnit还有一个`Theory`属性,它表示一个应该针对某些输入数据成功的测试。在实践中,大多数代码的行为取决于输入(例如,基于验证的不同结果),我发现我比`Fact`更频繁地使用`Theory`来创建参数化测试。有三种基本方法可以创建基于`Theory`的测试,这些方法将在下面介绍。

使用InlineData属性的理论

创建基于`Theory`测试最简单的方法是使用`InlineData`属性。下面的测试接受两个参数并将它们加在一起并测试结果。我们不是编写三个测试,而是创建三个带有不同参数值的`InlineData`属性。现在我们有了三个测试用例,而只需很少的额外代码!

        [Theory]
        [InlineData(2,3)]
        [InlineData(4,5)]
        [InlineData(5,11)]
        public void CanAddNumbersFromInlineDataInput(int x, int y)
        {
            // arrange
            Abacus abacus = new Abacus(Math.Max(x, y), x + y);

            // act
            int result = abacus.Add(x, y);

            // assert
            Assert.True(result > 0);
            Assert.Equal(x + y, result);
        }

当参数化案例的数量很少时,我倾向于使用这种形式。

使用MemberData属性的理论

创建基于`Theory`测试的另一种方法是使用`MemberData`属性来提供参数信息。在下面的加法测试中,`MemberData`属性提供`AddPositiveNumberData`列表来运行参数化测试。同样,使用不同的参数运行三个不同的测试用例。

        [Theory]
        [MemberData("AddPositiveNumberData")]
        public void CanAddNumbersFromMemberDataInput(int x, int y)
        {
            // arrange
            Abacus abacus = new Abacus(Math.Max(x, y), x + y);

            // act
            int result = abacus.Add(x, y);

            // assert
            Assert.True(result > 0);
            Assert.Equal(x + y, result);
        }

        private static List<object[]> AddPositiveNumberData()
        {
            return new List<object[]>
           {
               new object[] {1, 2},
               new object[] {2, 2},
               new object[] {5, 9}
           };
        }

我倾向于将其用于更大和/或可重用的参数数据集。

使用自定义DataAttribute的理论

最后,你可以通过定义和使用你自己的自定义`DataAttribute`来创建一个基于`Theory`的测试。下面,`AbacusDataAttribute`提供了一种提供`x`和`y`值的枚举(长度为`Count`)的方法,用于测试。

    public class AbacusDataAttribute : DataAttribute
    {
        private readonly int XStart, XIncrement, YStart, YIncrement, Count;

        public AbacusDataAttribute
        (int xStart, int xIncrement, int yStart, int yIncrement, int count)
        {
            XStart = xStart;
            XIncrement = xIncrement;
            YStart = yStart;
            YIncrement = yIncrement;
            Count = count;
        }

        public override IEnumerable<object[]> GetData(MethodInfo testMethod)
        {
            for (int i = 0; i < Count; i++)
            {
                yield return new object[] 
                { XStart + i * XIncrement, YStart + i * YIncrement };
            }
        }
    }

在这里,我们还有一个加法测试,它使用`AbacusData`属性提供20个具有不同`x`和`y`值的测试用例。

        [Theory]
        [AbacusData(1, 2, 4, 3, 20)]
        public void CanAddNumbersFromAttributeInput(int x, int y)
        {
            // arrange
            Abacus abacus = new Abacus(Math.Max(x, y), x + y);

            // act
            int result = abacus.Add(x, y);

            // assert
            Assert.True(result > 0);
            Assert.Equal(x + y, result);
        }

如果输入数据可以用算法以有用的方式表达(这个例子有点牵强),我倾向于使用自定义属性。

异常测试

在`XUnit`测试中使用断言与NUnit等非常相似,只是XUnit的语法更简洁。XUnit采用不同的方法来处理抛出异常的测试。XUnit没有更典型的`ExpectedException`属性,而是一个`Assert.Throws`断言,它使在执行测试操作时更容易管理异常和消息数据。在下面的测试中,我们断言抛出一个`ValidationException`,并且验证消息符合预期。

        [Theory]
        [AbacusData(1, 2, 4, 5, 10)]
        public void CannotOverFlowAddResult(int x, int y)
        {
            // arrange
            Abacus abacus = new Abacus(Math.Max(x, y), x);

            // act/assert
            Exception ex = Assert.Throws<ValidationException>(() => abacus.Add(x, y));
            Assert.Equal(String.Format("Result must be less than or equal to {0}.", x), ex.Message);
        }

示例下载中还有更多测试用例,你可以查看。构建解决方案,你应该能够运行所有测试并根据你的喜好调试测试。

结论

我希望这个简短的演练对您开始使用XUnit,特别是XUnit.net有所帮助。如果您想了解任何XUnit功能的更深入信息,请在下面评论。

© . All rights reserved.