Testing
面向开发者的 Claude Code skills 套件 | A developer-focused Claude Code skills suite
npx -y skills add Lion-1209/Lion-Skills --skill testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
需为代码写测试或改进脆弱测试时。
SKILL.md
9.4 KB, as published. Nobody here has run it
Testing
概述
写能真正抓住 bug、且不脆弱(改实现不会无端崩)的测试。核心:测试是行为的契约,不是"代码的镜像"——测的是"给它这个输入,该有这个结果",不是"它内部该这样工作"。好测试让你敢改实现(因为测试守着行为),坏测试让你不敢改(因为一改测试就崩)。
何时使用
- 写完功能,要补测试
- 写测试时纠结测什么、不测什么、要不要 mock
- 现有测试脆弱(改实现就一片红)或 flaky(时过时不过)
- 测试覆盖率挺高但 bug 还是漏
不该用:纯探索/原型(不要求正确,测试是负担);一次性脚本(跑完即弃)。
与相邻 skill 的衔接:testing 在 task-breakdown 下游、verify-and-fix 上游——task 拆出"做完 X 能验证 Y",testing 负责把 Y 写成可重复运行的测试,verify-and-fix 负责跑它验证。三者接力:task 定验证目标 → testing 把目标落地成测试 → verify-and-fix 用测试验证完成。
核心内容
先识别被测对象的性质
写测试前先看清"被测的是什么",策略大不相同——这决定了要不要 mock、用什么工具、放哪个层次:
- 纯函数(无副作用、输入决定输出,如
calculateDiscount)→ 直接输入输出断言,零 mock,单元层。 - 有外部依赖的逻辑(如调 DB/HTTP 的 service)→ mock 掉外部副作用,验证被测逻辑对依赖返回值的真实处理(详见下文 mock 纪律)。
- UI 组件(渲染 + 交互)→ 用组件测试库测渲染输出和用户交互,不测内部 state 细节。
- 模块协作(多个组件配合)→ 集成层,用真实(或内存版)依赖验证组件间契约。
识别性质能避免最常见的错配:给纯函数上 mock、给 UI 组件测 state、把单元能测的逻辑推到端到端。先问"它是什么",再问"怎么测"。
测行为,不测实现
这是测试设计的第一原则。测"做什么",不测"怎么做":
- 测行为(对):给定输入,断言输出/可观测结果。例:
calculateDiscount(100, 'vip')应返回80。 - 测实现(错):断言内部走了哪个分支、调了哪个方法几次。例:断言"内部调用了
multiply两次"。
为什么测实现糟糕:实现是会变的(重构、换算法、优化),但行为不该变。测实现的测试,每次合理的实现改动都会让它崩——这就是脆弱测试。它逼你改实现时还得改测试,让测试从"保护"变成"负担"。
判别尺子:问自己"如果我把内部实现整个换掉(但行为不变),这个测试还该过吗?" 该过 → 测的是行为(对);崩了 → 测的是实现(错,改)。
例外:有些"交互契约"本身就是行为——比如"调支付时确实发了请求""保存时确实写库了"。这类"验证发生了正确的外部交互"是测行为,不是测实现。区分点:你关心的是结果(钱扣了/数据存了),还是调用细节(调了 3 次不是 2 次)。前者是行为,后者是过度断言。
测什么:聚焦有判断的逻辑,跳过无价值的
不是每行代码都值得测。测试有价值,是因为代码有逻辑、可能错。按代码性质分:
- 有判断的逻辑(分支、计算、状态转换、边界处理)→ 重点测。这是 bug 高发区。
- 纯数据搬运(getter/setter、直接赋值、简单透传)→ 不值得专门测。测它等于测语言本身。
- 框架/库的代码 → 不测。你不需要测 ORM 的 save 有没有存数据库,那是框架的事。
判断尺子:这段代码如果写错了,测试能抓住吗?写对了,测试有信息量吗? 两问都否 → 不值得测(如 getter)。把测试预算投到"写错会出事"的地方。
边界和错误路径是重点:happy path 谁都会测,但 bug 大多藏在边界(空值、零、负数、空集合、最大值)和错误路径(异常、超时、依赖失败)。问自己"这个函数在什么输入下会出错?"——那些输入就是要补的测试。
mock 的纪律:隔离依赖,不隔离被测逻辑
mock 用来隔离外部依赖(数据库、网络、第三方服务、时间),让测试快、稳、可重复。但 mock 容易被滥用:
- 合理 mock:被测代码依赖的外部副作用(真连库太慢、真发邮件会骚扰人)。mock 掉它们,专注测被测逻辑。
- 过度 mock:把被测对象自己的依赖链也 mock 掉,导致测试退化成"测 mock"——你 mock 了 db.save 返回固定 id,又只断言"调了 save",那其实什么都没测。
判断尺子:mock 之后,被测对象的真实逻辑还在被验证吗? 还在(你对 mock 的返回做了真实处理和断言)→ 合理;不在(只是验证"调了 mock")→ 过度,测试失去意义。
mock 的使用原则:
- mock 边界,不 mock 内部——mock 系统边缘的依赖(DB、HTTP),不 mock 被测代码内部的辅助函数
- mock 行为,记录交互——mock 要表达"依赖应该怎么响应",而非"我猜被测会怎么调它"
- 少 mock——能用真实组件(如内存数据库、内存文件系统)就别 mock,真实 > mock
一个测试一件事
每个测试函数聚焦一个可验证的行为点,断言精简:
- 差:一个测试塞 10 个断言、测多个场景——失败时不知道哪条挂、改一条得动整个测试、名字没法概括(叫
testEverything)。 - 好:一个测试一个明确的断言点,名字就是行为描述(
discountsVipBy20Percent、returnsOriginalPriceForUnknownLevel)。
好名字的价值:测试失败时,名字直接告诉你哪个行为坏了,不用读测试代码。test1 failed 让你去看代码,discountsVipBy20Percent failed 直接定位问题。
测试金字塔:层次分明,多测便宜的
测试分三层,数量比例应是金字塔——底层多、顶层少,因为越往上越慢、越脆、越贵:
- 单元测试(底层,最多):测单个函数/类的行为,无外部依赖(依赖被 mock 或用真实轻量组件),毫秒级、跑得快。占绝大多数。
- 集成测试(中层,适量):测几个模块协作(如 service + 真实内存数据库),验证组件间契约。比单元慢,但比端到端快。
- 端到端测试(顶层,最少):测整条用户路径(如"从点击下单到支付成功"),最真实但也最慢、最脆(依赖多、易 flaky)。
常见误用:倒金字塔——端到端多、单元少。结果是测试套件又慢又脆,改一处一片红。判断尺子:**这个测试到底在测什么?**测纯逻辑 → 单元;测模块协作 → 集成;测用户能完成目标 → 端到端。能用单元测的别上集成,能用集成的别上端到端。
测试发现疑似 bug:先报告,别擅自当"行为"固化
写测试时常常发现代码行为可疑(如"负价居然照常打折""非数值返回 NaN")。这时别擅自决定——有两种情况:
- 该测的:这是预期的当前行为(哪怕怪)→ 写成测试契约化它,防止未来无意改动。
- 该报告的:这是潜在 bug(行为不符合预期)→ 别急着写测试固化一个错误行为,先报告给代码作者/需求方确认。确认是 bug 就先修代码再写测试;确认是预期再固化。
判别尺子:问"这个行为符合需求/常理吗?" 符合(哪怕反直觉)→ 固化;不符合 → 报告,别固化 bug。把 bug 固化成"通过的测试"是最危险的——它给错误行为盖了"已验证"的章,未来谁想修都会被这个测试挡住。
测试结构:Arrange-Act-Assert
每个测试用三段结构,清晰可读:
// Arrange:准备输入和依赖
const service = new OrderService(mockDb);
// Act:执行被测行为
const result = await service.create(order);
// Assert:断言结果(行为)
expect(result.id).toBeDefined();
expect(mockDb.save).toHaveBeenCalledWith(order); // 交互契约
三段分离让测试一眼能读懂"测了什么"。混在一起(准备、执行、断言交织)是测试难读、难维护的信号。
常见错误
| 问题 | 修法 |
|---|---|
| 测实现细节(断言内部调用次数/分支) | 改测行为,问"换实现行为不变,测试还该过吗" |
| mock 掉被测逻辑,退化成测 mock | mock 只隔离外部依赖,被测对象的真实逻辑仍要被验证 |
| 一个测试塞一堆断言 | 一个测试一个行为点,名字描述行为 |
| 测 getter/setter、测框架本身 | 把测试投到有判断的逻辑,跳过无价值的目标 |
| 只测 happy path | 重点补边界(空/零/负/空集合)和错误路径 |
| 追求覆盖率数字而非有效测试 | 覆盖率是必要不充分,从"什么 bug 漏了"反推该测什么 |
| 测试 flaky(时过时不过) | 多半是隐式依赖(时间/随机/顺序/共享状态),排查并隔离 |
| 测试层次倒金字塔(端到端多、单元少) | 金字塔分布:单元最多、集成适量、端到端最少;能下层测的别上上层 |
| 把发现的 bug 固化成"通过的测试" | 先报告确认——是 bug 先修代码再写测试,是预期行为再固化 |