关于 test-lens
test-lens 是一项评估测试价值与维护成本的技能。它帮助开发者选择测试方式、审视现有测试,并依据四项指标提出具体改进。
主要功能
- 01按四项质量指标评估测试
- 02优先验证可观察行为
- 03指导测试方式及模拟边界
- 04识别反模式和重构需求
- 05提供文件与行号定位的发现
适用场景
- 01检查测试的脆弱性与价值
- 02为新代码选择单元或集成测试
- 03改进有状态或异步变更的测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ robsonrung/ rar-skills --skill test-lens安装后在 Claude Code 中输入/test-lens确认已加载。
附带文件references/decision-trees.md
详细介绍
Test Lens — 有价值的单元测试
选择模型前,先选择分支。协调工作和确定性命令均在本次调用的权限范围内进行。在当前角色中应用此方法。它不会分派工作者。若要分派工作者,请使用 shared/references/model-preview.md 及其中唯一获批的路由快照。嵌套技能和运行器会使用该快照,无需再次选择或分派工作者。
通过 Vladimir Khorikov 的《Unit Testing: Principles, Practices, and Patterns》中的框架来评估或编写测试。这不是红绿循环(节奏控制请使用 tdd),也不是漏洞排查(那属于代码审查环节)。它只回答一个问题:这个测试是否值得维护成本?它是否以正确的方式测试了正确的内容?
测试套件的目标是实现项目的可持续增长——让你可以放心地添加功能和重构。少量高价值测试胜过大量平庸测试。**测试代码同样是负担。**设定高标准;删除达不到标准的测试。
以下示例使用 TypeScript + Jest。应用此视角后,具体报告发现:引用 file:line,指出规则,并提出修复建议。如果测试已经写得很好,就直截了当地说明。
核心评估标准:四大支柱
每项测试的四个属性评分均为 0–1;价值 = 四项评分相乘。任何一项为零,测试就毫无价值——即使其他三项都完美。
- 防止回归——测试覆盖了多少代码(你编写的代码 + 库),并根据复杂度和领域重要性加权。简单代码 → 防护作用低。
- 抗重构能力——产生误报(行为正常却导致测试失败)的可能性有多低。这完全取决于测试与实现细节的耦合程度。不可妥协。
- 反馈速度——运行速度有多快。
- 可维护性——阅读和运行测试有多难(包括准备工作、进程外依赖)。
关键权衡:前三项无法同时最大化。由于抗重构能力必须保持较高水平,你只能在防护能力 ⇄ 反馈速度之间权衡。三种经典失败模式各自最多只能满足两大支柱,同时让第三项归零:
| 测试类型 | 牺牲项 | 价值低的原因 |
|---|---|---|
| 仅端到端测试 | 反馈速度 | 覆盖面广且稳定,但速度太慢,不能作为整个测试套件 |
琐碎测试(对普通 getter 执行 expect(user.name).toBe('John')) | 防护能力 | 没有什么可捕获的 |
| 脆弱测试(断言 SQL 字符串 / 调用顺序 / 私有结构) | 抗重构能力 | 每次重构都会失败 → 被忽略 → 真正的 Bug 趁乱溜进生产环境 |
脆弱测试最危险。它们会让团队习惯于忽略失败,随后真正的回归问题就会伴随着噪声一起进入生产环境。
视角 1 — 可观察行为,而非实现细节
这是最重要的一条规则。误报源于测试耦合于代码的_实现方式_,而非代码的_产出结果_。验证对领域专家 / 最终用户有意义的最终结果——不必理会实现结果所采用的步骤。
危险信号(都会与实现耦合):
- 断言调用了哪些内部方法、调用顺序或次数(基于存根)。
- 断言生成的 SQL 字符串、内部协作者的列表 / 类型,或私有状态。
- 测试逐步复刻生产代码的算法(“泄漏领域知识”)。
- 每次重命名私有方法或调整内部结构时都必须修改的测试。
好的测试会讲述一个关于问题领域的故事。如果测试失败,说明故事与代码不一致——这才是唯一值得你关注的失败。
视角 2 — 选择合适的风格(按优先级排序)
| 风格 | 验证内容 | 质量 | 优先考虑的情况 |
|---|---|---|---|
| 基于输出 | 纯函数的返回值 | 最佳——天然不易受重构影响 | 只要代码允许,就始终优先采用 |
| 基于状态 | 操作后的对象/系统状态 | 尚可——可能与状态泄漏有关 | 实际结果就是状态变更时 |
| 基于交互 | 与协作者的调用(模拟对象) | 最脆弱 | 仅用于系统边界处未托管的依赖 |
通过将决策与操作分开,推动代码采用基于输出的方式:用函数式核心(纯业务逻辑,易于通过输出测试)包裹一个可变外壳(精简,只负责 I/O)。这是将六边形架构推向极致的做法。参见 references/decision-trees.md。
视角 3 — 模拟对象:桩与模拟对象、托管与未托管
这是 Agent 最容易弄错的规则。完整流程图见 references/decision-trees.md。
- 桩 = 模拟_传入_的交互(SUT 读取的输入数据)。绝不要断言对桩的调用——这是过度规定,也是脆弱测试的首要来源。
- 模拟对象 = 模拟_传出_的交互(SUT 引发的副作用)。只有当该副作用本身就是可观察结果时,才可以断言它。
- 托管依赖(你完全控制它;只有你的应用会访问它——例如你的数据库):交互属于实现细节 → 在集成测试中使用真实依赖。
- 未托管依赖(可从外部观察——SMTP、消息总线、第三方 API):交互属于可观察行为/契约 → 模拟它。
- 只在系统边界处模拟,且只模拟自有类型——用你自己的适配器封装第三方 SDK,并模拟适配器,而不是模拟库本身。
视角 4 — 究竟该测试什么(代码象限)
根据两个维度对被测代码分类——复杂度/领域重要性 × 协作者数量:
| 协作者少 | 协作者多 | |
|---|---|---|
| 复杂度/重要性高 | 领域模型/算法 → 重点进行单元测试(投资回报率最高) | 过于复杂 → 重构:拆分成下面两类 |
| 低 | 简单无足轻重 → 不要测试 | 控制器 → 编写少量集成测试 |
代码越重要或越复杂,应该拥有的协作者越少。
过于复杂的象限(例如,一个臃肿的控制器既执行业务逻辑_又_处理 I/O)就是陷阱。使用谦逊对象模式拆分它:将逻辑提取到纯算法/领域类中(对其进行基于输出的单元测试),留下一个精简的谦逊包装器,只负责连接依赖(通过集成测试少量覆盖)。100% 覆盖率不是目标——覆盖率高但测试毫无价值是完全可能的(反之亦然);每项测试能带来的实际价值才重要。没有测试也好过糟糕的测试。
反模式检查清单(标记这些问题)
- 测试私有方法 → 执行复杂工作的私有方法说明缺少抽象;应提取一个类。
- 暴露私有状态只为对其进行断言 → 改为测试可观察行为。
- 泄漏领域知识 → 测试硬编码算法预期的中间计算结果。
- 污染代码 → 在生产代码中加入仅供测试使用的开关/标志。
- 模拟具体类以保留其部分行为 → 违反单一职责原则;应拆分类。
- 将时间作为隐式上下文(直接调用
Date.now())→ 将时间作为显式依赖注入。 - 断言与桩的交互,或把追求某个覆盖率数字当作目标。
编写测试时的原则
- AAA — Arrange / Act / Assert,各执行一次;如果需要多个 Act 部分,那它很可能是集成测试。测试中不要使用
if语句(拆分测试)。对于一个行为单元,Act 部分应保持为一行。 - 命名 测试时,应将其写成描述行为的领域语句 —
delivery_with_a_past_date_is_invalid,而不是IsValid_PastDate_ReturnsFalse。标题中不要出现方法名;非程序员也应该能读懂。 - 使用 参数化 处理相似案例(
it.each)— 但不同的行为应分别作为独立测试。 - 通过 工厂函数 重用固定数据,而不是在
beforeEach中使用共享的可变状态(测试间高耦合是一种反模式)。
工作流
- 这是哪类测试? 新代码 → 编写前先引导选择风格(Lens 2/4)。审查现有测试 → 为其评分。
- 评估四大支柱。 指出最弱的一项。如果任何一项约为 0,建议重写或删除。
- 应用适用的视角:可观察行为(始终适用)、模拟规则(存在测试替身时适用)、象限(决定是否以及测试什么时适用)。
- 检查反模式清单。
- 具体报告每项发现:
file:line→ 对应规则 → 修复方法。跳过不适用的视角,不要为了凑内容而添加。
有关详细的决策树(风格迁移、模拟流程图、Humble Object 重构、集成测试规则 — 路径选择、日志记录、测试金字塔 — 以及包含前后代码示例的完整反模式目录),请阅读 references/decision-trees.md。
失败序列与固定数据边界
对于有状态或异步变更,应在首次审查前,根据实际状态机推导一个小型案例表。纳入有意义的事件顺序和需要保留的不变量,而不只是逐个覆盖每个处理程序。如果相关代码片段存在相同的因果路径,应将之前的缺陷案例纳入其中。编辑器取消、替换、作用域变更或延迟响应适用时,请阅读 advanced-react 的异步参考资料。
通过固定数据的测试只能证明其所覆盖的边界。使用有代表性的存储值检查生产适配器;如果架构允许,还应包含缺失、旧版和格式错误的值。有关锁和回滚的验证应使用实际事务。不要让固定数据构造出比生产环境更严格的架构或更干净的数据契约。针对错误数据的默认值或回退行为必须遵循产品契约。
在验收报告中分别列出模型测试、DOM 交互测试、部分应用浏览器检查和完整应用检查。DOM 测试或截图无法证明已部署环境中的授权情况。测试数量无法证明测试覆盖完整。