单位 10 / 11

虚假信任风险、测试质量和突变测试:测试测试

收益:

  • 能够识别伪信任的三个方面(不自信、自以为是、琐碎断言)并采取解药
  • 能够使用突变测试和突变评分作为比工具或手动覆盖百分比更准确的质量衡量标准
  • 能够将人工智能定位为对抗测试的红队,并寻找测试漏洞,而不会陷入赞扬陷阱

该模块的核心是反复出现的警告:发出绿光的测试面板并不是质量的证据。如果你的测试给了你信心,你需要知道这种信心是真的还是假的。在人工智能(AI)时代,这个问题比以往任何时候都更加关键,因为人工智能擅长产生流畅、看似流畅但空洞的测试。错误的信心——相信软件是正确的,因为测试是绿色的,而实际上测试没有验证任何东西——是 QA 团队可能发生的最危险的事情;因为它隐藏的不是没有错误,而是你看不到错误。该单元将整个模块的验证理念整合为一门学科:测试您的测试。

衡量测试质量的黄金标准:突变测试

了解测试是否真正起到保护作用的最有效方法是突变测试(突变测试 - 一种在源代码中产生有意的小扭曲/突变并测量测试是否检测到这些扭曲的技术)。逻辑很简单:如果你故意破坏代码(将 + 变成 -,将 > 变成 >=,将 true 变成 false),一个好的测试套件应该捕获这种损坏并变成红色。如果没有,那么这种破坏就是一个幸存的突变体——所以你的测试实际上并没有保留这种行为。

突变得分=被杀死的突变/总突变。具有 90% 品系覆盖率的软件包的突变分数可能为 40%;这表明线路正在工作,但行为尚未得到验证。突变分数是比覆盖率更诚实的质量衡量标准。

提示:有自动突变工具(适用于 Java 的 PIT/Pitest、适用于 JavaScript/TypeScript 的 Stryker、适用于 .NET 的 Stryker.NET、适用于 Python 的 mutmut)。它们会自动生成并测试数百个突变。如果您没有工具,即使是手动“破坏代码测试”方法对于关键功能来说也是无价的。

伪信任的三张面孔及其解药

伪信任形式

症状

解毒剂

不带断言的测试

代码有效,没有任何内容经过验证

每次测试都正确断言;突变测试

自我确认测试

预期 = 代码输出

独立计算期望值

简单的断言

“不为空”,“返回 200”

验证业务规则/实际结果

高范围谬误

90%线路,低保护

查看突变分数

脆性测试耐受性

“又卡住了,通过”

根本原因+确定性测试

使用人工智能作为“红队”

人工智能既可以产生伪信任,也可以成为追捕伪信任的强大盟友。使用人工智能作为红队来对抗你自己的测试:询问“编写通过这些测试但错误的代码”或“找到一个可以欺骗这些测试的颠覆”。如果人工智能在你的测试中发现漏洞,那么这些漏洞就是真正的风险。

注意:不要问 AI“我的测试质量好吗?”并以“是的,很好”作为保证。 AI往往是友善的。相反,向人工智能提出一项具体任务的挑战:“产生一个通过这些测试的错误。”如果它可以产生它,那么您的测试就不会注意到该错误。

等效突变和分数限制

突变测试很强大,但它有一个问题:某些突变根本不会改变代码的行为。这些称为等效突变(等效突变体 - 损坏的代码,产生与原始结果完全相同的突变)。例如,更改从未使用过的变量的初始值不会影响输出;任何测试都不能也不应该捕捉到这一点。因此,100% 的突变评分在实践中通常是无法实现的,也不是目标。手工清除等效突变是一项劳动密集型工作;因此,不要将突变分数视为绝对的考试分数,而应将其视为“我的测试真的能起到保护作用吗?”的诚实指标。

实用的方法是这样的:不要在整个代码库中不断运行突变测试,而是在包含最高风险和最复杂业务规则的模块上运行它。一一检查这些模块中幸存的突变;如果确实存在差距,则添加测试;如果是等价突变,则标记其合理性并通过。人工智能可以进行初步筛选,评估存活突变是否等效;但最终的决定是由了解代码用途的您做出的。

注意:突变测试的计算成本很高(每个突变都会重新运行所有相关测试)。因此,一个常见且合理的策略是将其安排为每周或发布前对关键模块进行深度检查,而不是每次合并。

弱提示/强提示

弱者:“我的测试足够吗?”
Strong:“充当此函数和测试套件的红队。(1) 在代码中生成 8 个可以被杀死的突变(运算符替换、边界移位、条件反转、返回值替换)。(2) 对于每个突变,指出现有的测试中哪些会捕获它,哪些不会。(3) 对于每个幸存的突变,编写一个新的测试来杀死它。(4) 还显示您是否可以生成一个通过所有这些测试但违反业务规则的代码示例。代码+测试: [粘贴]”

强大的提示功能;它将人工智能定位为突破考试的考官,而不是表扬机器。

四个可复制模板

1)手动突变控制:

为此代码生成 8 个显着突变(轻微有意破坏):算术运算符替换、比较限制(> 与 >=)、逻辑反转、返回/常量替换、条件跳过。对于每个突变,预测哪些可用的测试会捕获它或不会捕获它。代码+测试:[粘贴]

2)杀死幸存的突变:

以下突变测试报告包含存活(未捕获)的突变:[列表/报告]。对于每个,编写一个最小的测试来杀死该突变(当以这种方式破坏时,代码将变成红色)。评论测试确认的行为。

3) 红队——抽血测试:

您能否编写通过以下所有测试但违反以下业务规则的代码:[业务规则]。如果是这样,这些测试中的什么漏洞允许这样做?添加将弥补该漏洞的测试。测试:[粘贴]

4)试验质量检验:

检查此测试套件的质量。为每个测试打勾:- 是否有一个真正的断言或者是 props?- 期望值是独立的,从代码中派生出来的吗?- 它验证了业务规则还是一些琐碎的事情?最后给出估计的“真实断言分数”和 3 个最弱的测试。测试:[粘贴]

三个迷你箱子

案例 1 — 覆盖率 92%,突变评分 38%。一支球队依赖于高覆盖率。当使用 Stryker 进行突变测试时,得分为 38%:大多数产生的突变都得以幸存。这证明测试没有运行线路并验证行为。团队花了三周时间来测试质量;突变分数增加到 81%,并且在下一个版本中这些增强的测试发现了两个实际的计算错误。

案例 2——人工智能欺骗了测试。通过“红队”模板,专家向 AI 询问通过现有测试但违反折扣规则的代码。人工智能编写的代码总是返回零折扣——并且所有测试都保持绿色,因为没有测试验证实际折扣值。看到了差距,增加了真实的断言。

案例 3——赞美陷阱。一名初级测试员问人工智能:“我的测试好吗?”听到答复,松了口气:“很全面。”他的高级同事使用“测试质量审核”模板审核了相同的测试;事实证明,20 个测试中有 12 个是装饰(没有断言或垃圾)。正确的问题带来了正确的答案。

常见错误

  • 将范围误认为质量。依赖于高行覆盖率,根本不考虑突变分数。
  • 相信人工智能的赞扬。询问“你的测试结果好吗?”并将肯定的答案视为保证。
  • 从代码中得出预期值。确认错误代码的自我验证测试。
  • 对琐碎的断言感到满意。检查不验证实际规则,例如“not null”、“200 returned”。
  • 忽略幸存的突变。忽略突变报告中未捕获的内容。
  • 甚至不尝试手动改变关键代码。如果该工具不可用,则跳过“破坏代码并测试”步骤。

总之

伪信任是相信软件是正确的,因为测试是绿色的;而测试可能无法证实任何事情。衡量这一点的黄金标准是突变测试:故意破坏代码并衡量测试是否捕获它。突变分数是比覆盖率更诚实的质量衡量标准。人工智能既会产生伪信任,又会成为追捕伪信任的强大红队——要求“产生一个通过这些测试的错误”。测试您的测试:真实断言、独立预期值、业务规则验证和终止突变。

应用任务

从您自己的项目导入包含业务规则及其测试的函数。如果可能,运行突变工具(Stryker/Pitest/mutmut)并测量突变分数;如果没有工具,则使用“手动突变控制”模板生成至少8个突变并手动尝试。对于每个幸存的突变,使用“杀死幸存的突变”模板编写一个新的测试。最后,通过“红队”模式,看看人工智能是否可以生成欺骗你的测试的代码。报告您的开始和结束突变分数(或捕获/总突变率)。

清单

  • [ ] 我通过突变分数而不是覆盖率来评估测试质量。
  • [ ] 我对关键代码进行了突变测试(通过工具或手动)。
  • [ ] 我为每个幸存的突变编写了新的测试。
  • [ ] 我用AI作为红队,在我的测试中寻找漏洞。
  • [ ] 我并没有把人工智能的“你的测试很好”的表扬当作安慰。
  • [ ] 我检查了每个测试都验证了实际断言、独立期望值和业务规则。