收益:
- 通过独立于接受规则计算单元测试中的期望值,能够防止人工智能将错误行为视为“正确”
- 能够通过应用 AAA 和 FIRST 原则并模拟外部依赖关系来打印快速、独立和可重复的测试
- 能够通过突变(代码破坏)来测试测试并将难以测试的代码识别为设计味道
测试金字塔中最大、最快的一层是单元测试——独立于其他所有内容来验证一个函数或一小段代码的测试。数以千计的单元测试在几秒钟内运行,并在代码仍在开发人员的屏幕上时捕获错误。人工智能 (AI) 也许最擅长生成单元测试:你给它一个函数,AI 会生成数十个测试。但正是这种便利带来了最大的陷阱:人工智能很容易生成“发出绿光但不验证任何内容”的测试,或者接受代码当前(可能是错误的)行为为“正确”。在本单元中,您将学习如何使用 AI 编写真正具有保护性的单元测试以及可测试代码与 AI 之间的关系。
良好单元测试的品质:第一
好的单元测试遵循第一原则:快速、独立(测试不应相互依赖)、可重复(可重复——在任何环境中结果相同)、自我验证(明确通过/失败)、及时(准时)。让人工智能进行测试时提醒自己这些原则;特别要求测试不依赖于外界(实际的数据库、网络、时钟),做到“独立”和“可重复”。
AAA 模式和表达断言
可靠的单元测试遵循 AAA 结构:安排(准备 — 设置输入和依赖项)、执行(执行 — 调用被测函数)、断言(验证 — 将结果与预期值进行比较)。关键的是断言。人工智能最常见的错误是从被测代码的输出中得出断言——“无论代码返回什么都是正确的”逻辑。这使得测试变得毫无意义。正确的方法是独立确定期望值(根据验收标准,手动计算)。
注意:如果你告诉AI“为此函数编写一个测试”,AI可能会运行该函数并将其输出写为“预期”。即使该函数为 false,该测试也会通过。相反,可以说“您根据这些规则计算预期结果,不要引用函数的当前输出。”
模拟、存根和依赖项
单元测试需要隔离。如果您的函数依赖于数据库或 API,则在测试中它们会被替换为模拟对象(模拟/存根 - 真正依赖项的受控虚拟替代品)。这使得测试快速、独立且可重复。 AI可制作模拟安装;但要注意过度的模拟:如果您模拟所有内容,测试将仅验证“模拟返回的内容”,而不是实际的逻辑。平衡:模拟外部世界,执行被测的真实逻辑。
可测试性和人工智能
有一个有趣的反馈:难以测试的代码通常是设计不佳的代码。如果人工智能在向函数编写测试时遇到困难(太多依赖项、隐藏的全局状态、副作用),那就是设计味道。询问人工智能“你将如何重构这段代码以使其可测试”可以带来更好的测试和更好的代码。
参数化测试和数据多样性
每次编写一个单独的测试来验证具有不同输入的相同规则既乏味又难以维护。参数化测试——一种在输入和预期结果列表上重复运行相同测试逻辑的结构——消除了这种重复:单个测试主体被提供了数十个输入对。当你给它你的接受规则时,人工智能可以非常有效地生成这些输入预期结果表;特别是,它系统地列出了极限值和等价类。
但这里也存在一个陷阱:人工智能倾向于从被测试的代码中推导出生成表中的预期结果。这个错误在参数化测试中更加危险,因为一个错误的逻辑就会导致几十行代码无效。因此,始终根据验收规则独立计算预期结果列,并手动验证至少几行。还要求提供一个描述列“每行代表什么”;因此,当一行中断时,您会立即看到哪个状态被中断。
提示:故意将“陷阱行”添加到参数化测试表中,即故意错误地输入结果。如果运行测试时该线没有变成红色,则您的测试实际上并未验证该情况。这是一个快速的模拟通过检查。
弱提示/强提示
弱:“为此函数编写一个单元测试。”
强:为“taxCalculate(amount, rates)”函数编写[语言/框架]单元测试。接受规则:结果=金额 * 比率,四舍五入到小数点后两位;负金额或比率会引发错误;如果比率为 0,则返回 0。使用 AAA 结构。根据这些规则手动计算期望值;不要引用函数的当前输出。涵盖绑定和负数情况(0、负数、非常大、四舍五入到小数)。让每个测试的名称描述它的规则验证外部依赖性“否”。
强大的提示功能;它给出了接受规则、独立期望值期望、结构和边缘情况。因此,测试成为规则的守护者,而不是代码的镜子。
单元测试质量表
症状
错误的测试(虚假信任)
好的测试
断言
无或“非空”
预期具体价值
期望值来源
函数的输出
验收规则/手工计算
成瘾
实际数据库/网络/小时
用模拟/存根绝缘
边缘情况
唯有幸福之路
极限、负数、错误
当你破解密码时
保持绿色
变红
名称
测试1,测试方法
描述它所确认的规则
四个可复制模板
1)规则驱动的单元测试:
您的角色:高级软件测试工程师。使用[语言/框架]编写以下功能的单元测试:[签名]。接受规则:[规则]。-使用AAA结构。-根据这些规则手动计算期望值; 不要引用函数的当前输出。 - 通过单独的测试涵盖限制、负面、错误和快乐路径。 - 让每个测试名称描述它验证的规则。 - 模拟外部依赖项;让实际的逻辑发挥作用。
2)突变抗性控制:
查看这些单元测试。列出我可以对被测代码进行的 5 个小调整(用 - 代替 +,用 >= 代替 >,边界移位),并告诉我每一项测试中哪些会变成红色?如果没有返回,则测试不充分。代码+测试:[粘贴]
3)可测试性审查:
为什么为这个函数编写单元测试很困难?隐性成瘾、全球地位、副作用,责任多吗?建议进行最少的重构以使其可测试;不要改变行为。代码:[粘贴]
4)场景完成不完整:
给出了以下功能和可用的测试。列出哪些行为/边缘情况从未被测试过(范围差距)并为每个添加测试。功能+测试:[粘贴]
三个迷你箱子
案例 1 — 测试镜像代码。开发人员让 AI 为舍入函数编写测试; 10 个测试是绿色的。事实上,该函数在错误的方向上舍入,但人工智能已从函数的输出中获取了预期值,因此测试认为错误为“true”。当用“规则驱动”模板手动计算期望值时,4个测试变成红色,真正的错误被揭露。
案例 2——突变控制的价值。一个团队依赖于 45 个单元测试。通过“突变稳健性检查”对代码进行了 20 次小调整;测试仅捕获其中 11 例。剩下的9次干扰都悄无声息地过去了。团队加强薄弱测试;下一版本中的这些增强测试发现了实际的计算错误。
案例 3——不可测试性是一种设计味道。人工智能无法为排序功能编写测试,它不断需要真实的数据库。 “可测试性审查”模板显示该功能嵌入了数据库访问。当依赖注入被删除时,可以编写测试并且代码变得更干净。
常见错误
- 从代码中得出预期值。 AI接受函数输出为“正确”;确认错误代码的测试。
- 不使用断言或使用简单断言进行测试。 “他没有抛出错误,他通过了”逻辑;它并不能证实任何事情。
- 极端的嘲笑。模拟一切并仅测试模拟返回的内容;真正的逻辑没有经过测试。
- 只为幸福之路。绕过限制、负值和错误状态。
- 不通过破坏代码进行测试。信任绿色而不检查突变。
- 忽略不可测试性。不识别和修复糟糕的设计,而不是推动严格的测试。
综上所述
单元测试是测试金字塔中最快、最大的一层;它在最便宜的时刻发现了错误。人工智能非常有能力生成单元测试,但其最大的陷阱是编写测试,通过从代码本身得出预期值来将不正确的行为假设为“正确”。解决方案:给出接受规则,手动计算期望值,强制执行 AAA 和 FIRST 原则,模拟外界并运行实际逻辑,并通过突变(破坏代码)来测试每个测试。难以测试的代码是需要修复的设计标志。
应用任务
从您自己的项目中选择包含业务规则的函数。编写验收规则并让AI使用“规则驱动的单元测试”模板编写测试;手动计算期望值。然后应用“突变稳健性检查”:在代码中至少进行 5 个小中断,并测量有多少测试变成红色。添加针对未捕获的损坏的新测试。报告发现了多少干扰(例如突变评分)。
清单
- [ ] 我给出了接受规则并手动计算了期望值。
- [ ] 我确保测试没有从代码中得出预期值。
- [ ] 我已经按照 AAA 和 FIRST 指南建立了独立测试。
- [ ] 我模拟了外部依赖项并运行了实际的逻辑。
- [ ] 我涵盖了限制、否定和错误情况。
- [ ] 通过破坏代码(突变),我证明了测试确实可以提供保护。