关于 testing-craft
testing-craft 是面向开发者的测试技能,按能力将行为交给最合适的证明或测试层,并聚焦于这些层无法证明的部分。
主要功能
- 01按层分配不变量的验证责任
- 02依据行为和故障模式选择测试
- 03使用类型化替身和真实协作者
- 04保持测试套件精简且确定
- 05检查回归敏感性并记录验证结果
适用场景
- 01规划代码变更的验证方案
- 02选择单元、集成或端到端测试
- 03审查测试能否发现实际缺陷
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jssblck/ agents --skill testing-craft安装后在 Claude Code 中输入/testing-craft确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamllanguages/go.mdpython.mdrust.mdtypescript.mdreferences/episodes.md详细介绍
测试工艺
测试只负责证明没有更强层级能够证明的内容。每条不变量都由一个层级负责:code-craft 证明阶梯中实际能够证明它的最高层级。顺序为:类型和 lint 工具、效应、证明、数据库约束、单元测试、集成测试、端到端测试。尽可能让错误情况无法通过编译或构造,然后在能够证明剩余内容的最高测试层级进行测试,并尽量减少测试数量,只保留足以支撑该证明的测试。
范围与验证
- 明确要更改的行为,以及每个新增或修改的测试应捕获的具体缺陷。测试不必检测无关缺陷。
- 测试新代码之前,列出其失败模式,并将每种模式交给能够负责它的最高层级。
- 优先遵循项目说明和现有测试惯例,而非这些默认规则。
- 运行受影响的测试和必要检查。如果更改的依赖、故障或未解决的风险有此必要,则扩大覆盖范围。
- 不要仅为了满足测试偏好而更改生产架构。为实现所请求的修复,需要时可以重构;独立的重构则应另行提出。
- 如果现有自动化已覆盖更改的行为,就使用它。手动测试在能捕获自动化无法发现的风险时很有用,例如视觉布局。
- 需要运行器或 fixture 指引时,只阅读下方相关语言的文件。设计指引位于
code-craft和database-craft中。
测试剩余内容
类型无法证明计算、解析边界情况、SQL、排序、时序、远程行为、渲染或进程间连线。对这些内容,使用能够证明它们的最高测试层级:
| 属性 | 层级 | 方法 |
|---|---|---|
| 纯计算:格式化、排序、差异比较、日期计算、解析器 | 单元 | 使用实际值直接调用 |
| 服务逻辑 | 单元 | 在声明的依赖项处使用带类型的替身 |
| 渲染出的控件 | 单元 | 使用真实组件、带类型的 props 和注入的服务 |
| 查询、存储库、约束、作业、迁移 | 集成 | 使用真实的 Postgres(database-craft) |
| 提供商 SDK 和传输 | 集成 | 使用真实客户端对接 Vercel Emulate |
| 原生模块、文件、进程 | 集成 | 使用真实代码、fixture SQLite、临时目录和一次性 Git 存储库 |
| 导航、身份验证、进程与平台间连线 | 端到端 | 使用少量确定性用例作为发布门禁 |
单元测试优于集成测试,集成测试优于端到端测试:每降低一个层级,测试就更慢、更容易产生噪声,且每次运行覆盖的输入更少。只有在更高层级无法验证时,才选择较低层级。
精简测试套件
- 不要测试类型、lint 规则、效应签名、证明或约束已经证明的内容。
- 每项属性只在一个层级证明。路由测试不重复其存储库的 SQL 用例;端到端流程不重复格式化函数的边界情况。契约测试套件是唯一有意设置的例外。
- 当更高层级接管某项测试的用例时,删除该测试并注明新的负责人。删除前先运行任何替代测试。
- 移动或删除覆盖范围时,保留仍需测试的所有权、并发、取消、无效输入和失败情况。
- 使用编译失败检查守护证明和所有权边界:这些检查通过断言类型错误,确保放宽证明类型会导致构建失败。不要为普通类型添加此类检查。
- 不要在测试中复述实现,也不要仅为测试而公开私有辅助函数。
双打
只有在更强的测试层级发挥作用后,双打才合适。类型固定依赖项的形状,效果固定它的要求和失败方式,证明固定它的前置条件,因此在单元测试中,类型化双打可以替代其行为。但双打无法证明真实依赖项仍按这种方式运行;这需要由冒烟测试覆盖。
- 基于真实接口进行类型检查。 编译器必须能拒绝形状不符的双打:根据服务接口构建双打,或将其声明为真实函数的类型。未类型化的双打(
as any、as unknown as)属于类型断言。 - 替换依赖项,而非被证明的对象。 测试 SQL 需要 Postgres,测试提供商的协议需要模拟器。调用它们的逻辑可以使用双打。绝不要双打被测单元。
- 优先使用现有接缝。 在尝试模块模拟(它会让测试依赖文件布局)之前,先通过服务标签提供测试实现,或传入类型化参数。
- 驱动已声明的失败。 类型化失败便于通过双打触发每种错误变体;覆盖调用方会处理的错误。
- 断言结果。 只有当调用本身就是契约时才断言调用,例如发送一封邮件、缓存命中时跳过网络请求,或在写入前检查授权。只断言相关调用及参数。
- 当伪实现承载行为时使用契约测试套件。 当共享伪实现模拟了许多测试所依赖的状态、分页或错误时,编写一套每种实现都必须满足的测试用例,并同时针对伪实现和真实实现运行。真实行为改变时,先修改契约。
冒烟测试是哨兵
每个集成点都至少保留一个针对真实对象的测试:Postgres、每个提供商(通过 Emulate 或提供商的沙盒)、每个进程边界,以及通过端到端测试覆盖的每个应用界面。覆盖主路径,以及只有真实对象才可能引发问题的部分:身份验证、序列化、传输。冒烟测试用于在集成点发生变化时失败;它不是单元测试用例的另一份副本。
真实协作者
在集成测试层级,使用真实存储:隔离的数据库实例、fixture SQLite、临时目录和一次性 Git 仓库。
对于提供商和服务 API,请使用
Vercel Emulate。优先使用内置提供商;对于不受支持的 API 或项目自身的服务,则使用自定义模拟器。通过项目的技能安装器将其上游 emulate 技能安装到仓库中,并在使用前阅读。在每个导入它的工作区中声明测试依赖项。
- 通过原生 HTTP 驱动真实 SDK 或客户端。通过现有依赖边界注入模拟器的 URL。保留标头、请求体和取消行为。
- 在自定义模拟器路由中,使用应用程序采用的同一套架构解码请求体,并保留可观察状态。在 HTTP 服务器后提供预设成功响应并不能证明集成有效。
- 为每个 fixture 提供独立状态和由操作系统分配的端口(
port: 0)。在场景之间重置或重新创建状态,并在失败后也等待清理完成。绝不要在并行工作树之间共享可变状态、固定端口或持久化文件。 - 通过服务边界测试相关失败。当授权、分页、重试和取消属于契约的一部分时,检查这些行为。
如果工具存在限制,应如实报告,而不是悄悄改用保真度更低的测试,并将其称为集成测试或端到端测试。
端到端测试是发布门槛
保留一组规模精简、结果确定的测试,驱动正在运行的应用、公共 CLI 或打包后的应用,并断言用户可观察到的结果,包括持久化效果。发布门槛只依据确定性的断言;模型评估可以解释失败,但绝不能作为门槛。仅测试渲染组件,无法证明导航、身份验证或原生平台连接正常。
避免镜像实现的测试
源码断言和精确的内部调用脚本往往会锁定实现,却无法保护其契约。对于可疑的测试,请明确:
- 它保护的行为。
- 会导致它失败的具体缺陷。
- 它是否会拒绝行为相同的有效实现。
将脆弱的断言改写为检查结果、状态、渲染输出或外部效果。如果某个测试无法发现更强一层测试未能发现的真实缺陷,就删除它。保留用于落实文档所述仓库规范的检查。
编写易读的测试
- 明确说明场景和预期结果。让因果关系一目了然。
- 在测试主体中保留相关输入和预期结果。辅助函数可以隐藏无关的构造过程;针对并行场景时,可以使用表驱动用例。
- 对一组用例使用同一个
check(input, expected)辅助函数,这样 API 变更时只需修改辅助函数,而不用改每个测试。预期输出较大或经常变化时,使用 inline snapshot(expect)测试。 - 不要通过重复生产算法来计算预期结果。
- 选择能暴露输入交换或意外默认值的不同值。在行为测试涉及空值、零值和边界值时,应将它们纳入测试。
- 断言重要字段。若整个对象就是契约,应使用完整对象相等性断言,而不是对偶然细节做宽泛的快照断言。
- 让失败信息显示预期值和实际值。
- 覆盖相关的失败路径和边界。只有在不变量或庞大的输入空间确有需要时才使用属性测试,不要仅仅因为有现成的库就使用。
保持测试确定性
- 等待完成信号或带超时的可观察条件;不要随意休眠一段时间并假定任务已完成。
- 如果时间和随机性会影响结果,就对其加以控制。
- 在测试之间隔离可变状态。
- 通过受控依赖项或输入,使重要失败可复现。
确认回归检测能力
修复 Bug 时,先在能展示问题的最强一层复现失败:能拒绝错误状态的类型,或失败的测试。确认失败原因正是预期原因,然后确认修复后测试通过。如果无法复现真实失败,请说明已有证据和仍存的不确定性。
对于其他变更,请明确断言能检测到的缺陷。如果检测能力不确定,或风险值得验证,请使用定向变异测试。如果修改生产代码,请隔离实验,并在继续之前还原代码。
反转断言只能检查代码是否执行,无法检查其对目标缺陷的检测能力。常规测试修改和从通过到通过的重构不需要修改生产代码。保留已覆盖的行为,并比较重构前后的相关结果。说明无法执行的验证。
留下可重复验证的证据
明确保证内容:哪些无效输入、状态或调用现在无法编译或构造,以及具体位置。对于每个保留的测试,说明它能捕获的缺陷及其所属层级。
每个端到端测试都应以可验证的产物收尾:准确的命令或可重放流程,以及生成的输出、截图或录制内容。使用项目规定的产物目录和发布规则。
迭代期间运行针对性场景,并在交付前通过所需的全部检查。只有在相关修改、失败或尚未解决的风险之后,才重复已通过的检查。更改慢速测试套件的执行方式前,先测量其耗时;优化运行时间时,保留隔离性和有效覆盖。
语言参考
需要了解运行器、固定装置或异步测试的细节时,请阅读相关文件:
其他语言请遵循项目约定。
来源
改编自 Google 的 Testing on the Toilet 系列,涵盖以行为为重点的测试、测试替身、DAMP 和 SMURF。源文章索引见 references/episodes.md,来源包括 shamashel/testing-on-the-toilet,以及 matklad 的 How to Test。