我在给AI编程助手调教自定义Skill时发现很多人把Skill理解成“一段提示词”结果做出来的东西换个项目就废。尤其是测试类Skill如果你只告诉它“帮我写单元测试”它大概率会写出只覆盖Happy Path的用例甚至把mock写错。而真正好用的测试Skill核心就三样东西SKILL.md、scripts、references。这篇文章就把这三步走的方法、代码、坑一次讲清楚适合正在折腾Agent自定义技能、想把重复测试工作交给AI的同学。1. Skill开发前必须想清楚的事1.1 Skill到底是什么它和普通提示词、Agent的区别很多人第一次接触Skill时会把它当成“大号提示词”。这不能说错但会限制你对它的设计。Skill本質上是一个“能力包”它通过一个结构化的目录把任务说明、可执行脚本和参考资料打包在一起让AI Agent在特定场景下能调用。Skill不是AgentAgent是能自主决策、多步执行的主体Skill更像是Agent的“职业资格证书”告诉它“你在测试这件事上应该按什么标准干活”。以Claude Code、Codex或OpenClaw这类工具为例它们都开始支持自定义Skill。你给一个Skill起好名字、写好描述Agent就会在遇到相关任务时自动加载它。如果你只是把一段测试规范贴在系统提示词里那每次换项目、换模型都要重新调但如果你把规范放进Skill的references把重复判断逻辑写进scripts就能在不同项目间复用而且Agent的执行稳定性能明显提升。我一开始也走过弯路把测试方法全写在提示词里结果上下文被占掉一大截Agent还经常“忘记”用覆盖率工具。后来改成SKILL.md scripts references的结构问题才真正解决。1.2 为什么测试类Skill特别适合“三步走”测试类任务和写文案、写邮件这类纯文本任务最大的区别是它需要确定性。你不能让模型“猜”一个测试用例是否通过了必须让它真正跑一遍pytest、看覆盖率报告、解析失败日志。这就需要scripts发挥作用。同时测试又强依赖项目规范和工具用法比如你们的测试文件放哪、mock必须怎么写、覆盖率阈值是多少这些知识库内容正好放到references里。SKILL.md则负责“决策”什么时候触发、先做什么后做什么、哪些情况下要停下来征求用户意见。三层各司其职比什么都塞进提示词要清晰得多。如果你准备开发一个测试Skill我的建议是先别急着写代码花半小时想清楚下面几个问题这个Skill要服务什么语言和测试框架Python/pytest、JavaScript/vitest等它需要执行哪些操作生成用例、跑测试、查覆盖率、做静态检查团队有没有必须遵守的测试规范命名、目录、mock规则、阈值想清楚这三个问题后面三步走就是填内容而已。1.3 选定场景我们这次要做一个什么样的测试Skill为了让教程不悬空我以Python项目为例做一个名为pytest-qa的测试Skill。它能做四件事分析项目源码结构列出待测模块和函数清单。根据团队规范生成或补全pytest测试用例。自动执行测试与覆盖率检查并生成可读报告。在覆盖率不达标时明确指出缺口代码位置。这个Skill麻雀虽小但SKILL.md、scripts、references三部分都会用到足够覆盖大部分自定义Skill的开发套路。你完全可以照着它改成JavaScript、Go或者其他语言版本。2. 第一步SKILL.md是技能的大脑2.1 SKILL.md文件结构与元信息写法SKILL.md是整个Skill的入口文件Agent会优先读取它。它通常由两部分组成YAML格式的frontmatter和正文Markdown。frontmatter里最重要的是name和description前者是Skill的唯一标识后者决定了Agent在什么场景下会触发它。description的写法很有讲究。我见过很多人写“用于测试”这太模糊了。正确的是写清楚“什么情况下用、能解决什么问题”比如--- name: pytest-qa description: 用于Python项目的测试分析、用例生成与质量检查。当用户要求写测试、补测试、分析测试覆盖率或检查测试质量时使用。 ---这样Agent在决策时能通过语义匹配把“帮我看看为什么测试覆盖率这么低”归类到这个Skill。如果你的Skill只负责特定框架也要在description里写清楚避免被误触发。正文部分不需要长篇大论。SKILL.md不是技术文档更像是一份“操作手册摘要”它告诉Agent“按什么流程做、遵守什么原则”。核心信息包括适用场景、工作流程、执行规范、输出格式、脚本调用方式和references索引。2.2 如何描述测试任务才能让Agent执行不跑偏很多Skill失败问题不在模型能力而在SKILL.md写得太像“需求文档”没有形成可执行的约束。我总结了一个比较实用的写法用步骤约束输出格式来控制行为。步骤要足够具体比如先调用scripts/analyze.py扫描src/目录获得待测函数清单。读取references/testing_guidelines.md确认项目测试规范。按规范生成测试文件到tests/目录。调用scripts/run_checks.py执行pytest和覆盖率检查。如果覆盖率低于阈值返回具体未覆盖行号并给出补充建议。约束要比步骤更重要。没有约束的Agent会自作主张典型问题包括不读规范直接生成测试、乱改业务代码、把整个项目日志打印到输出里。所以我会在SKILL.md里单独写一节“执行约束”明确禁止哪些行为注意不要跳过分析脚本直接凭经验写测试不要在未经用户确认时修改业务代码不要在执行结果中展示大段原始日志如果覆盖率不达标不要只写“建议补充测试”要列出具体缺口。输出格式也要提前定义好。我的习惯是要求Agent最终输出包括本次执行的测试数量、通过/失败数量、覆盖率变化、未覆盖文件/函数清单、风险分级。这样结果才能直接用于团队评审。2.3 一个可直接参考的SKILL.md示例下面是我实际在用的SKILL.md简化版本你可以直接复制改--- name: pytest-qa description: 用于Python项目的测试分析、用例生成与质量检查。当用户要求写测试、补测试、分析测试覆盖率或检查测试质量时使用。 --- # pytest-qa ## 适用场景 - 新模块开发后需要补第一轮单元测试 - 已有测试覆盖不足需要定位并补全 - 需要执行pytest并输出覆盖率报告 - 需要检查测试质量识别脆弱测试和无效断言 ## 工作流程 1. 读取项目根目录确认源码位置通常是src/或项目同名目录。 2. 读取references/project_context.md获取项目结构摘要。 3. 调用scripts/check_env.py检查pytest和pytest-cov是否已安装。 4. 调用scripts/analyze.py提取待测模块的类、函数、异常分支。 5. 参照references/testing_guidelines.md生成或补全测试。 6. 调用scripts/run_checks.py执行测试并收集覆盖率。 7. 汇总结果给出风险清单和下一步建议。 ## 执行约束 - 必须先运行分析脚本再决定写哪些测试。 - 不修改业务代码除非用户明确要求。 - 测试代码必须遵循references/testing_guidelines.md的命名和结构规范。 - 外部HTTP调用必须mock不允许测试访问真实网络。 - 覆盖率低于阈值时必须列出未覆盖的具体文件和行号。 ## 输出格式 - 测试总数、通过数、失败数、跳过数。 - 覆盖率总覆盖率、核心文件逐一覆盖率。 - 未覆盖风险按“高/中/低”分级列出。 - 建议动作每个风险点给出可执行的补充测试方案。这份文档的关键不是“写得好”而是能让Agent在有限上下文里快速形成正确的行动路径。SKILL.md本身不需要把所有细节写进去细节交给references执行交给scripts。3. 第二步scripts是技能的双手3.1 scripts里该放什么不该放什么scripts目录放的是可以被Agent调用的可执行脚本。测试Skill里最常见的脚本包括环境检测、源码分析、测试生成、测试执行、覆盖率统计和质量门禁。脚本的价值在于它把模型不擅长的确定性计算和精确判断接管过来。但也要注意不应该把整个业务逻辑写进scripts。scripts只是辅助Agent的“工具”它应该保持小而专。大而全的脚本反而会让Agent不知道怎么用也会增加维护成本。我一般控制在4个脚本以内每个脚本只做一件事并且支持--help参数因为Agent会尝试用--help来理解脚本功能。3.2 测试Skill常用脚本拆解环境检测、用例生成、质量门禁先看环境检测脚本check_env.py。它的作用不是装依赖而是快速告诉Agent当前环境缺什么、版本够不够。下面是一个简化但可用的版本#!/usr/bin/env python3 检查当前Python项目运行pytest所需依赖是否就绪。 import importlib.util import sys from pathlib import Path REQUIRED [ (pytest, pytest), (pytest_cov, pytest-cov), ] def main(): project_dir Path.cwd() req_file project_dir / requirements.txt if not req_file.exists(): print(WARN: 未找到requirements.txt建议补全依赖声明后交付。) missing [] for module, package in REQUIRED: if importlib.util.find_spec(module) is None: missing.append(package) if missing: print(fMISSING: {, .join(missing)}) sys.exit(1) print(ENV_OK: pytest与pytest-cov均已安装。) if __name__ __main__: main()这个脚本的逻辑很简单但效果很好。Agent拿到ENV_OK或MISSING结果后就知道是继续跑测试还是先装依赖而不是盲目执行pytest然后报错。源码分析脚本analyze.py用Python自带的AST实现用来扫描待测函数#!/usr/bin/env python3 扫描指定源码目录输出候选待测函数清单。 import ast import sys from pathlib import Path def extract_functions(path: Path): tree ast.parse(path.read_text(encodingutf-8)) result [] for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): # 跳过魔法方法 if node.name.startswith(__) and node.name.endswith(__): continue result.append({ file: str(path.relative_to(Path.cwd())), name: node.name, line: node.lineno, args: [arg.arg for arg in node.args.args], is_async: isinstance(node, ast.AsyncFunctionDef), }) return result def main(): root Path(sys.argv[1] if len(sys.argv) 1 else src) if not root.exists(): print(ERROR: 源码目录不存在。) sys.exit(1) for py_file in sorted(root.rglob(*.py)): for info in extract_functions(py_file): print(f{info[file]}:{info[line]} {info[name]}({, .join(info[args])}) {async if info[is_async] else }) if __name__ __main__: main()这个脚本输出的是纯文本行Agent不需要额外解析JSON直接读就能定位待测函数。我故意用这种简单格式因为大模型读纯文本比读复杂结构更不容易出错。执行检查脚本run_checks.py才是质量门禁的核心#!/usr/bin/env python3 执行pytest并输出覆盖率摘要。 import subprocess import sys from pathlib import Path COVERAGE_THRESHOLD 70 def main(): project Path.cwd() cov_cmd [ sys.executable, -m, pytest, --covsrc, --cov-reportterm-missing, --tbshort, -q, ] result subprocess.run(cov_cmd, cwdproject, capture_outputTrue, textTrue) print(result.stdout[-3000:]) if passed not in result.stdout and failed not in result.stdout: print(ERROR: 无法获取pytest结果请检查测试文件是否存在。) sys.exit(2) # 这里简单从输出中提取总覆盖率 try: percent_line [line for line in result.stdout.splitlines() if TOTAL in line][0] total_percent float(percent_line.split()[-1].replace(%, )) if total_percent COVERAGE_THRESHOLD: print(f\nQUALITY_GATE_FAILED: 总覆盖率{total_percent:.1f}% {COVERAGE_THRESHOLD}%) sys.exit(3) except Exception: pass sys.exit(result.returncode) if __name__ __main__: main()注意这个脚本里有几个“巧思”--tbshort能减少日志长度result.stdout[-3000:]只保留最后3000字符避免把上下文撑爆覆盖率低于阈值时返回非0退出码Agent通过退出码就能判断是否触发质量门禁而不是靠“读文本猜”。3.3 脚本依赖与“requirements.txt”的坑很多人在开发Skill脚本时会忽略依赖声明导致换一台机器Skill就废。尤其是热词里出现过的“python skill 缺少 requirements.txt 或依赖声明”这是实际高频踩坑点。我的建议是Skill目录内单独放一个scripts/requirements.txt把脚本自身依赖和项目依赖分开。比如pytest7.0 pytest-cov4.0然后在SKILL.md的工作流程里加上一条如果check_env.py提示缺失依赖先安装scripts/requirements.txt中的包再继续执行。这样既不会污染项目主依赖也能保证Skill在干净环境里跑起来。另一个坑是路径问题。脚本执行时的工作目录不一定是Skill目录所以脚本里所有路径都要基于Path.cwd()或显式传入项目根目录。我的习惯是要求Agent统一在项目根目录执行脚本并把这一条写进SKILL.md。4. 第三步references是技能的弹药库4.1 references目录的选材与组织references目录的作用是给Agent提供“背景知识”相当于给新员工看的团队文档。它可以是Markdown、TXT、PDF甚至JSON但为了Agent解析方便我强烈建议统一用Markdown并控制单个文件体积。哪些内容适合放references我总结了三类规范类团队的测试命名规范、目录结构、mock规则、覆盖率阈值。工具类pytest常用写法、fixture示例、参数化用例、异常测试技巧。上下文类当前项目的结构摘要、历史测试分析结论、已知风险模块。这些内容如果写进SKILL.md会让决策路径变得臃肿如果写进scripts又没法让Agent理解。放在references里让Agent按需读取是最合适的。4.2 测试规范、pytest手册与项目上下文的落地写法我通常会在references目录放这三个文件references/testing_guidelines.md写团队规范内容要具体到能直接执行# 测试规范 - 测试文件统一放在tests/目录文件名以test_开头。 - 每个待测模块对应一个测试文件例如src/user.py - tests/test_user.py。 - 公共fixture统一放在tests/conftest.py中不在测试文件里重复定义。 - 对外部HTTP请求必须mock禁止在测试中访问真实网络。 - 纯函数优先使用参数化测试覆盖正常值、边界值、异常输入。 - 测试函数命名格式test_被测函数_场景_期望结果。references/pytest_cookbook.md写工具技巧类似“代码块字典”# pytest常用写法 ## 参数化 pytest.mark.parametrize(value,expected, [(1, 2), (0, 0), (-1, -2)]) def test_double(value, expected): assert double(value) expected ## 异常断言 with pytest.raises(ValueError): parse_input(bad) ## 临时目录 tmp_path是pytest内置fixture可直接使用无需自行创建临时文件夹。 ## mock外部调用 from unittest.mock import patch with patch(project.service.requests.get) as mock_get: mock_get.return_value.status_code 200references/project_context.md则建议由脚本自动生成每次运行Skill时更新。它不需要很复杂只要记录模块路径、关键函数、已知的坏味道位置就行。比如# 当前项目上下文 - 项目类型Python 3.11 FastAPI服务 - 源码目录src/app/ - 核心模块 - src/app/services/payment.py支付逻辑高风险覆盖率缺口集中在退款流程。 - src/app/utils/validator.py入参校验近两周改动频繁。 - 已有测试目录tests/ - 最近一次覆盖率68%距阈值70%还差2个百分点。4.3 控制references体积避免上下文爆炸references也不是越多越好。模型上下文是有限资源尤其是大项目如果你把几千页文档全塞进去Agent反而抓不住重点。我有三个控制原则第一单个文件控制在100行以内超过就拆成多个小文件。第二在最需要的时候才读取SKILL.md里明确写“仅在生成测试前读取testing_guidelines.md”而不是让Agent一开始就加载全部references。第三能够动态生成的内容不要静态维护比如项目上下文用脚本在Skill运行时自动生成免得每次手动更新。实际上很多Agent工具支持在Skill内部通过相对路径引用references文件你可以让Agent在需要时自行查看。这样同一个Skill既不会占满上下文又能保证信息不过时。5. 完整实战三步拼装一个可用的“pytest-qa”测试Skill5.1 目录结构与安装方式现在把前三步的内容组合起来。最终目录结构是这样的pytest-qa/ ├── SKILL.md ├── scripts/ │ ├── check_env.py │ ├── analyze.py │ ├── run_checks.py │ └── requirements.txt └── references/ ├── testing_guidelines.md ├── pytest_cookbook.md └── project_context.md安装方式取决于你用的Agent工具。以Claude Code为例通常是把整个目录放到~/.claude/skills/下Codex系列工具有些放在~/.codex/skills/或项目级.codex/skills/OpenClaw这类插件化工具则可能有自己的导入流程。不管路径怎么变核心目录结构是一致的SKILL.md必须在一级目录下scripts和references保持同名。不同工具对Skill的发现机制略有差异最稳妥的办法是查看Agent输出日志看它是否成功索引到了SKILL.md。如果没有多半是路径放错了或者description里没有触发关键词。5.2 从SKILL.md到scripts的调用链路整个调用链路是这样的用户提出“帮我把payment模块的测试补一下”→ Agent读取SKILL.md判断适用→ 按流程先读references/project_context.md了解背景→ 调用scripts/check_env.py检查环境→ 调用scripts/analyze.py src/app/services/payment.py获取函数清单→ 读取references/testing_guidelines.md确认规范→ 生成测试文件→ 调用scripts/run_checks.py执行质量门禁→ 输出报告。这里最容易被忽略的是“环境检查”这一步。很多测试Skill一上来就写测试、跑pytest结果环境里连pytest都没装回头还得找用户问。有了check_env.pyAgent可以自己在脚本输出里看到缺失依赖然后提示用户安装整个流程就顺了。scripts的输出格式也要为Agent设计。比如analyze.py输出“文件:行号 函数名(参数)”Agent可以直接把行号对应到源码run_checks.py输出最后3000字符包含TOTAL覆盖率和通过/失败统计。Agent不需要理解大量日志只需要抓住末尾关键行。5.3 在Claude Code / Codex / OpenClaw中的接入说明接入前先确认你的Agent工具支持自定义Skill或类似扩展。现在主流Agent都在做这个方向但命名和目录规则有差异。我的建议是先查官方文档确定目录再把Skill目录原样放进去然后在一个小项目上验证触发效果。验证时不要直接测完整流程先问一个简单问题“我的测试覆盖率是多少”看Agent是否主动加载pytest-qa。如果它没反应检查两点一是description里是否包含“测试”“覆盖率”这些关键语义二是Skill目录是否被工具正确识别。如果用的是OpenClaw这类更偏自动化流程的工具可能还涉及权限配置或依赖安装。不过没关系只要SKILL.mdscriptsreferences的结构清楚迁移到任何工具都只是目录和配置文件的差异。6. 常见问题与排查技巧实录6.1 Skill文件格式正确却没有被加载这个问题我遇到好几次。最常见的原因是SKILL.md的frontmatter写错比如YAML里冒号后没有空格、description为空、或者文件编码不是UTF-8。另一个原因是Agent工具只扫描特定目录没有把目录放到正确路径。还有一种情况是文件名大小写不一致比如把SKILL.md写成了skill.md有些工具能兼容有些不能。排查时优先看工具日志确认它扫描到了哪个目录。然后检查SKILL.md的YAML块确保name和description都正常。最后用一个非常直白的问题触发它比如“请读取pytest-qa skill并给出它的工作流程”如果Agent能正确总结说明加载正常。6.2 脚本执行报错python环境、依赖缺失、路径错误脚本报错大多集中在三处第一系统里没有安装python或者sys.executable不是虚拟环境导致子进程调用pytest失败第二src目录路径不对项目用的可能是app/或lib/第三缺少依赖。我的处理方法是让check_env.py把环境信息一次打印清楚包括Python版本、当前目录、pytest是否可用。这样Agent不用猜脚本也能给出明确退出码。路径问题尤其隐蔽。比如脚本里写Path(src)但Agent执行时的工作目录不是项目根目录就会报“目录不存在”。所以我在SKILL.md里明确要求所有脚本统一在项目根目录下执行并且脚本里使用Path.cwd()推导路径不要写死绝对路径。6.3 references不生效或上下文被撑爆references不生效通常是因为SKILL.md里没有明确告诉Agent“什么时候去读哪个文件”。有些工具会在Skill加载时自动把所有references内容读进来但更常见的是按需读取。你需要在SKILL.md中加入类似“生成测试前先读取references/testing_guidelines.md”的指令否则Agent不知道这些文件的存在。反过来上下文被撑爆是因为把大文件放进了references。我的经验是单个Markdown文件超过100行后Agent读取时消耗的token会明显增加而且容易丢失重点。解决办法是把长文档拆成小模块并且在SKILL.md里规定“只读取需要的部分不要把整个文件内容重复输出”。6.4 常见问题速查表问题可能原因快速解法Skill未被加载目录路径错误/文件名大小写/frontmatter异常检查工具日志确认SKILL.md在正确目录YAML格式规范脚本能跑但退出码无意义子进程异常未被捕获在脚本中显式sys.exit非0值并输出ERROR:前缀pytest找不到测试文件测试文件命名或目录不符检查tests/下文件是否以test_开头确认run_checks.py覆盖路径覆盖率始终为0--covsrc路径与源码路径不符修改cov_cmd中的src为实际源码目录Agent输出冗长日志未限制stdout输出长度脚本中截取末尾字符并提示Agent只提取关键行references内容与项目过期项目结构变动后未更新用脚本自动刷新project_context.md不用手工维护这个表我每次开发Skill都会复用。排查时先按“加载—资源—退出码—输出”四个维度定位基本能在五分钟内找到问题。7. 最后分享一个我自己的习惯每次做完一个测试Skill我不会立刻拿到真实项目上用而是先造一个“故意留了3个bug、2个未覆盖函数”的小项目跑一遍完整流程。这样能快速校验SKILL.md里的流程是否通、scripts的退出码是否合理、references规范是不是真被Agent遵守了。你会发现很多问题在“测试Skill”自己身上要么Agent跳过了分析脚本要么环境检测没有返回期望的退出码要么输出里漏掉了覆盖率门槛。这些问题在干净小项目里暴露出来比在真实项目里Debug要高效得多。测试Skill本质上也在被测试把这条原则内化到开发流程里比任何模板都管用。