使用 XUnit 进行单元测试






4.80/5 (5投票s)
使用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功能的更深入信息,请在下面评论。