关于 testing-best-practices
testing-best-practices 是用于编写和审查软件测试的技能,提供 Write、Assess、Upgrade 和 Detect 四种模式,并涵盖多种语言、框架与测试类型。
主要功能
- 01编写测试并先测后修
- 02评估测试套件质量
- 03强化薄弱或不稳定的测试
- 04发现跳过或误导性测试
- 05按语言和主题选择参考资料
适用场景
- 01为缺陷修复添加测试
- 02超越覆盖率指标审查测试套件
- 03改进不稳定或过度模拟的测试
- 04发现跳过的测试和覆盖缺口
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ adewale/ testing-best-practices --skill testing-best-practices安装后在 Claude Code 中输入/testing-best-practices确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/antipatterns.mdcharacterization-testing.mdcombinatorial-testing.mdcorrectness-by-construction.mddeterministic-time.mddifferential-testing.mddoc-sync-testing.mdexhaustive-testing.mdfuzzing.mdgate-integrity.mdgo.mdgolden-file-testing.mdmathematical-properties.mdmutation-testing.mdproperty-based-testing.mdpython.mdrust.mdtest-data-builders.mdtest-types.mdtypescript.mdvcr-cassettes.md详细介绍
测试最佳实践
此技能包含四种模式:
- 编写 — 添加测试,或采用测试优先的方式修复缺陷/实现功能。
- 评估 — 评审现有测试套件的质量,而不只是测试覆盖率。
- 改进 — 加强薄弱、易出错或过度模拟的测试。
- 检测 — 查找被跳过、被篡改、虚假覆盖或容易遗漏问题的测试。
前 90 秒
在编写、评估或修改测试之前:
- 检测语言/框架:根据仓库文件和附近的测试确定。
- 阅读附近的测试:了解命名、测试夹具、构建器、断言风格和运行器命令。
- 找到最近的验证命令,再考虑范围更广的完整套件命令。
- 确定风险边界:纯逻辑、内部组件边界、外部 API、UI/用户流程、安全边界、恶意输入解析器或类型/架构不变量。
- 对变更和资产进行分类:行为变更还是结构调整/整理;可复用库还是临时探测;面向客户的规则还是内部辅助工具。
- 根据
references/test-types.md,选择最小且有用的测试层级。 - 根据下方矩阵,仅加载相关参考资料。
如果某种语言没有专门的参考资料,请遵循项目惯例和此处的通用原则。
参考矩阵
始终考虑:
- 测试类型选择 →
references/test-types.md - 反模式检测 →
references/antipatterns.md
语言/框架参考:
- Python / pytest / Hypothesis →
references/python.md - TypeScript/JavaScript / Vitest/Jest/fast-check/Playwright →
references/typescript.md - Go / testing / testing.F / Rapid →
references/go.md - Rust →
references/rust.md
对于基于属性的测试或覆盖率引导的模糊测试,也要加载匹配的语言参考:不同引擎在收集、发现和重放方面各不相同。如果此处未记录所选引擎,请遵循其项目文档,不要套用其他引擎的行为。
按触发条件选择专题参考:
- 编写/审查属性测试、选择属性、生成器设计、有状态/基于模型的测试、种子和重放 →
references/property-based-testing.md - 针对暴露/自定义解析器、原生或不安全解码器、易崩溃边界,或崩溃会禁用检查的安全工具,设计覆盖率引导的目标或测试活动 →
references/fuzzing.md - 遗留代码/重构安全 →
references/characterization-testing.md - 重新实现、移植、多语言 SDK、没有参考实现的自定义数据结构,或近似/概率/ANN/排序输出 →
references/differential-testing.md - 复杂输出、快照、转换流水线、保存/加载或迁移往返 →
references/golden-file-testing.md - 时间、计时器、计划任务、休眠、不稳定的时间测试、测试只能通过休眠才能访问的后台线程/异步工作 →
references/deterministic-time.md - 外部 API、录制的真实响应、模拟数据漂移 →
references/vcr-cassettes.md - CLI/插件/文档注册表同步,或文档和 Agent 指令中的验证声明 →
references/doc-sync-testing.md - 变异测试、变异分数/存活变异、定期变异测试流程,或覆盖率很高却仍有漏洞 →
references/mutation-testing.md。对于定期执行的流程,如果没有已完成的目标 CI 基线或已证明对某类故障有效,就不要安排;绝不要照搬绝对分数下限。 - 有限状态空间或代价高昂的笛卡尔积 →
references/exhaustive-testing.md - 大型配置/功能/运行时矩阵、成对替代方案或相互作用的因素 →
references/combinatorial-testing.md。保留固定回归用例和精确的注册项;使用具名的可变强度组,并在删除前保留影子证据。 - 算术/领域运算符/定律 →
references/mathematical-properties.md - 测试夹具/构建器/断言辅助函数 →
references/test-data-builders.md - 多层检查同一不变量、类型与测试的取舍、无效状态 →
references/correctness-by-construction.md - CI/钩子/测试配置变更、门控或定期执行的层级、“此检查是否在运行且能否失败?”、部署验证 →
references/gate-integrity.md
核心原则
通过公共契约测试行为
优秀的测试描述可观察到的行为:输出、状态转换、副作用、渲染的 UI、持久化数据、API 契约或不变量。避免编写会因不改变行为的重构而失效的测试,例如测试私有方法、偶然的调用顺序、精确的 SQL 字符串或大范围的模拟调用编排。
当用户提供示例、表格、错误报告、电子表格行或支持案例时,首先将其视为沟通材料,其次才是断言。保留业务语言,然后通过最窄且诚实的接缝运行这些示例:通常是领域/服务/API 契约,而不是为每一行业务规则都走一遍完整 UI 流程。UI/E2E 测试应证明接线正确且关键路径可用;规则表应直接测试领域层。
根据生命周期和改动类型调整测试投入
不要对所有产物采用相同的测试计划。可复用库、解析器、支付/身份验证/安全边界、迁移,以及将会重构的代码,都值得更强的回归/属性/契约测试覆盖。一次性探测、临时研究脚本或生成的探索性代码,可能只需要冒烟测试;如果用户接受其生命周期,也可以不写测试。
区分行为改动与结构改动。行为改动需要有能因新行为或变更后的行为而失败的测试。纯整理/重构应依托现有的全绿测试套件;如果没有这样的测试套件,应在重构前为需要保留的行为添加特征测试。
对于没有流水线的新服务,应从步行骨架开始:构建/部署/测试的最精简切片。将尚未完成的验收测试作为可见的进行中事项,并关联到故事/问题,而不要悄悄跳过。保持单元测试全绿;不要提交失败的单元测试来充当证明。
在可行时采用红-绿-重构
对于 Bug 修复和新行为,默认采用以下流程:
- 红 — 添加一个有针对性的测试,它会因当前 Bug/缺失行为而失败。
- 绿 — 实现能够通过测试的最小改动。
- 重构 — 在测试保持全绿的同时进行清理。
证据很重要。将红阶段证据(修复前的确切命令和失败输出)与绿阶段证据(修复后的通过命令/结果)分开。只有按顺序观察到两者,才能声称已完成 TDD/红-绿-重构。如果只有通过/绿阶段的运行结果,应说明已添加测试且现已通过,但未观察到/无法核实红阶段;不要仅凭只有绿阶段的日志,事后声称采用了 TDD。对于探索性原型、生成的代码或用户限定为“仅编写测试”的任务,应遵守范围并说明偏离之处。
采用 TDD 时,维护一份可见的测试列表。将新的边界情况想法先记入列表,不要在当前循环中追着它们实现。如果遇到阻碍,可降低难度:先写断言,拆出更小的子测试,为第三方 API 编写学习测试,或使用崩溃测试替身触发难以覆盖的错误路径。根据不确定性选择伪实现、三角法或显而易见的实现;绝不要让伪造的常量成为最终行为。
质量胜过覆盖率
覆盖率能显示运行过哪些代码,但不能证明能发现 bug。优先关注分支覆盖率,而非行覆盖率,并将覆盖率视为查找未测试路径的地图,而不是单独的质量门槛。
断言数量只是经验法则,不是铁律;切勿将其变成配额。基于示例的行为测试通常需要多个有意义的断言来验证结构、状态和反例。但属性测试、表格行或异常测试可能只需一个出色的判定依据。留意薄弱的单一断言,例如“非空”、toBeDefined()、toBeTruthy()、Assert.IsNotNull(result),或没有断言的日志记录。
对于清理器、验证器、过滤器、身份验证/安全检查和转换,在适用时验证两个方向:危险/无效内容会被拒绝或移除,安全/有效内容会被保留。
断言与被测行为相关的字段。对整个对象/整个结构进行相等性比较,会隐式断言所有无关字段,因此测试会因无关改动而失败,并陷入变更检测的疲劳循环(每次新增字段都要更新字面量)。仅在覆盖范围本身就是约定时,才比较整体状态——例如黄金文件和保存/加载往返测试(references/golden-file-testing.md)。优先使用仅凭失败消息就能着手调试的断言形式(能打印预期值与实际值的匹配器/流畅断言,而不是裸布尔值);当许多测试因迭代顺序等无关因素反复失败时,应改进断言用法(使用忽略顺序/结构化匹配器),而不是逐个修改测试。对于覆盖率,应针对新增/变更代码设定门槛,而不是设定整个仓库的统一数值;将未覆盖的部分视为评审信号。
每个自定义判定器、代码检查器、验证器、修复器或评估评分器,都需要一个已知良好的输入(应通过)和一个已知有问题的输入(应失败);保持输入精简,不要对每个有问题的输入都重新运行耗时的检查器。在与代码相同的改动中由同一方编写的黄金文件、审批样例或评估用例,只能用于描述现状,不能作为独立判定依据。
优先测试真实行为,而非模拟对象
按以下顺序优先选择:
- 真实的内存中/本地对象、临时目录、内存数据库、真实解析器。
- 对真实运行时的本地仿真(workerd/vitest-pool-workers、Node 中的 Pyodide、
wrangler dev、从迁移文件加载的 SQLite)。 - 实现相同接口且能记录历史记录的专用伪造对象。
- 用于受控边界情况的确定性桩对象。
- 最后才考虑框架模拟对象。
模拟对象的预期值应取自真实服务(通过记录或本地仿真获取),而不是模拟对象作者的设想;如果能在进程内运行真实引擎,就删除模拟对象。
优先验证可见状态,而不是调用编排:断言已保存的行、已发出的事件、响应正文、磁盘上的文件,或简单日志伪造对象记录的效果。不要模拟值,而应自行构造。模拟由你负责的角色,不要直接模拟第三方库;应将提供商 SDK 封装在自有接口之后,并为真实提供商的数据形态添加契约/VCR 检查。对于交互测试,允许查询,预期命令:getter 可以自由调用;只有产生副作用的命令才值得设置预期。
对宽泛输入空间使用属性测试
当行为涉及的输入或操作空间比示例能够经济覆盖的范围更广时,使用基于属性的测试。选择能够说明有意义行为的属性,例如往返、一致性、守恒、蜕变关系、代数定律,或与小型模型的结果一致,而不只是验证执行时没有崩溃。
让生成器与待测行为相匹配:使用任意输入测试全域性和文档所述的错误;使用符合规范的输入测试语义分支;使用有状态轨迹测试生命周期行为;使用语料变异进行覆盖引导的发现。使用独立于实现的判定依据。属性测试工具负责构造值并缩减失败用例;覆盖引导的模糊测试器则会变异语料,以探索新的控制流。不要混用这些术语或工具。
对于较小的有限空间,在检查每个单元的成本以及是否有更低成本的层能够判定该属性(references/exhaustive-testing.md)后,优先采用穷举生成,而不是抽样。关于生成器、判定依据、执行和重放的指导,请参阅 references/property-based-testing.md。
测试错误处理路径,而不只是无效输入
大多数关键的生产故障都发生在浅层:它们位于错误处理代码中,只有在依赖项已经失败时才会运行,例如磁盘写入错误、连接中断、超时或部分响应。对灾难性分布式系统故障的实证研究发现,其中大多数都能通过触发这些路径的简单测试来复现。因此,“异常路径”不只是拒绝错误参数:还要注入下游故障(例如会抛出异常的 fake/stub、注入 I/O 错误或 side_effect 异常),并断言系统能够正确降级。
断言代码实际具有的失败行为,而不是你希望它具有的行为。阅读规范或观察代码,了解故障时应该采取什么措施——传播、重试、包装、回退还是回滚——然后据此测试。不要虚构重试次数限制、自定义错误类型或契约从未承诺的回滚语义;这会测试一份虚构的契约,属于范围蔓延。如果预期的故障行为未明确规定,就对代码当前的行为进行特征测试,并将这一缺口标记为后续事项,而不是在测试中设计新的生产行为。
将内部不变量纳入类型/模式/契约
类型和测试回答的是不同问题:
- 类型/模式/契约:“这种无效状态是否可能存在?”
- 测试:“可观察行为是否符合规范?”
如果某个不变量在多个内部层中重复出现,可以考虑将其提升到边界解析器、智能构造函数、模式、密封枚举、状态机或数据库约束中。在删除检查或相关测试之前,请确认以下各项:
- 上下文属于内部环境,且不存在恶意输入,
- 类型/模式/构造函数确实强制执行了该不变量,
- 边界测试覆盖了恶意或格式错误的输入,
- 安全/身份验证/外部故障层负责防御其他故障模式,
- 生产类型变更属于范围之内,或已获明确批准。
如果用户要求“仅修改测试”或“不要更改生产代码”,就为当前 API 添加测试,并将收紧类型/模式列为后续事项。
在竞态检测器下测试并发代码,并明确并发契约
对于供多个 goroutine/线程使用的代码,顺序测试几乎无法证明什么。让多个工作线程同时启动并操作共享状态(通过启动屏障最大限度地增加竞争),并在竞态检测器或线程消毒器下运行(go test -race、ThreadSanitizer / -fsanitize=thread,以及通过 -count 重复运行)。将并发测试通过视为比顺序测试更弱的证据——竞态具有概率性。
断言 API 实际承诺的不变量——没有更新丢失、计数单调递增/线性一致、最多计算一次——而不是记录碰巧观察到的计数后就此作罢。如果实现可能违反该契约(例如在锁外计算,导致两个调用方重复计算),这就是应通过失败的测试或显式标记揭示的缺陷,而不是用 t.Logf 悄悄将竞态固化为预期行为。
模式工作流
编写模式
- 阅读相邻的测试/配置,并加载相关语言参考文档。
- 根据
references/test-types.md选择测试层级。 - 对于 Bug/新行为,尽可能先添加失败的回归测试。
- 将用户/客户示例转化为易读的测试用例,而不必强迫每个示例都经过 UI。
- 当设置代码的杂音掩盖了测试意图时,使用构建器/工厂/辅助函数;当自定义辅助函数变成一个小型 DSL 时,为其编写测试。
- 优先使用面向用户的/公开接口,而非内部实现。
- 固定非确定性因素:时间、随机性、网络、文件系统、顺序。
- 有意识地选择测试值:包含不同于默认值的独特值,确保至少一个用例能够暴露被丢弃、被设为默认值或被交换的参数。同时,在零值、空值和相等值属于边界情况或契约内容时,也要覆盖这些情况。预期结果应独立于被测系统(SUT)得出:简单示例用字面量最清晰,较广泛的情况则适合使用属性或真正独立的参考模型。不要用 SUT 自身的逻辑或常量重新计算预期结果,否则测试可能与它共享同一个 Bug,却仍然通过。
- 先运行最相关的测试,再在可行时运行范围更广的检查。
对于转换或复杂的生成输出,使用黄金文件,并明确执行审查流程;参见 references/golden-file-testing.md。对于外部 API,优先使用录制的真实夹具/录制回放或契约检查,而非在 CI 中调用实时服务;参见 references/vcr-cassettes.md。
评估模式
按严重程度报告证据,并列出正面观察。对于生成式测试,在建议针对特定引擎进行更改之前,先加载相关语言和主题参考资料。检查:
- 是否在运行,以及它是否可能失败? 对于每个层级:运行它的任务、它在默认分支上的上次结果、受门禁控制的测试是否可达,以及每个门禁是否可能变红(
|| true、continue-on-error、基线更新器、skip-green、空目标)。失效的门禁是首要发现。参见references/gate-integrity.md。 - 破坏测试/虚假信心:跳过或聚焦的测试、没有断言、只记录日志而不做断言、已注释掉的断言、恒为真的断言。
- 判定依据的强度:唯一断言过弱、缺少负向/错误/状态/结构断言、同义反复。对于属性测试,如果存在语义不变量,那么仅验证不会崩溃的判定依据就很弱。
- 模拟与现实脱节:硬编码的模拟无法发现真实 API/模式变更。
- 层级完整性:单元测试访问真实网络;集成测试模拟了它们声称要测试的所有边界;端到端测试模拟被测系统或通过测试外观驱动系统;PR 套件面向生产环境;以及被测产物是否就是部署的产物。
- 确定性:休眠、墙上时钟时间、未设种子的随机性、顺序依赖、全局状态泄漏。
- 覆盖质量:分支覆盖率;对于覆盖率很高但仍有缺陷漏出的测试套件,在关键路径上植入故障,看看是否有测试会失败,并从成本最低的方式开始(
references/gate-integrity.md§5)。不默认建议设置常设变异测试流程。 - 不变量放置位置:本应由类型/模式/契约承担,却被重复进行的内部验证。
- 生命周期匹配度:一次性探测被过度测试、可复用资产测试不足,或仅涉及结构的更改却进行行为测试表演。
- 示例质量:业务示例埋在 UI 脚本中、测试夹具难以阅读,或辅助工具组成了一种 DSL,却没有自己的测试。
- 生成式证据:所配置的运行器是否会触达生产路径、生成器是否覆盖声称的领域、判定依据是否独立,以及种子重放是否被误称为主动发现。若存在多个运行器、项目、筛选器或重复的目标列表,导致可达性不明确,请核实确切的 CI 配置;仅在风险反复出现时添加永久漂移保护。
- 测试代码可读性(DAMP):测试主体中的逻辑(循环/分支/计算得出的预期)、从 SUT 自身常量推导出的预期、在远离相关断言的位置被修改的共享测试夹具。参见
references/antipatterns.md。 - 测试套件结构:统计每个层级的测试数量;指出倒金字塔/冰淇淋筒(端到端测试占多数,单元测试基础薄弱)以及沙漏形结构(单元测试 + 端到端测试,中间的集成测试为空)。对于每个有更低层级归属的端到端测试用例,建议将其移至最低层级——业务规则应下沉到 UI 之下——同时保留少量覆盖黄金路径/关键旅程的端到端测试作为核心。
升级模式
按风险优先处理:
- P0:安全性、身份验证、注入和清理器测试中的断言较弱或缺失。
- P1:只断言非空、非空字符串或真值的测试。
- P2:不稳定的测试:休眠、实时网络、墙上时钟、顺序耦合。在修补等待逻辑之前,先询问能否在更小的 SUT 中运行测试——大小(依赖项、进程、RAM 占用)比工具选择更能预测不稳定性——并将自动重试/隔离视为掩盖债务,而非修复。
- P3:依赖大量模拟对象的“集成”测试,以及与实现耦合的模拟对象。
- P4:夹具噪声、重复内容、名称不清晰、缺少构建器。
常见升级方式:
- 将
sleep()替换为虚拟时间、注入时钟或基于条件的等待。 - 将实时 API 调用替换为录制的磁带或已提交的夹具。
- 将宽泛的模拟对象替换为伪造对象、契约测试或针对公开行为的断言。
- 将重复的对象字面量替换为构建器,并明确保留与行为相关的字段。
绝不悄悄降低测试等级:按等级列出已删除或替换的测试;除非有同级或更高级别的测试替代,否则保留真实引擎测试(SQLite、workerd、浏览器)。只有检查能够强制执行的教训或事后复盘才算数。
检测模式
使用 references/antipatterns.md 中的具体搜索信号:
@skip、skip、skipif(没有真实条件时)、test.only、fit、xit、xdescribe。- 使用
print、console.log、t.Log、Debug.WriteLine代替断言。 - 无断言的测试,或只检查真值/非空字符串/非空值的测试。
- 没有独立预言的属性测试、生成器被筛选为单一简单路径的测试,或对可穷举的小型有限域进行采样的测试。
- 用于同步的
sleep、waitForTimeout、Thread.Sleep、Task.Delay。 - 吞掉异常的 try/catch。
- 与断言完全相同的模拟返回值。
- 修改全局状态/环境变量/注册表却未清理。
- 更新快照/黄金文件却不检查差异。
- 使用
|| true、continue-on-error、scan --baseline或::warning::跳过步骤的 CI 步骤;从不运行的覆盖率或变异测试阈值。 - 将变异测试作为程序运行:在代码未更改时按日历安排、使用整轮数字下限,或仅为消除残留变异而导出或固定内部实现。
- 只在可能为空的结果循环中进行断言;没有非空检查的
violations == [];未从被测代码导入任何内容的测试。 - 将源代码、CSS 或工作流文本读入并作为字符串断言;辅助层级的重试循环;调高全局超时,或使用
isCI ? a : b设置时限。
验证循环
编写或修改测试后:
- 运行最近的相关测试命令。
- 如果计划采用修复 Bug 的 TDD,请分别报告红色阶段和绿色阶段的证据;如果未观察到修复前的失败运行,应将红色阶段标记为未验证,而不是声称已完成 TDD。
- 检查改动的测试中是否存在脆弱的单一断言、跳过/聚焦标记、只记录日志却不做断言、休眠、实时网络访问,以及与实现细节的耦合。还要检查:预期结果是否来自独立于 SUT 的判定依据,至少一个测试值是否能暴露被丢弃、设为默认值或互换的输入,是否仍覆盖相关边界值,以及断言是否仅针对行为涉及的字段。
- 对安全或转换测试,验证拒绝/移除行为和保留行为。
- 对不变量相关工作,在适用时验证两种策略:属性/不变量证明和无效状态的可达性。
- 对属性/模糊测试,确认所声称的输入域、独立判定依据、引擎原生重放,以及在配置不确定时,使用确切 CI 命令进行收集或目标选择。
- 根据退出状态判断通过或失败,而非根据输出文本。运行确切的 CI 入口,而不是子模式;使用其标签编译你修改过的、由标签控制的文件。
- 修复 CI 时,将“更新过时的选择器”与“弱化断言或延长超时”分开处理,并说明每项弱化的理由。只有在干净检出基线提交后复现失败,才能称其为已有失败。
- 如果验证受阻,请报告确切命令、失败情况和次优检查方案。切勿声称测试已通过,除非确实运行过;说明绿色结果来自哪个环境,并在存在已推送提交时检查其 CI。
最终报告要求
测试工作结束时,使用:
Tests changed/assessed:
Behavior covered:
Commands run:
Results:
CI status (ran / red / not running):
TDD evidence (if claimed):
Gaps / risks:
Follow-ups:
保持简洁,同时确保验证情况和剩余风险可供审查。