关于 test-writing
面向开发者和编码 Agent 的测试技能,帮助选择可靠且成本较低的验证边界。重点是围绕行为编写测试、保持环境可控、让失败原因明确,并完成项目要求的检查。
主要功能
- 01根据复杂度所在位置选择测试范围
- 02优先采用可控且低成本的测试环境
- 03测试行为而非实现结构
- 04条件允许时使用真实本地依赖
- 05让失败原因集中且可观察
适用场景
- 01为涉及领域逻辑的改动选择测试方式
- 02在本地验证数据库或文件系统行为
- 03围绕服务边界规划集成测试
- 04检查脆弱或误导性的断言
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wilbeibi/ wilbeibi-skills --skill test-writing安装后在 Claude Code 中输入/test-writing确认已加载。
附带文件REFERENCE.md
详细介绍
test-writing
通过最宽泛且仍保持环境隔离、易于搭建的边界来测试行为。
根据改动选择适当的验证方式,并完成项目要求的检查。 不要仅仅为了遵循偏好的测试风格而扩大生产范围。
需要改正的习惯
- 根据刚写的代码编写测试。 阅读实现并断言其行为,会把其中的缺陷也固化下来。应从需求推导预期值,并手动计算。如果不运行代码就无法说出预期结果,那你还没有充分理解相关行为,暂时无法对其进行测试。
- 把实际输出复制到预期结果中。 运行测试,看到它失败,然后把实际值粘贴进去,得到的是记录,而不是测试。把容差不断放宽直到测试通过,也是一样。
- 照搬代码结构。 每个源文件对应一个测试文件、每个公有方法对应一个测试,这是一套以代码为单位的测试集。应以行为为单位进行测试;一个行为由多少个类实现并不重要。
- 默认使用模拟。 如果真实的本地依赖既便宜又能确保结果一致,就优先使用它。在外部边界使用伪对象或模拟对象,前提是它能提供可靠的信号;仅仅因为存在模拟对象,并不足以成为重新设计的理由。
- 修测试,而不是修代码。 在证实之前,失败的测试只是假设代码有问题。绝不要为了让测试变绿而弱化断言、添加模拟对象或跳过测试。在同一个提交中修改测试及其覆盖的代码时,必须说明两者为何都有问题。
- 修改环境来让测试变绿。 手动插入一行数据、切换标志、重启服务或清空队列,都是部署环境层面的弱化断言——测试通过了,问题却没解决,而且共享状态也因此发生了偏移,影响所有其他人。
- 在共享环境中盲目试错调试。 共享环境中的失败运行本来就很难判断原因:可能是你的改动、别人的改动、过期数据,也可能是环境损坏。先在本地通过环境隔离的测试复现;如果做不到,就如实说明,而不是猜测。
- 盲目接受快照。 不查看差异就运行更新模式,会把 expect-test 循环变成自动批准循环。未审核的快照更新应视为未经测试的改动。
- 堆砌测试。 廉价的测试会变成近乎重复的测试,既会一起失败,又会掩盖真正的故障。覆盖率百分比和测试数量都不是目标。
- 掩盖失败。 避免使用重试或异常处理来让损坏的行为显示为通过。对于异步行为,可以设置有界等待和超时,但失败必须保持清晰可见。
- 测试框架本身。 断言 ORM 能保存数据或标准库能排序,实际上是在测试别人的代码。
在 Agent 循环中,测试集不是安全网,而是正在被优化的验收标准。循环论证式的测试集不仅会漏掉缺陷,还会把工作引向错误的方向。
路由
首先要弄清复杂性实际存在于哪里,因为这决定了整套测试的形态:
- 在进程内部 — 算法、领域规则、解析器。在领域层深入测试;边界测试只能证明接线正确。
- 在边界处 — 服务、胶水代码、编排。契约就是产品,因此要从边缘开始,使用贴近实际的固定数据,并将实现细节测试限制在少数真正复杂且隔离的部分。
然后:
- 简单的胶水代码/getter?跳过 — 不可能失败的测试只会给故障信号带来噪声。
- 纯逻辑、依赖很少?通过公共 API 测试。优先选择属性测试或穷举测试,而不是手工挑选示例。
- 编排、真实 I/O、控制器/数据库/文件系统行为?做集成测试 — 单独测试模块或服务,让它自己的依赖保持真实,在边缘伪造协作者。
- 既复杂又依赖繁多?提取领域逻辑并测试它;保留一个精简的编排器用于集成测试。
- 部署、接线、真实第三方行为,或负载下的基础设施?只有这些情况需要部署环境。优先采用临时环境(每个 PR 一个环境、compose 文件),而不是共享的预发布环境。
逐步扩大边界,直到准备工作变得昂贵或测试不再是纯隔离的,然后停止。I/O 不是判断标准 — 回环 HTTP 和进程内 SQLite 的成本很低,而共享远程数据库则不然。真正带来成本的是准备时间、资源占用,以及任何你无法控制的东西。
规则
- 常规测试应独立于共享外部服务。验证真实外部契约时,可明确使用集成测试或端到端检查;并将环境故障单独报告。
- 故障必须能定位缺陷。只说明“系统中有问题”的失败结果几乎毫无价值,因为下一步只能猜。
- 一个测试 = 一个领域专家能够识别的场景。按行为命名,而不是按方法命名。
- 优先使用基于输出的测试,其次是基于状态的测试;仅在对兼容性敏感的外部调用中使用通信模拟。
- 对非托管依赖优先使用伪造对象;条件允许时,使用真实的本地数据库/文件系统。让边界与被测行为相匹配。
- 对依赖时间的行为,优先注入时钟或使用项目既有的假计时器机制。等待异步工作完成,或通过有时限的公共信号观察其完成情况。
- 准备、执行、断言。执行阶段只做一个操作。断言范围要窄,以便故障只指向一个原因。
- 将准备工作限定在需要它的测试中。类级共享固定数据会拖累每次单独的测试运行,而这正是你调试时要进行的运行。
- 对缓存命中或回退使用等行为,优先利用现有的公共可观测性。仅当所需契约需要时才添加检测,不要只为断言某个实现分支而添加。
危险信号
- 预期值是根据实际输出反推出来的,而不是依据需求确定的。
- 测试复制了生产逻辑,而不是将答案硬编码。
- 测试会因重构而失败,因为它检查了内部实现、私有方法、调用顺序或类布局。
- 测试必须依赖多个服务运行,或依赖共享环境处于健康状态才能通过。
- 数据库/文件系统行为被模拟掉。
- 使用无上限的休眠/重试,或会抑制故障的条件判断。
- 测试与它覆盖的代码在同一个提交中被修改,却没有任何解释。
质量标准
判断测试是否合格,要看它能否提供可信的验证信号:行为出错时会失败,重构后仍能通过,并且失败时能明确指出一个显而易见的原因。抗重构能力最为重要 — 它决定了整套测试通过究竟意味着什么。锁定错误结构的测试比没有测试更糟,因为人们会相信它。
关于测试规模、金字塔/蜂窝模型的选择、check() 接缝、可观测性节点、期望测试、属性测试与模糊测试、谦逊对象重构以及数据库模式,请参阅 REFERENCE.md。
评论
支持 Markdown