本教程面向已经跑通过 Claude Code、想把自己的重复工作流沉淀成可复用能力的开发者。你将从零创建一个真实的「单元测试生成」Skill,并采用 Anthropic 官方 skill-creator 推荐的 Evals 闭环:先写草稿 → 跑测试用例 → 量化打分 → 根据反馈迭代,直到 Skill 在真实场景下稳定可用。学完你能独立完成"草稿—评测—打磨—发布"全流程,并理解为什么"会写 Skill"不等于"Skill 写得好"。
claude --version 可正常运行),并能完成一次普通会话。claude plugin install skill-creator@anthropics (对应仓库 anthropics/claude-plugins-official)。SKILL.md 的两段式结构(YAML frontmatter + Markdown 正文),有基本概念再动手。一个 Skill 本质是一个目录,至少含一个 SKILL.md。但"能触发"和"触发后输出靠谱"是两回事。官方 skill-creator 把创建过程明确建模为一个循环:
关键点:永远用"装了 Skill vs 没装 Skill"的对照来判断 Skill 是否真的在创造增量价值——否则你可能在优化一个本就靠 Claude 默认能力就能完成的任务。
Skill 的触发几乎完全由 frontmatter 里的 description 决定,正文里写得再详细也不会参与"是否触发"的判断。因此设计阶段先回答四个问题:
常见问题:Skill 不触发或乱触发。 根因几乎都在 description 写得太泛或太窄。官方给出的对策是"适度 pushy"——不要只写功能,要写清触发场景,并主动覆盖那些"用户没明说但意图明显"的情况。
目录结构遵循三层渐进式披露(progressive disclosure):
unit-test-gen/
├── SKILL.md # 必须:frontmatter + 正文(<500 行)
├── scripts/
│ └── scaffold.py # 可执行脚本:确定性、重复性任务
└── references/
└── naming.md # 按需加载的参考资料
SKILL.md 草稿:
---
name: unit-test-gen
description: 为函数或模块生成结构化单元测试,覆盖正常路径、边界条件和异常分支。当用户提到"写测试""补单测""生成测试用例""提高覆盖率""给这个函数加测试",或直接粘贴一段函数/类期望被验证时,都应使用本 Skill;即使用户没明说"单元测试",只要意图是验证代码行为,也请使用。
---
# 单元测试生成
为目标函数生成可直接执行的测试文件。核心目标不是"凑用例数",而是
用最少的用例覆盖最值得验证的行为。
## 工作流程
1. 读懂被测函数的契约:入参类型、返回值、副作用、可能的异常。
2. 把用例分成三类:正常路径、边界条件、异常/错误路径。
3. 用"行为描述"命名用例(`it('returns 0 when both inputs are 0')`),
而不是 `test1`、`test2`——名字本身就是文档。
4. 优先调用 scripts/scaffold.py 生成测试文件骨架,再填充用例。
## 为什么强调命名
未来有人改这条用例时,名字会告诉他这条用例在保护什么行为。
命名模糊的用例在被改坏时不会立刻被发现。
预期结果: 保存后,在 Claude Code 里说"帮我给下面这个函数写测试",该 Skill 应被自动加载。
常见问题: 若正文超过 500 行,应拆出 references/ 子文件并在正文中用明确指引告诉模型"何时去读哪个文件"——避免把所有细节都塞进常驻上下文。
在与 Skill 目录平级的位置建 <skill-name>-workspace/。先只写 prompt,保存到 evals/evals.json:
{
"skill_name": "unit-test-gen",
"evals": [
{
"id": 1,
"prompt": "给这个函数写测试:function clamp(v, lo, hi) { return Math.min(hi, Math.max(lo, v)); }",
"expected_output": "针对 clamp 的测试,含 v 在区间内、低于 lo、高于 hi、lo==hi 等边界",
"files": []
},
{
"id": 2,
"prompt": "帮我补单测,要求覆盖异常路径:function divide(a, b) { if (b === 0) throw new RangeError('div0'); return a / b; }",
"expected_output": "包含 b=0 抛 RangeError 的断言,以及正常除法",
"files": []
},
{
"id": 3,
"prompt": "提高这个模块的覆盖率:贴入一段含多个分支的计税函数",
"expected_output": "覆盖每个税率区间的边界值",
"files": []
}
]
}
写完先给用户过一眼:"这几个测试用例看着对吗?要加吗?"——用例本身的质量决定整个闭环的上限。
官方反复强调:别先跑装了 Skill 的、回头再补基线。每条用例都在同一轮里同时起两个子任务——一个带 Skill、一个不带(改进已有 Skill 时基线换成旧版本快照)。这样两组几乎同时完成,省墙钟时间。
工作区按迭代组织:
unit-test-gen-workspace/
└── iteration-1/
├── clamp-bounds/
│ ├── with_skill/outputs/clamp.test.js
│ ├── without_skill/outputs/clamp.test.js
│ └── timing.json
├── divide-errors/
│ └── ...
└── benchmark.json
每个用例目录用描述性名字(clamp-bounds),不要叫 eval-0——这样在结果看板里一眼能看懂。每个用例写一份 eval_metadata.json,先留空断言:
{ "eval_id": 1, "eval_name": "clamp-bounds", "prompt": "...", "assertions": [] }
timing.json 在子任务完成通知到达时立刻落盘(这是捕获 token/耗时的唯一时机,通知里带 total_tokens 和 duration_ms,别想批量后补):
{ "total_tokens": 84852, "duration_ms": 23332, "total_duration_seconds": 23.3 }
不要干等。趁子任务跑,为每条用例起草可客观验证的断言,并解释每条在查什么。好断言有两个特征:可客观判定、名字读得懂。对本 Skill 合适的断言如:
test-file-parses:生成的测试文件能被 jest --listTests 或 pytest --collect-only 静态解析通过。has-boundary-case:至少一个用例落在边界值(0、负数、上/下界、等值边界)。has-error-path:至少一个用例验证异常或错误返回。descriptive-names:用例名描述了被测行为,而非 test1/case2。能脚本判定的就写脚本判定(快、稳、可复用),别靠肉眼。断言写进 eval_metadata.json 与 evals/evals.json。
全部跑完后:
grading.json,字段必须严格是 text、passed、evidence(看板依赖这三个确切字段名,写成 name/met/details 会渲染失败):json
{ "expectations": [
{ "text": "has-boundary-case", "passed": true, "evidence": "it('clamps to lo when v < lo')" }
] }
bash
python -m scripts.aggregate_benchmark unit-test-gen-workspace/iteration-1 --skill-name unit-test-gen
产出 benchmark.json 与 benchmark.md,含每个配置(with_skill / without_skill)的 pass_rate、耗时、token,给出均值±标准差与差值。展示时把 with_skill 放在对应 baseline 前面。
分析师视角:读一遍数据,挑出聚合统计会掩盖的问题——比如某条断言"不管装不装 Skill 都过"(无区分度,该删或该换);某条用例方差极大(可能 flaky);或耗/token 的取舍是否值得。
启动看板:
bash
nohup python <skill-creator-path>/eval-viewer/generate_review.py \
unit-test-gen-workspace/iteration-1 \
--skill-name unit-test-gen \
--benchmark unit-test-gen-workspace/iteration-1/benchmark.json \
> /dev/null 2>&1 &
无显示环境(容器/CI)改用 --static out.html 生成单文件。看板两个页签:Outputs 逐条看产物并留反馈,Benchmark 看量化对比。用户点 "Submit All Reviews" 后,反馈落成 feedback.json。
读取 feedback.json,聚焦用户有具体意见的用例(空反馈=满意,不必动)。官方给了四条改进心法,比"改文案"重要得多:
ALWAYS/NEVER,就是黄灯——尝试改写成带原因的陈述。scripts/,让 Skill 调用它,省掉未来每次调用的重复造轮子。| 用例 | 断言 | without_skill | with_skill(iter-1) |
|---|---|---|---|
| clamp-bounds | has-boundary-case | ✅ | ✅ |
| divide-errors | has-error-path | ⚠️ 命名随意 | ✅ |
| tax-brackets | descriptive-names | ❌ test1/test2 | ❌ 同样含糊 |
反馈显示:with_skill 在边界/异常上更稳,但命名依然含糊——因为草稿正文只说了"要行为描述命名",没说为什么、也没给范式。改进措施:在正文补一段"为什么强调命名"的解释,并在 references/naming.md 放范式对照表;同时把骨架生成下沉为 scripts/scaffold.py。
改完进入 iteration-2:重跑全部用例(含基线),看板用 --previous-workspace iteration-1 并排对比,直到反馈全空或你满意为止。基线选择上——新建 Skill 时基线恒为"无 Skill";改进已有 Skill 时,基线用你最初拿到的版本或上一迭代,按判断决定。
完整可跑的最小项目(Node 环境):
unit-test-gen/
├── SKILL.md
├── scripts/scaffold.py # 生成 .test.js 骨架
├── references/naming.md
└── evals/evals.json
scaffold.py 核心逻辑(确定性、可复用):
# Generate a Jest test scaffold with behavior-descriptive placeholder names
import sys, pathlib
target = pathlib.Path(sys.argv[1])
out = target.with_suffix(".test" + target.suffix)
out.write_text(
"import { describe, it, expect } from '@jest/globals';\n"
f"import {{ /* exports from {target.name} */ }} from './{target.name}';\n\n"
"describe('<unit under test>', () => {\n"
" it('returns <expected> when <condition>', () => {\n"
" // arrange / act / assert\n"
" });\n"
"});\n"
)
验收:iteration-2 后 descriptive-names 对三条用例全部 pass,has-boundary-case 与 has-error-path 的 with_skill 通过率稳定高于 without_skill 一个标准差以上,且耗时/token 差异可接受。此时 Skill 才算"上线就绪"。
flowchart TD
A[明确意图与触发] --> B[写 SKILL.md 草稿]
B --> C[写 evals.json 测试用例<br/>先不写断言]
C --> D[同一轮并发:<br/>with_skill + baseline]
D --> E[运行间隙起草<br/>可客观验证断言]
E --> F[打分 grading.json<br/>聚合 benchmark.json]
F --> G[启动看板<br/>人工留反馈 feedback.json]
G --> H{反馈全空<br/>或满意?}
H -- 否 --> I[读 transcript 改写 Skill<br/>通用化/精简/讲why/下沉脚本]
I --> D
H -- 是 --> J[运行 description 优化器<br/>微调触发]
J --> K[发布 Skill]
code.claude.com/docs/en/skills——权限、共享、组织级 Skill 管理。github.com/anthropics/claude-plugins-official(本教程 Evals 闭环、断言/看板/反馈流程的权威出处)。benchmark.md 证据。[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
