关于 test-generation
面向编码 Agent 的测试生成技能,可为现有代码或已修复缺陷编写项目框架适用的测试,并运行测试、指出未覆盖部分。重点在边界与错误路径的案例选择。
主要功能
- 01按项目测试框架生成测试
- 02覆盖边界与错误路径
- 03支持回归、单元、集成及 UI 测试
- 04运行测试并报告未测试部分
- 05依据技术栈选择测试位置
适用场景
- 01为新函数或模块补充测试
- 02为已修复缺陷编写回归测试
- 03确定低覆盖率模块接下来应测试什么
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jablonkai/ skills --skill test-generation安装后在 Claude Code 中输入/test-generation确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/flutter-dart.mdkotlin-kmp.mdother-languages.mdrust-cpp.mdswift-apple.mdtest-design.md详细介绍
test-generation
用途
将现有代码转化为真正能捕获故障的测试。输入可以是函数、类、模块或错误报告;输出则是使用项目自身的测试框架、放在正确源集中的测试文件。这些测试应当能够运行并通过,同时附上一段简短说明,指出仍未覆盖的部分。
价值在于选择测试用例,而不是敲代码。只覆盖正常流程的测试只能说明代码的行为,无法保护它。大多数真实缺陷都出现在边界条件和错误路径中,因此测试用例也应重点覆盖这些情况。
适用场景
- “为
<file/function/class>编写测试”——针对新代码或现有代码 - 刚刚修复了一个错误,需要添加回归测试,确保没有修复时测试会失败
- 模块没有测试,需要搭建测试套件
- 覆盖率较低,问题是接下来该测什么,而不是要测多少
不适用于此技能的场景:
- 顺带提到缺少测试的全项目审计 →
code-analyzer - 测试或应用运行失败,需要查找根因 →
error-debugging - 需要设置项目中尚不存在的测试框架 → 先完成设置(参见对应技术栈的参考文档),再回来
优先使用更具体的技能
若干技术栈已有专用技能。遇到适用的技能时,请调用该技能,而不要在此重复其指导;使用本技能处理相关决策(测试哪些情况、使用哪个源集、覆盖范围),仅在没有专用技能时自行处理。
| 情况 | 要调用的技能 |
|---|---|
使用 package:test 编写 Dart 单元测试 | dart-add-unit-test |
通过 mockito + build_runner 创建 Dart mock | dart-generate-test-mocks |
| Flutter 组件测试 | flutter-add-widget-test |
Flutter integration_test 流程 | flutter-add-integration-test |
| Compose / CMP UI 测试、语义测试、截图测试 | compose-ui-testing-patterns |
Swift、Rust、C++ 和非 UI Kotlin/KMP 没有专用技能——本技能直接覆盖这些技术栈。
前置条件
1. 这是哪种技术栈,已经在使用什么测试框架? 根据构建文件判断,不要只看文件扩展名——KMP commonTest 源集中的 .kt 文件,与 Android 单元测试中的 .kt 文件,测试方式大不相同。
| 构建文件 / 信号 | 技术栈 | 参考文档 |
|---|---|---|
build.gradle.kts 中包含 kotlin("multiplatform")、commonTest | Kotlin Multiplatform | kotlin-kmp.md |
build.gradle(.kts) 中包含 com.android.application、androidTest/ | Kotlin/JVM、Android | kotlin-kmp.md |
androidx.compose.* 或 compose-multiplatform 依赖 | Compose / CMP | compose-ui-testing-patterns 技能 |
pubspec.yaml | Flutter / Dart | flutter-dart.md |
Package.swift、*.xcodeproj、*.xcworkspace | Swift(iOS/macOS) | swift-apple.md |
Cargo.toml | Rust | rust-cpp.md |
CMakeLists.txt、conanfile.txt、*.cpp | C / C++ | rust-cpp.md |
package.json、pyproject.toml、go.mod、Maven pom.xml | JS/TS、Python、Go、Java | other-languages.md |
只加载一份技术栈参考文档,即与当前项目匹配的那一份。加载多份会混入不适用的惯例,并浪费上下文。
2. 是否已经有测试套件? 编写任何内容之前,先阅读两三个现有测试文件。项目的命名方式、断言库、测试夹具风格和 mock 方式都已经确定;遵循这些既有做法,比遵循本技能中的任何惯例都重要。
# find the existing suite and its conventions
find . -type d \( -name test -o -name tests -o -name '*Test*' -o -name commonTest \) -not -path '*/build/*' -not -path '*/node_modules/*' | head -20
如果项目完全没有测试套件,请明确说明——项目中的第一个测试通常需要添加测试框架依赖并配置运行器,这比用户预期的改动范围更大。
工作流程
步骤 1:阅读被测代码
阅读实际实现,而不只是函数签名。重点关注测试可以断言的内容,以及会增加测试难度的因素:
- 契约——输入、输出、抛出或返回的错误、文档说明的不变量
- 分支——每个
if、when/switch、提前返回、循环边界和?:回退 - 依赖项——需要替换的对象(时钟、网络、文件系统、数据库、随机数)
- 状态——结果是输入的纯函数,还是取决于之前的调用?
- 并发——挂起函数、async/await、线程、actor、通道
使用能识别符号的工具(Serena)读取外围符号及其调用点,而不是通读整个文件。调用点能展现代码的实际用法,也能帮助找出贴近实际的测试案例。
如果代码按当前写法无法测试(例如硬编码的单例、隐藏的时钟、在构造函数中执行 I/O),请说明原因,并指出最小的修复方案。不要为了让测试可行而悄悄重构生产代码——提出建议,由用户决定。
步骤 2:选择测试案例
根据契约和分支推导案例,而不是套用模板。针对每个被测单元,逐一考虑以下类别,并保留对当前代码确实适用的类别:
| 类别 | 需要考虑的问题 |
|---|---|
| 正常路径 | 使用典型值进行的典型调用——每种有意义的行为各一个,而不是每个方法各一个 |
| 边界 | 零、一个、多个;第一个、最后一个;最小值、最大值;每个索引和长度的差一错误 |
| 空值 / 缺失 | 空字符串、空集合、null/None/nil、缺少可选值、默认参数 |
| 无效输入 | 格式错误的数据、超出范围的值、语言允许时传入错误类型 |
| 错误路径 | 每个抛出的异常、Err、失败的 Future 及其恢复逻辑 |
| 状态转换 | 调用两次、调用顺序错误、关闭或释放后调用 |
| 并发 | 仅在代码确实存在并发时考虑取消、超时、调用方竞争 |
以下两条规则决定了大多数案例的取舍:
- 每个测试只验证一种行为。 一个测试断言五件事,失败时只会给出一条消息,无法说明其他四件事的情况。
- 只有可能失败的案例才值得保留。 如果没有任何合理的实现改动能让测试变红,那它就没有在测试任何东西——删除它。
修复缺陷的回归测试应反过来处理:先编写能复现缺陷的测试,确认它在未修复的代码上失败(或者说明为何无法做到),然后保留该测试。
案例选择的启发式、命名以及 fixture/mock/fake 的选择,参见 test-design.md——当契约无法明确指引案例时,请加载该文档。
步骤 3:正确放置测试文件
文件放置错误是生成的测试无法运行的最常见原因。技术栈参考文档给出了准确规则;整体原则始终相同:
- 在测试源码根目录下镜像源码路径,并以被测单元命名文件
- 根据测试需求选择源码集:共享或纯逻辑放入 common 或普通单元测试集;需要设备、真实框架或 UI 的测试放入平台或插桩测试集
- 与现有测试套件的包/模块声明保持一致
第 4 步:编写测试
遵循“先决条件 2”中的约定以及技术栈参考中的惯用写法:
- 安排、执行、断言——按此顺序清晰分隔
- 名称应具有描述性——名称应说明条件和预期结果,这样即使不打开文件,也能看懂失败原因
- 断言行为,而非实现——断言内部调用会锁定设计,并在每次重构时导致测试失败
- 只替换必要的部分——真实对象优于伪造对象,伪造对象优于模拟对象;模拟自己拥有的类型通常是设计异味
- 从构造上保证确定性——注入时钟,为随机源设定种子,控制调度器;绝不要用
sleep等待异步任务 - 优先使用测试夹具,避免重复——不过,测试中少量重复也比无人能理清的夹具层次结构更可取
编写能被项目现有测试套件认可为自身风格的测试。
第 5 步:运行测试
从未运行过的生成测试只是草稿。运行测试套件——先缩小范围,只运行新测试,再运行整个文件——并修复失败的测试。
技术栈参考中列出了准确的命令。精简输出:只筛选失败项,不要粘贴完整运行结果。
然后确认这些测试值得保留:设想一种应该导致测试失败的实现更改(可以只在脑中设想,也可以实际修改后再还原),并确认相应测试能够捕获问题。如果测试在故意写错的实现上仍然通过,请如实报告——这意味着它没有验证任何内容。
第 6 步:报告覆盖范围和缺口
如果项目已配置覆盖率工具,请运行它(Kover、flutter test --coverage、xccov、cargo llvm-cov、gcov/llvm-cov 或 JS/Python 对应工具——参见技术栈参考)。如果未配置,不要在无人要求时添加;请通过阅读分支来报告缺口。
报告缺口,而不是百分比:
## Tests added
<file paths, and one line per behavior covered>
## Verification
<command run, and the real result>
## Still untested
<specific branches or paths, as `file:line` — with why, if there is a reason>
覆盖率是发现未测试分支的手段,不是目标。不要仅为提高数字而添加测试。
操作
| 操作 | 用户意图 | 输出 |
|---|---|---|
unit(默认) | “为这个函数/类编写测试” | 替换依赖项的隔离测试,运行后通过 |
integration | “端到端测试这个功能”“一起测试这个模块” | 针对真实协作者的测试——数据库、HTTP、文件系统——包含设置/清理 |
ui | “测试这个屏幕/组件/视图” | 使用技术栈的 UI 测试框架进行 UI 测试;若有专用技能,则转至该技能 |
regression | “为刚修复的错误编写测试” | 一个有针对性的测试,修复前会失败 |
coverage | “有哪些内容尚未测试” | 覆盖率运行结果,以及按优先级排列的未测试分支列表;仅在用户要求时编写测试 |
关键约束
- 绝不针对臆造的行为编写断言。 阅读实现;如果预期行为确实含糊不清,应先询问,而不是把猜测写成断言。
- 绝不编写未运行过的测试。 如实报告失败,不要隐瞒。
- 不要为了让测试通过而修改生产代码——这违背了测试的初衷。如果代码难以测试,请单独提出修改建议。
- 添加测试时,不要重写或“改进”现有测试套件。
- 不要删除或弱化自己未编写的失败测试。 红色测试传递了信息。
- 单元测试中禁止使用等待、依赖墙上时钟或访问网络。 不稳定的测试比缺失的测试更糟糕,因为它会让人习惯忽略失败。
- 不要追逐覆盖率数字。 没有断言的测试和 getter 测试会抬高覆盖率,却起不到保护作用。
- 测试夹具中绝不要放入真实凭据、令牌或生产数据。 测试数据会提交到公开仓库。
参考资料
- test-design.md — 跨领域的用例选择、命名、夹具与伪造对象及模拟对象的区别,以及如何阅读覆盖率报告
- kotlin-kmp.md —
kotlin.test和 JUnit、源集、runTest/TestDispatcher、Turbine、MockK、Kover - flutter-dart.md — 指向专门的 Dart/Flutter 技能,以及黄金测试和覆盖率
- swift-apple.md — Swift Testing、XCTest、XCUITest、异步测试、
xccov - rust-cpp.md —
cargo test、文档测试、proptest、criterion、llvm-cov;GoogleTest、Catch2、CTest - other-languages.md — Jest/Vitest、pytest、Go
testing、纯 JUnit