mcpField
T
test-generation

test-generation

github.com/jablonkai测试与质量443 次安装今天更新
代码仓库

关于 test-generation

面向编码 Agent 的测试生成技能,可为现有代码或已修复缺陷编写项目框架适用的测试,并运行测试、指出未覆盖部分。重点在边界与错误路径的案例选择。

主要功能

  1. 01按项目测试框架生成测试
  2. 02覆盖边界与错误路径
  3. 03支持回归、单元、集成及 UI 测试
  4. 04运行测试并报告未测试部分
  5. 05依据技术栈选择测试位置

适用场景

  1. 01为新函数或模块补充测试
  2. 02为已修复缺陷编写回归测试
  3. 03确定低覆盖率模块接下来应测试什么

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jablonkai/skills --skill test-generation

安装后在 Claude Code 中输入/test-generation确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
flutter-dart.md
kotlin-kmp.md
other-languages.md
rust-cpp.md
swift-apple.md
test-design.md

详细介绍

test-generation

用途

将现有代码转化为真正能捕获故障的测试。输入可以是函数、类、模块或错误报告;输出则是使用项目自身的测试框架、放在正确源集中的测试文件。这些测试应当能够运行并通过,同时附上一段简短说明,指出仍未覆盖的部分。

价值在于选择测试用例,而不是敲代码。只覆盖正常流程的测试只能说明代码的行为,无法保护它。大多数真实缺陷都出现在边界条件和错误路径中,因此测试用例也应重点覆盖这些情况。

适用场景

  • “为 <file/function/class> 编写测试”——针对新代码或现有代码
  • 刚刚修复了一个错误,需要添加回归测试,确保没有修复时测试会失败
  • 模块没有测试,需要搭建测试套件
  • 覆盖率较低,问题是接下来该测什么,而不是要测多少

不适用于此技能的场景:

  • 顺带提到缺少测试的全项目审计 → code-analyzer
  • 测试或应用运行失败,需要查找根因 → error-debugging
  • 需要设置项目中尚不存在的测试框架 → 先完成设置(参见对应技术栈的参考文档),再回来

优先使用更具体的技能

若干技术栈已有专用技能。遇到适用的技能时,请调用该技能,而不要在此重复其指导;使用本技能处理相关决策(测试哪些情况、使用哪个源集、覆盖范围),仅在没有专用技能时自行处理。

情况要调用的技能
使用 package:test 编写 Dart 单元测试dart-add-unit-test
通过 mockito + build_runner 创建 Dart mockdart-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")、commonTestKotlin Multiplatformkotlin-kmp.md
build.gradle(.kts) 中包含 com.android.application、androidTest/Kotlin/JVM、Androidkotlin-kmp.md
androidx.compose.* 或 compose-multiplatform 依赖Compose / CMPcompose-ui-testing-patterns 技能
pubspec.yamlFlutter / Dartflutter-dart.md
Package.swift、*.xcodeproj、*.xcworkspaceSwift(iOS/macOS)swift-apple.md
Cargo.tomlRustrust-cpp.md
CMakeLists.txt、conanfile.txt、*.cppC / C++rust-cpp.md
package.json、pyproject.toml、go.mod、Maven pom.xmlJS/TS、Python、Go、Javaother-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

评论

支持 Markdown