关于 test-gap-analysis
面向开发者的测试分析技能,用于评估现有测试是否保护调用方可见的生产行为。它将公开结果与断言对应,识别可信的覆盖缺口,并支持定向验证或补充测试。
主要功能
- 01盘点调用方可见结果
- 02将断言映射到行为
- 03分析边界与错误路径
- 04验证可信的变异存活项
- 05指导定向补充测试
适用场景
- 01审查组件的测试覆盖
- 02检查指定风险是否有测试
- 03添加测试前验证覆盖缺口
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill test-gap-analysis安装后在 Claude Code 中输入/test-gap-analysis确认已加载。
附带文件references/mutation-catalog.mdreferences/per-test-read-only.md
详细介绍
测试缺口分析
回答一个问题:**哪些调用方可见的生产行为可能发生变化,而现有测试却不会失败?**变异推理只是探查手段,并非目标。先盘点公开结果,再核实可信的缺口。
组合模式分派
进入决策流程前,先检查调用方的模式。明确请求 per-test-read-only(包括来自 grade-tests 的组合请求)时,只使用 references/per-test-read-only.md。
此模式是通过宿主支持的调用方指令传达的评估上下文,并非新的技能加载器参数或特定模式的技能名称。阅读该参考文件,并以内联方式返回其逐项测试评估;不要进入下方的独立工作流。加载此模式并不授权执行基线、运行变异、编辑文件、发现测试套件或委派任务。
如果无法找到捆绑的参考文件,最多仅可列出一次已知的 references/ 目录,然后以 N/A / unverified 和原因作答。
不带此模式的请求仍按下方的独立分析、验证和添加测试流程处理。
决策流程
1. 确定范围
根据清单文件和文件类型发现生产代码文件及测试文件。缩小范围搜索无果后,先全面检查当前目录,再询问路径。
| 请求 | 操作 |
|---|---|
| 单个组件或指定风险 | 盘点范围内所有高风险公开结果;除非请求验证,否则不要编辑生产代码 |
| 通用的小型组件审查 | 盘点不同结果,并根据源代码与断言的映射报告调用方可见的缺口 |
| 明确要求验证存活项 | 盘点所有指定结果;在验证范围内,为每个不同的高风险结果执行一个有代表性的可观测候选项,然后将其归类为 Survived 或 Killed |
| 明确要求穷尽式审计 | 阅读 references/mutation-catalog.md,并对所有有意义的候选项进行分类 |
| 为现有测试套件添加测试 | 先分析;仅为已验证存活项或已证实未覆盖的结果添加测试 |
| 创建新测试套件 | 停止,并使用 code-testing |
当请求指定风险时,在阅读代码前,将其转换为一行公开结果允许列表。仅仅因为某个方法会写入某个结果,并不意味着该结果就在范围内。
对于 money math,允许范围包括计算得出或返回的金额、费率、档位/边界选择、百分比的基数/顺序、下限/上限和舍入;不包括非货币状态谓词(包括派生布尔值)、身份信息和格式化。仅当私有代码用于追踪允许的结果时,才将其纳入范围。
不要将聚焦请求扩展为仓库审计、计划文档或仪表板。对于熟悉的框架,直接使用源代码和测试。如果发现流程或断言语义不明确,请根据调用方提供的或运行时列出的 test-analysis-extensions 目录,读取相应语言文件;不要调用仅供参考的辅助工具,也不要搜索安装目录。若该目录缺失,请说明限制,并遵循此处固定的 API 和规则。
2. 建立一个基线
对于仅基于源代码的建议性审查,跳过执行,并将经验性存活情况标记为未经验证。用户要求执行或验证存活情况,或要求通过测试弥补缺口时,必须建立基线。运行一次现有的最精简测试命令。根据项目清单选择该命令;Microsoft.Testing.Platform 可执行文件可能需要使用 dotnet run。确认测试已执行:退出代码为 0 且输出只有构建信息,并不代表测试通过。如果这一次尝试无法运行测试套件,对于建议性审查,不要排查测试运行器,也不要尝试其他命令;继续静态审查,并将所有候选项标记为未经验证。不要根据缺少输出推断项目配置方面的原因;只有命令明确报告原因时,才说明原因。
测试运行器输出缺失,只会限制对变异能否经验性存活的判断,不会使已由源代码证实的事实变得不确定:如果公开结果没有对应的测试覆盖,仍然属于无覆盖;如果精确预期值是根据未经修改的实现推导得出的,仍然可以直接采取行动。只说明一次基线的限制,然后直接给出静态源代码与断言的结论,不必在每一行都使用保留措辞。
对于“测试能否发现这个问题?”之类的建议性审查,基线之后就停止执行。源代码到断言的映射,足以作为无覆盖和**候选存活项(未经验证)**的证据。仅当原始值不明确时,才追踪或运行一次未修改的代码。只有在明确要求验证、进行全面审计或通过测试弥补缺口时,才应用变异。
任何针对特定范围的变异预算只限制执行,不限制发现。将每个不同且未断言的公开结果都保留在清单中。
3. 清点公开结果
对于每个公开入口,梳理:
- 输入分区:分类器分支、复合条件、无效及最接近有效值的守卫边界,以及默认情况;
- 每项独立观察结果:返回字段/变体、异常类型、是否接受无效输入、公开状态转换或外部副作用;
- 通过公开调用者观察到的私有辅助函数组合、常量/比率、舍入、重试、取消及错误传播。
使用 公开输入/序列 -> 预期结果 -> 现有断言 -> 缺口。
断言一个返回字段,并未覆盖其他字段。一种允许的结果,并未覆盖其拒绝情况。
**金额计算:**清点空操作路径、每种比率/档位及其精确边界、运算顺序、百分比基数或组合方式、下限/上限和舍入。通过公开结果追踪私有辅助函数。仅断言大致范围的测试无法确定任何精确金额。对于每条可执行的金额记录,推导一个见证输入,并给出其经过完整调用链后得到的精确原始结果;不要只笼统建议“断言精确金额”,却不提供金额。
**有序守卫和重试:**清点 最小值以下的无效值 | 第一个有效值 | 最后一个允许值或可重试值 | 第一个被阻止的值 | 后续被阻止的值。对于 value >= limit 这样的上限守卫,使用 limit - 1、limit 和 limit + 1;最后一个见证值可揭示是否缩窄到 value == limit。清点每一种接受和拒绝的错误类别。类型匹配具有多态性时,加入一个有代表性的派生类接受类型,以揭示是否缩窄为精确运行时类型。仅测试第一个被阻止的值,无法保护最后一个允许值或后续被阻止的值。
**授权:**从调用者视角,枚举每种相关身份/角色、资源类别和操作。未测试的 false、禁止访问和角色不变结果,都是首要安全缺口。如果拒绝结果尚未清点,不要分析允许路径的变体。检查每个公开接口:
- 权限返回 API:分别检查每种不同角色/资源类别,以及每种返回的能力;
- 操作分发 API:检查每个读取/写入/删除类操作分支,尤其是必须返回拒绝结果的路径;
- 角色/状态转换:检查接受、拒绝、无效、
null和空输入,包括必须保持状态不变的结果。
在再次检查已部分覆盖的辅助函数变体之前,优先执行完全未经测试的公开分支。如果有超过五项高风险行为未被断言,则执行优先级最高的 3-5 项,并将其余项标记为 无覆盖 或 候选幸存者(未经验证)。
执行测试永远不能替代清单。在修改或作答之前,先对每项必需结果进行分类,包括每种无效输入、守卫边界、分类器分支、操作和拒绝结果。
**完整性检查点:**选择发现项之前,明确核对每个独立模式/标志、<= 0 守卫中的零和负数、每种接受的异常类别,以及多态匹配时一个有代表性的派生类接受异常。如果移除了守卫,请追踪后续执行路径:如果仍会产生相同的公开异常类型,则二者等价,除非更细致的异常元数据已是既定约定。
4. 只纳入可观察到的候选项
首先,将每项精确变更重放到每个已有断言输入或序列上,并固定所有参数。任何返回值、异常、状态或副作用发生变化的候选项都属于很可能被杀死;无需为其编写专门的单一用途测试。绝不要用原始实现的一个输入与变异版本的另一个输入进行比较。
对于未被杀死的候选项,在执行或报告前选定一个见证,并按 见证 -> 原始实现的观察结果 -> 变异版本的观察结果 的格式说明。在最小化测试中复用该见证。只有追踪完整调用链后,确认最后两个观察结果在公开行为上存在差异,才纳入候选项;否则,选择一个能区分两者的见证,或将其舍弃。
排除以下情况:
- 需要插入或重排语句,而非变更或移除现有表达式、条件、常量、返回值或副作用的改动;
- 无法编译的改动,包括移除仍被引用的声明;
- 溢出行为、异常消息/
ParamName元数据,或当前契约、源码意图或测试未确立的其他语义; - 移除后仍会得到相同结果、异常、状态和副作用的保护条件或短路逻辑;
- 私有表示形式的变更,即使测试套件仍全部通过,只要所有公开输入序列观察到的行为都相同;
- 提议的测试对原始实现和变异版本都会通过的变异;
- 在用于区分两者的输入上返回相同值的边界条件改动;例如,将
result < floor ? floor : result改为<=,在相等时等价,因为两个分支都返回floor; - 独立的自动属性或简单的单行包装器/谓词,若其没有有意义的分支、计算或副作用,除非用户明确指定;
- 假设的未来影响、生成的代码、仅影响日志/格式的改动、不可能的值以及重复的语法变体。
缺少直接断言并不能证明无覆盖:应先追踪现有断言经过的公开调用方和共享分支。缺少断言会使可观察到的候选项成为未被杀死项;但不会让不起作用的变异变得有意义。
5. 排序和分类
排序:(1)安全拒绝、财务结果、错误和状态变更; (2)完全未断言的公开结果;(3)通过较弱断言覆盖的边界或精确值;(4)已断言行为的其他变体。
完成清单后,再选择变异或给出结论。一次变异被杀死、某种异常类型或某个 switch 分支,并不意味着其余同类项也已覆盖。
根据已完成的清单选择结论:
- Strong:核心分支和主要边界均受到保护,且仅剩少数验证或默认情况的变体;
- Mixed:存在有意义的覆盖,但至少有一个重要结果分区未受保护;
- Weak:重要结果普遍未受保护。
少数验证缺口不会让原本覆盖广泛的测试套件变成 Mixed,除非验证本身就是指定的风险,或者这些缺口会威胁安全、数据或其他合同关键行为。
当清单满足上述 Strong 标准时,先给出 Strong,并在列出次要缺口之前说明受到保护的边界和双重断言。不要以 Mixed、“仅覆盖核心路径”或风险过重的仪表板开头。
当现有断言能够杀死剩余候选项,或不再存在可信的公开存活项时,即可停止。不要为了填满报告或计算分数而变异每个运算符。
| 结果 | 含义 |
|---|---|
| Likely killed | 现有断言会观察到变更后的结果 |
| Candidate survivor (unverified) | 观察到的变更似乎未经断言;尚未执行 |
| Killed | 已执行精确的可观察变异,且相关断言失败 |
| Survived | 已执行精确的可观察变异,且测试仍通过 |
| No coverage | 没有测试到达该公开结果;报告缺失的分支,不要凭空编造存活项 |
| Equivalent | 公开观察结果没有变化;从发现项中省略 |
除非明确要求验证、全面审计或新增测试,否则不要执行任何变异。不要为了确认明显的无覆盖而进行变异。对于明确要求的验证,针对范围内每种不同的高风险结果各执行一个代表性候选项;如果仍有其他守卫条件、操作分支、错误类别或拒绝行为尚未分类,不要在执行一两个后就停止。省略等效的语法变体。
6. 验证时避免误报
仅在明确要求验证、全面审计或新增测试时进入此阶段。
- 应用一个候选项,并确认差异只更改了一个预期表达式。
- 运行范围最窄且能覆盖该项的测试,并确认测试确实执行:通过即为 Survived;相关断言失败即为仅针对该次编辑的 Killed。构建、发现、基础设施或无关失败会使候选项保持未验证,而不是 Killed。
- 立即回滚,并确认源代码和测试均已恢复干净基线。
- 测试通过后,重新检查公开反事实。执行只能证明测试套件未发现该编辑,不能证明该编辑会改变行为;删除无效或不可观察的变异。
绝不要让变异留在工作区中。报告之前,将每个未经断言覆盖的高风险结果归类为 Survived、Candidate survivor (unverified)、No coverage,或省略的 Equivalent。当不再存在可信的公开缺口时即停止;除非用户要求全面审计,否则不要用内部细节填充报告,也不要计算分数。
7. 仅在请求时弥补覆盖缺口
- 仅针对已执行且结果为 Survived 的变异,或已证明的 No coverage 行为,添加有针对性的测试。
- 在为已覆盖行为的其他变体添加测试之前,先覆盖请求范围内的每个不同缺口。
- 编辑前,先创建“存活变异到测试”的核对清单。停止前,将每个已验证的存活变异映射到新增测试,并将每个新增测试反向映射到已验证的存活变异;仅最终测试套件通过,并不能证明覆盖完整。
- 按要求保留生产代码和现有测试。
- 优先编写一个聚焦行为的测试来杀死相关变异,而不是针对每处语法变更各编写一个测试。
- 重新应用原始变异,并证明新测试能将其杀死,然后还原源代码并干净地运行范围最小的测试套件。
- 如果测试夹具或代码仓库提供了规范的变异验证器,请在添加测试后运行其精确命令,并引用成功结果。手动创建的替代变异、大范围测试套件通过或测试数量增加,都不能替代所提供的验证工具。一旦每个请求的存活变异都映射到有针对性的测试,且规范验证器通过,就应停止;额外测试并无益处。
- 如果请求要求现有源文件或测试文件保持不变,请将每个受保护文件与编辑前快照逐字节比较,并报告该证据。添加测试前,先证明其验证用例在相关分支、边界或舍入结果上与每个现有用例都不同,避免名义上的新测试重复已有覆盖。
输出要求
根据请求调整回复内容的详略。
对于聚焦或小规模分析,请返回:
-
一行结论:Strong、Mixed 或 Weak,并说明原因。
-
对于 Strong 套件,用一句简短的优势说明指出支撑该结论的具体受保护边界、守卫条件或配对观察。
-
每个可采取行动的 Survived、Candidate survivor (unverified) 或 No coverage 结果各占一行。添加行之前,如果请求列出了风险,先应用结果白名单,再应用可观察候选项规则;任一筛选不通过的候选项均应省略。包含所有高风险结果;每个不同的公开结果各占一行,只有相关的低风险变体可以合并。
风险 公开结果 变更 结果/证据 最小测试 每个缺口都需要一个可区分的验证用例和一个具体的最小测试。错误路径缺口必须指出无效输入及预期错误/结果。
-
对于 Mixed 或 Weak 套件,用一句简短的优势说明指出已成功杀死的重要行为。
-
如果请求列出了排除项,用一句简短的范围说明指出有意跳过的生成代码、简单代码或无关代码。
不要在正文中重复表格,也不要报告已剔除的变异、工具执行顺序或尚未完成的推理过程。
对于穷尽式审计,请分别统计已执行的 Killed / Survived 数量,以及静态分类的 Likely killed / Candidate survivor (unverified) 和 No coverage / Equivalent 数量。静态推理绝不能计入实测的杀死比例。按风险对结果分组。
对于新增测试,请列出新增的测试、它们杀死的已验证变异,以及成功的最终命令。
可靠性规则
- 如果通过的测试没有断言已更改的结果,就无法杀死该变异。
- 覆盖率按行为分区计算。一个 switch/三元表达式分支或一组复合输入,并不能证明其余分支也受覆盖:允许不代表拒绝;读取不代表写入;如果 null、空值或空白值对调用方的可见结果不同,那么验证 null 并不能证明另外两者。只有实际执行的编辑和路径才算清除。
- 通过公共方法调用的私有辅助方法仍在范围内。
- 错误语义因语言而异:在 Rust 中,
?传播与 panic 是可观察行为;在 C# 中,异常类型以及输入校验是否接受或拒绝某个值,都是可观察行为。 - 每个精确数值或边界结果都要与未经修改的实现或现有精确断言交叉核对。如果无法核对,就只说明行为关系,不要编造数值。
- 不要仅仅因为某个变异存活,就将发现标为高风险。
- 对现有测试套件已经覆盖的行为,绝不要建议添加冗余测试。
验证
- 范围与请求相称
- 原始测试套件通过,或已明确说明仅进行静态分析的限制
- 已盘点范围内所有高风险的公共行为结果
- 原始版本和变异版本产生了不同的调用方可见结果
- 已执行所有标记为 Survived 的结果;未执行的候选项标记为 Candidate survivor (unverified)
- 所有临时变异均已还原
- 发现不包括琐碎、生成的和等效的更改
- 建议仅针对已证实的缺口
- 已明确说明范围内的每个公共入口分支和每种已接受的异常类型
- 已运行并报告提供的规范变异验证器,而非用临时替代方案取代它