您的单元测试在撒谎(而您心知肚明)
\n\n想象一下:您写了50个单元测试,全部通过,覆盖率90%。您很满意。但一周后,生产环境崩溃了。原来,您的方法SortArray()对于[1, 2, 3]和[3, 1, 2]运行良好,但在[int.MaxValue, -1, 0]上崩溃了。熟悉吗?
经典单元测试的问题在于开发者的偏见。您编写的测试只针对您预期的数据。黑客、真实用户和随机的边界条件遵循着不同的规则。
\n\n这时,基于属性的测试在C#中(或基于属性的测试)登场了。与其检查“如果A,则B”,您描述的是属性,这些属性必须始终为真。而框架会自动生成数百个随机输入数据并验证您的断言。
\n\n在本文中,我们将探讨如何通过FsCheck库在C#中引入基于属性的测试,编写真实示例,并看看它如何捕获普通测试遗漏的Bug。
\n\n什么是基于属性的测试?
\n\n基于属性的测试是一种方法,您在其中制定程序的不变量(属性)。例如:
\n- \n
- “排序后数组长度不变” \n
- “反转字符串的逆序得到原始字符串” \n
- “两个正数之和始终为正” \n
框架(例如FsCheck)会生成数十甚至数百个随机输入数据,并检查该属性是否对所有数据都成立。如果找到反例,测试失败,您会得到导致代码崩溃的具体值。
\n\n与单元测试不同,您不会写Assert.AreEqual(3, Add(1,2))。您会写Assert(x + y == y + x)——然后测试数千对数字的交换律。
与普通单元测试的区别
\n\n| 单元测试 | 基于属性的测试 |
|---|---|
| 检查具体示例 | 检查通用法则 |
| 数据由开发者指定 | 数据由框架生成 |
| 遗漏边界情况 | 自动探索边界 |
| 易于阅读 | 需要抽象思维 |
FsCheck库:您的主要工具
\n\n在C#世界中,基于属性测试的事实标准是FsCheck(最初是Haskell库QuickCheck的移植)。它与xUnit、NUnit和MSTest集成。
\n\n安装很简单:
\ndotnet add package FsCheck\ndotnet add package FsCheck.Xunit // 用于与xUnit集成\n\nFsCheck能够为基本类型(int、string、float)、列表、数组、元组甚至自定义类型(通过自定义生成器)生成随机数据。
编写第一个基于属性的C#测试
\n\n我们从简单的开始:验证整数加法运算的交换律。
\n\nusing FsCheck;\nusing FsCheck.Xunit;\n\npublic class AdditionTests\n{\n [Property]\n public bool Commutative_Addition(int x, int y)\n {\n return x + y == y + x;\n }\n}\n\n运行测试。FsCheck会生成100对随机的int(默认情况下)并检查该属性。如果没有找到反例,测试通过。如果我们犯了错误(例如,写了x - y == y - x),测试会失败并给出具体示例:(5, 3)——因为5 - 3 != 3 - 5。
解析代码
\n- \n
- [Property]——属性,告诉FsCheck这是一个基于属性的测试。 \n
- 方法返回
bool——如果返回false,测试失败。 \n - 参数
int x, int y——FsCheck会自动填入随机值