SuperAPI AI 工具集

writing-tests

测试与质量

查看项目

更新于 10-31 02:50

技能介绍

用途

本规范用于约束测试的编写、修改与评审,覆盖单元、集成、组件与端到端测试,也包括在实现功能或修复缺陷时新增的测试。其根本立场是:默认不新增测试。每条测试都会在每次 CI 运行中消耗时间,在每次变更中消耗代理 token,并在每次重构中带来长期摩擦,因此它必须通过捕获重要回归来证明自身价值。规范特别强调,一条正确且写得好的测试仍可能是净损失。

价值判断

写测试前先判断失败意味着什么:是代码出错,还是有人有意改变决策。若期望值直接抄自代码(类名列表、SQL 文本、映射表、调用顺序),那么只有当有人故意修改该代码时测试才会失败,而修复方式永远是同步修改测试,因此它捕获不了任何错误。若期望值来自需求(购物车应付金额、某租户不得看到某行),测试才可能真正暴露规则实现错误。

判断一条测试能否存在,需至少满足以下四项中的三项:

  1. 被破坏时代价高:涉及金额、点数、账单、支付或订阅状态、租户与归属隔离、认证与权限、数据丢失或完整性、向客户发送消息、外部契约(webhook、公开 API、持久化枚举、文件格式)、迁移、安全清洗等。
  2. 被破坏时不会被察觉:类型系统、其他已运行该代码的测试、正常使用都无法快速暴露问题。用户在界面上能直接看到的问题(布局、样式、文案、点击无反应、日期或颜色错误)不属于此类。
  3. 容易出错:存在分支、并发、时区、解析、状态机等非平凡逻辑,代码频繁变动,或曾经出过 bug。
  4. 保护成本低:速度快、setup 小、无需每个用例启动应用或登录,代码行数相对于所保护逻辑较少。

规范给出示例:一个 70 行的“业务时区中的今天”辅助函数测试,虽然正确、注释完善并覆盖真实 ICU 边界情况,但显示错误日期在界面上可见且修复成本低(第 1 项不满足),且已有其他测试运行该辅助函数(第 2 项不满足),四项中仅满足两项,因此不应存在。UI 同理:测试点击单选按钮标签会选中它、或预览保持其 CSS,即使没有其他测试覆盖,也不应编写。

例外:对于高代价行为(租户隔离、认证、金额、数据完整性)的唯一证明,即使不满足三项也应保留,必要时重写,包括已跳过或隔离的测试。但仅仅位于高代价代码中还不够,必须确认没有其他测试已经证明该行为。

编写前流程

先搜索:同目录测试、覆盖同一函数的其他文件、另一层级(单元、集成、e2e)。优先级依次为:在既有流程中增加断言、在既有表驱动测试中增加一行、在既有文件中增加一个用例、新建文件。

修复缺陷时,仅当测试在修复前失败且无既有测试能捕获时才添加回归测试;在所属层级写一条,不要每跨一层就写一条。

不得为测试添加生产代码切口:导出、forTest 钩子、阈值选项、可变字段、"if running under tests" 分支均不允许。

不得削弱、跳过或删除既有测试来让变更通过。测试失败意味着代码有误,除非需求已变更;此时应明确说明并有意修改测试。

禁止模式

规范要求无论价值门槛如何判断,都不得编写以下模式:

  • TYPE:类型系统已保证的内容,如“是函数”“最后断言为已定义”、对类型化对象断言“有属性 X”、schema 含某键、ref 转发。
  • CONST:常量、配置、枚举、CSS 类、翻译键、提示词短语或标签映射等于(或包含)其自身字面量。注释“防止被删除”不能使其合法。
  • TAUT:通过构造即通过,如断言 mock 被设为返回的值、用实现自身规则重算期望值、把生产正则或 SQL 抄进测试、对纯函数断言“相同输入得到相同输出”。
  • CALLS:只断言内部函数被调用、调用次数或顺序;mock 代码库自身模块以提供被断言的值;断言日志行。
  • NOOP:标题承诺的行为在主体中从未执行,如仅渲染、无交互却断言“未被调用”、>= 0 计数、可能不执行的循环内断言、数据缺失时提前返回。
  • TRIVIAL:一行函数、getter、a && b、薄包装、属性回显、“超长文本可渲染”。
  • LIB:通过薄包装测试库或框架行为(UI 工具包键盘处理、表单库校验、简单 schema 的 schema 库、ORM 行为)。
  • SOURCE:读取源文件或仓库布局(“文件存在”、对代码做正则)。应改用 linter 规则或检查真实生成产物。
  • SNAP:整体输出快照,或对无害重写就会失败的精确生成 SQL/IR 文本。
  • DUP:同一路径已在别处被证明(同目录测试、其他文件、其他层级),或 N 个近乎相同的用例应合并为一张表。
  • LAYER:层级错误,如对数据库或路由行为做重度 mock 的单元测试,或对纯逻辑、静态文本做缓慢的端到端测试。
  • FLAKY:固定 sleep(包括“sleep 后断言什么都没发生”)、真实时钟、未播种随机、共享可变状态、顺序依赖。不得用重试掩盖。
  • DEAD:被跳过、带主体的 todo、被硬编码标志禁用,或依赖默认关闭的环境变量。

代理常照抄的模式与替代做法

规范列出代理容易照抄的低价值形态及替代方案:

  • CRUD 回显(创建后断言每个字段等于输入、id 已定义、时间戳是日期,再加“最小/全字段/长标题/特殊字符”网格):只测规则(校验、作用域、派生字段),普通持久化由任意读取即可证明。
  • 每个新列或选项一组用例(“保存 / null 清除 / 省略保留”):合并为一张跨列的表,或对透传列不测。
  • 每个方法都重复“归属错误 / 未找到 / 已删除”:每个服务一张隔离表。
  • 每个操作符、字段、提供方、渠道或文件类型一个文件或测试:一张表驱动测试配共享 fixture。
  • 在已创建 X 的“删除 X”测试旁再写“创建 X”测试:保留流程,去掉该步骤。
  • UI 组件的 Sizes / Variants / Ref forwarding / Combinations / Edge cases 套件:只测组件自身逻辑,纯展示包装不测。
  • 带十个 mock 并断言 mock 被调用的组件测试:抽出逻辑做单元测试,或端到端覆盖一次流程。
  • 手工假数据库或查询构造器:用真实测试数据库。
  • 端到端“创建 X,看到成功 toast”;每个“确认删除”都配“取消保留”孪生测试:重载后或通过另一视图读回结果。
  • 端到端重复证明单元测试已负责的数学:规则用单元表,接线最多一条端到端测试。
  • 同一辅助函数复制到多个应用、每份各带测试:共享代码,保留一条测试。

分层

  • 纯规则(金额、日期、解析、状态机):单元测试、表驱动、固定时钟。
  • 服务、查询、租户作用域、事件:真实服务配真实测试数据库。
  • 路由 schema、认证、HTTP 序列化:路由级 harness,每个文件启动一次应用。
  • 用户可见的组件逻辑:断言渲染结果的组件测试。
  • 跨 UI 与服务的关键旅程:一条端到端测试,断言持久化状态而非 toast。

不得在两个层级证明同一行为,除非第二层能增加第一层无法触达的传输、序列化或生命周期风险。

断言形态

断言结果:返回值、错误、数据库行、HTTP 响应、事件载荷、用户可见文本。只在外部边界(HTTP、支付提供方、队列、邮件)上断言调用,且使用代码计算出的参数。

否定断言需配正向锚点。证明“未发生”应通过 await 生产者或有界轮询,不得用 sleep。优先使用不变量与往返断言(总和匹配总额、parse(serialize(x)) 等于 x),而非魔法字面量。遍历真实来源,不要重新手打列表。使用固定时钟、播种随机、每个测试新建租户。覆盖率百分比永远不是添加测试的理由。

完成前检查

对每条新增或修改的测试,说明它满足哪三项以上门槛;无法说明的应删除。新回归测试需展示回退修复后失败、恢复后通过。报告新增测试行数与生产代码行数;测试行数多于生产行数需给出理由。