ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

claude_test_agent 工程实践:用 Claude Code Skills 搭建智能测试用例生成系统

2026/10/1 15:12:59 拓冰建站 浏览量
claude_test_agent 工程实践:用 Claude Code Skills 搭建智能测试用例生成系统 1. 从需求文档到测试用例claude_test_agent 要解决的工程问题测试用例生成这件事很多团队都试过直接丢一段需求给大模型让它一次性输出几十条用例。结果往往是这样格式忽好忽坏字段时有时无覆盖范围说不清楚改了两轮之后连最初的需求点都对不上了。问题不在于模型能力不够而在于把“测试用例设计”当成了一次性文本生成任务而不是一个可校验、可回跳、可追踪的工程流程。claude_test_agent 的思路是把测试用例设计拆成一组职责明确的节点测试点设计、测试点门禁、用例生成、覆盖映射、集合校验、单条评审、结果导出。每个节点由 Claude Code Skills 承载生成能力由 Python Runtime 负责状态机、字段校验、路由和导出。这样模型只负责“生成候选内容”而“状态是否合法、下一步走哪里、最终导出什么”由确定性代码判断。这套东西适合谁适合需要批量产出测试用例、又要求交付物可审计的研发团队尤其是做 AUTOSAR、RCP、Web 这类有明确需求编号和测试点体系的领域。你不需要一开始就把所有节点都实现可以先跑通“需求 → 测试点 → 用例 → 导出”这条最小链路再逐步加门禁和校验。我试过把同一份需求分别用“一次性生成”和“Skill 节点流”跑一遍前者出来的用例需要人工逐条核对字段后者虽然多花了几个节点调用但导出时字段完整率和覆盖可追溯性明显更好。下面按工程落地的顺序把目录结构、配置骨架、提示词模板和一次完整验证流程讲清楚。2. TaoToken 前置准备Claude Code Skills 接入的 Base URL 与 Key 配置Claude Code Skills 要跑起来第一步是让 Claude Code 能正常调用模型。这里用 TaoToken 作为模型接入层它的 API 地址是https://taotoken.net/api兼容 Anthropic 风格的接口Claude Code 可以直接对接。你需要先拿到一个 API Key。打开https://taotoken.net/api-keys登录后创建一个 Key复制出来备用。注意 Key 只在创建时完整显示一次丢了就重新建一个。拿到 Key 之后配置 Claude Code 的环境变量。Claude Code 读取的是ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY这两个变量。在 Linux/macOS 下可以写进~/.bashrc或~/.zshrcexport ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的KeyWindows PowerShell 下用$env:ANTHROPIC_BASE_URLhttps://taotoken.net/api $env:ANTHROPIC_API_KEYsk-你的Key如果你用的是 Claude Code 的 settings 文件方式可以在项目根目录建.claude/settings.json把模型和接入信息写进去。这个文件后面还会用来挂 Hooks所以先建好{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key }, model: claude-sonnet-4-20250514 }这里有个容易踩的坑ANTHROPIC_BASE_URL结尾不要带/v1Claude Code 会自己拼路径。如果你写成https://taotoken.net/api/v1请求会变成/api/v1/v1/messages直接 404。配置完之后用一条最简单的命令验证连通性claude -p 回复 ok如果返回ok说明 Base URL 和 Key 都生效了。如果报 401先检查 Key 有没有复制完整、有没有多余空格如果报连接失败检查 Base URL 是不是写成了带/v1的形式。模型选择上测试用例生成这种任务对推理能力要求中等偏上用 Sonnet 系列就够成本也比 Opus 低。如果你要跑大批量用例建议在 settings 里固定模型 ID避免每次调用时模型漂移导致输出格式不一致。关于 Coding Plan 和按量计费的差异如果你只是偶尔跑几条需求验证流程按量就够如果团队要长期批量生成测试资产可以看下https://taotoken.net/coding-plan的套餐通常比纯按量更划算。接入文档在https://taotoken.net/doc里面有不同客户端的完整配置示例。3. Skills 目录结构与 settings.json 配置骨架Claude Code Skills 的约定是放在项目根目录的.claude/skills/下每个 Skill 一个子目录目录里放一个SKILL.md描述这个 Skill 的用途、输入输出和调用方式。claude_test_agent 的目录结构建议这样组织your-project/ ├── .claude/ │ ├── settings.json │ └── skills/ │ ├── testcase-init-run/ │ │ └── SKILL.md │ ├── testcase-design-testpoints/ │ │ └── SKILL.md │ ├── testcase-gate-testpoints/ │ │ └── SKILL.md │ ├── testcase-design-cases/ │ │ └── SKILL.md │ ├── testcase-map-coverage/ │ │ └── SKILL.md │ ├── testcase-validate-case-set/ │ │ └── SKILL.md │ └── testcase-export-result/ │ └── SKILL.md ├── runtime/ │ ├── init_agent_team_run.py │ ├── route_next_node_cli.py │ ├── validate_node_output_cli.py │ ├── validate_case_set_cli.py │ ├── complete_node_task.py │ └── export_testcase_excel_cli.py ├── profiles/ │ └── autosar_cp/ │ ├── profile.py │ ├── prompts/ │ ├── templates/ │ └── rules/ └── .runs/ └── {run_id}/ ├── manifest.json ├── state/current_state.json ├── snapshots/ ├── node_outputs/ ├── artifacts/ └── logs/每个SKILL.md的头部用 YAML front matter 声明名称和描述正文写清楚这个节点接收什么、产出什么、约束是什么。以testcase-design-testpoints为例--- name: testcase-design-testpoints description: 基于需求上下文生成候选测试点输出结构化 JSON --- ## 输入 - 需求文件路径由 Runtime 注入到上下文 - 领域 Profile 中的测试设计原则 ## 输出 写入 .runs/{run_id}/node_outputs/testcase-design-testpoints.json ## 约束 - 每个测试点必须包含 testpoint_id、requirement_id、input_category、action、expected_result、scenario_type - scenario_type 只能是 normal / abnormal / boundary - 不得修改 current_state.jsonsettings.json 里除了环境变量和模型还要挂 Hooks用来阻止模型直接改状态文件。Hooks 的配置写在settings.json的hooks字段下{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key }, model: claude-sonnet-4-20250514, hooks: { PreToolUse: [ { matcher: Write|Edit, hooks: [ { type: command, command: python runtime/guard_state_write.py } ] } ] } }guard_state_write.py的逻辑很简单读取工具调用的目标路径如果路径里包含.runs/且文件名是current_state.json或manifest.json就返回非零退出码Claude Code 会拦截这次写入。这样模型只能在 Skill 里生成候选内容状态流转必须走 Runtime 的 CLI。这里要注意 Hooks 的 matcher 写法。Write|Edit是正则匹配工具名。如果你只写Write那 Edit 就不会被拦截模型可能通过 Edit 绕过。建议两个都加上。另外.claude/settings.json里的 Key 是明文不要提交到 Git。把.claude/settings.json加进.gitignore团队里每个人用自己的 Key或者用环境变量注入。如果团队要共享配置可以提交一个settings.example.json把 Key 位置留空。4. 可复制的用例生成提示词模板与节点调用Skill 的核心是提示词。提示词写得好不好直接决定输出能不能被 Runtime 的 Schema 校验通过。下面给一个testcase-design-cases的提示词模板你可以直接复制到SKILL.md里改。--- name: testcase-design-cases description: 基于冻结的 canonical test points 生成结构化测试用例 --- 你是一个测试用例设计节点。你的输入是 .runs/{run_id}/state/current_state.json 中已冻结的 canonical_test_points 列表。 ## 任务 为每一个 canonical test point 生成至少一条测试用例。如果某个测试点同时包含正常和异常场景需要分别生成。 ## 输出格式 输出一个 JSON 数组每个元素包含以下字段 - case_id: 字符串格式为 TC-{requirement_id}-{序号}序号从 001 开始 - requirement_id: 字符串关联的需求编号 - testpoint_id: 字符串关联的测试点 ID - precondition: 字符串前置条件 - input_data: 字符串输入数据描述 - steps: 字符串数组操作步骤 - expected_result: 字符串预期结果 - case_type: 字符串只能是 normal / abnormal / boundary - priority: 字符串只能是 P0 / P1 / P2 - automatable: 布尔值是否可自动化 ## 约束 1. 不得修改 testpoint_id 和 requirement_id必须与输入中的 canonical test points 一致 2. 不得合并多个测试点到一个用例 3. 每个用例的 steps 至少 2 步 4. 输出必须是合法 JSON不要包裹在 markdown 代码块里 5. 写入 .runs/{run_id}/node_outputs/testcase-design-cases.json ## 示例 输入测试点 {testpoint_id: TP-001, requirement_id: REQ-100, input_category: 车速信号, action: 设置为无效值, expected_result: 系统进入降级模式, scenario_type: abnormal} 输出用例 { case_id: TC-REQ-100-001, requirement_id: REQ-100, testpoint_id: TP-001, precondition: 系统处于正常运行状态车速信号有效, input_data: 车速信号 0xFFFF无效值, steps: [启动系统并等待初始化完成, 通过诊断接口注入无效车速信号, 观察系统状态变化], expected_result: 系统在 100ms 内进入降级模式仪表显示降级提示, case_type: abnormal, priority: P0, automatable: true }这个模板的关键点在于字段名和类型写死示例给全约束里明确“不得修改覆盖相关字段”。模型在生成时有了明确的 Schema 参照输出格式稳定性会高很多。调用节点时Orchestrator 不需要自己拼提示词而是通过 Skill 名称触发。在 Claude Code 里可以这样调用claude -p 执行 testcase-design-cases 节点run_id 为 run_20250610_001Claude Code 会加载对应的SKILL.md把上下文注入然后执行。执行完之后Runtime 的validate_node_output_cli.py会校验输出文件是否符合 Schemapython runtime/validate_node_output_cli.py \ --run-id run_20250610_001 \ --node testcase-design-cases \ --schema profiles/autosar_cp/templates/testcase_schema.json如果校验通过complete_node_task.py会把节点输出合并进current_state.json并写一个快照python runtime/complete_node_task.py \ --run-id run_20250610_001 \ --node testcase-design-cases然后route_next_node_cli.py根据当前状态计算下一个节点python runtime/route_next_node_cli.py --run-id run_20250610_001返回testcase-map-coverageOrchestrator 就继续调用下一个 Skill。整个链路就是这样串起来的。这里有个实用技巧把validate_node_output_cli.py的 Schema 校验做得严格一点比如case_id必须匹配正则^TC-REQ-\d-\d{3}$case_type必须是枚举值之一。这样模型输出稍有偏差就会被拦下来而不是等到导出时才发现字段不对。拦下来之后Runtime 可以生成一个修复任务路由回testcase-design-cases重跑形成回跳闭环。5. 常见报错排查401、local proxy failed 与 reading choices 字段缺失跑这套流程时报错基本集中在接入层和输出层。下面按真实遇到的错误对照排查。401 Unauthorized这是最常见的接入错误。报错长这样API Error: 401 {error:{type:authentication_error,message:invalid api key}}原因通常是三个Key 复制不完整、Key 前后有空格、环境变量没生效。排查顺序先echo $ANTHROPIC_API_KEY看值对不对再确认ANTHROPIC_BASE_URL是不是https://taotoken.net/api。如果是在 settings.json 里配的检查 JSON 有没有语法错误可以用python -m json.tool .claude/settings.json验证。local proxy failed / connection refusedAPI Error: local proxy failed: dial tcp 127.0.0.1:xxxx: connect: connection refused这个报错说明 Claude Code 在往本地某个端口发请求而不是往你配的 Base URL 发。原因通常是环境变量被其他配置覆盖了比如系统里之前设过ANTHROPIC_BASE_URL指向本地代理。用env | grep ANTHROPIC看一下当前生效的值把多余的清掉。另外检查.claude/settings.json里有没有重复的env字段JSON 里同名字段后者会覆盖前者。reading choices 字段缺失TypeError: Cannot read properties of undefined (reading choices)这个报错一般出现在用 OpenAI 兼容格式调用时。Claude Code 走的是 Anthropic 的/v1/messages接口返回结构是content数组不是choices。如果你在某个脚本里手动拼了 OpenAI 格式的请求就会拿到不匹配的响应。解决办法是统一用 Anthropic 格式或者确认你调用的客户端支持 Anthropic 协议。TaoToken 的 API 地址https://taotoken.net/api同时兼容两种风格但 Claude Code 本身只发 Anthropic 格式所以不要手动改请求体。OAuth 相关报错OAuth error: invalid_grant如果你之前用 Claude 官方账号登录过本地可能残留了 OAuth tokenClaude Code 会优先用 OAuth 而不是 API Key。解决办法是清掉本地的凭据缓存通常在~/.claude/目录下删掉credentials.json之类的文件然后重新用 API Key 配置。或者显式设置ANTHROPIC_API_KEY让它覆盖 OAuth。节点输出 Schema 校验失败ValidationError: case_type is not one of [normal, abnormal, boundary]这是模型输出不符合 Schema。排查时先看node_outputs/testcase-design-cases.json里具体哪个字段不对。常见原因是提示词里的枚举值没写全或者模型把abnormal写成了exception。解决办法是在提示词里把枚举值用代码块标出来并在 Schema 校验失败时把错误信息回传给模型让它修复。Runtime 可以生成一个修复任务python runtime/route_next_node_cli.py \ --run-id run_20250610_001 \ --force-node testcase-design-cases \ --reason case_type enum violation这样下一轮会重新调用testcase-design-cases并在上下文里带上校验错误信息模型通常能自己改对。覆盖映射缺失导致集合校验失败CaseSetValidationError: testpoint TP-007 not covered by any case这说明testcase-map-coverage的输出里某个 canonical test point 没有对应的用例。排查时先看node_outputs/testcase-map-coverage.json确认是映射节点漏了还是用例生成节点根本没生成这个测试点的用例。如果是后者路由回testcase-design-cases并指定只补这个测试点python runtime/route_next_node_cli.py \ --run-id run_20250610_001 \ --force-node testcase-design-cases \ --target-testpoint TP-007这种定向回跳比全量重跑省很多 token也更可控。6. 一次完整验证从需求描述到测试用例导出前面讲了配置和排查现在把整条链路跑一遍。假设你有一份需求文件requirements/REQ-100.md内容是“车速信号无效时系统进入降级模式”。第一步初始化运行python runtime/init_agent_team_run.py \ --domain autosar_cp \ --requirement requirements/REQ-100.md \ --run-id run_20250610_001执行后会在.runs/run_20250610_001/下生成manifest.json和state/current_state.jsoncurrent_state.json里初始的current_node是testcase-design-testpoints。第二步调用测试点设计节点claude -p 执行 testcase-design-testpoints 节点run_id 为 run_20250610_001执行完检查输出cat .runs/run_20250610_001/node_outputs/testcase-design-testpoints.json你应该能看到类似这样的结构[ { testpoint_id: TP-001, requirement_id: REQ-100, input_category: 车速信号, action: 设置为无效值, expected_result: 系统进入降级模式, scenario_type: abnormal }, { testpoint_id: TP-002, requirement_id: REQ-100, input_category: 车速信号, action: 恢复正常值, expected_result: 系统退出降级模式, scenario_type: normal } ]第三步跑门禁节点把候选测试点冻结为 canonicalclaude -p 执行 testcase-gate-testpoints 节点run_id 为 run_20250610_001 python runtime/validate_node_output_cli.py --run-id run_20250610_001 --node testcase-gate-testpoints python runtime/complete_node_task.py --run-id run_20250610_001 --node testcase-gate-testpoints第四步生成测试用例claude -p 执行 testcase-design-cases 节点run_id 为 run_20250610_001 python runtime/validate_node_output_cli.py --run-id run_20250610_001 --node testcase-design-cases python runtime/complete_node_task.py --run-id run_20250610_001 --node testcase-design-cases第五步覆盖映射和集合校验claude -p 执行 testcase-map-coverage 节点run_id 为 run_20250610_001 python runtime/complete_node_task.py --run-id run_20250610_001 --node testcase-map-coverage python runtime/validate_case_set_cli.py --run-id run_20250610_001如果validate_case_set_cli.py返回 0说明覆盖完整、字段合法。如果返回非零看错误信息按上一节的排查方法处理。第六步导出结果claude -p 执行 testcase-export-result 节点run_id 为 run_20250610_001 python runtime/export_testcase_excel_cli.py --run-id run_20250610_001导出后检查artifacts/目录ls .runs/run_20250610_001/artifacts/ # final_testcases.json final_testcases.xlsx打开final_testcases.json确认每条用例都有完整的case_id、testpoint_id、expected_result字段。到这里从需求到测试用例的完整链路就跑通了。如果你要批量跑多个需求把init_agent_team_run.py的--run-id换成不同的值或者写个循环脚本遍历需求目录。每个 run 独立目录互不干扰方便对比不同需求的生成质量。最后说一个实际使用中的经验不要一上来就把所有节点都开满。先跑通“测试点 → 用例 → 导出”三个节点确认输出格式稳定之后再加门禁和集合校验。门禁节点如果规则太严会把一些合理的测试点也拦掉导致回跳次数增加。建议门禁先只做字段完整性校验语义评审放到后面用 Subagent 单独做。整套系统跑顺之后你可以把 Runtime 的 CLI 封装成 MCP Tools 或者 HTTP API让 CI/CD 流水线也能调用。这样每次需求变更自动触发一轮测试用例生成导出结果直接进测试管理平台。测试资产生成这件事就从“人工写”变成了“流程跑”。