ワンクリックで
unit-test-python-generate-run
Python 单测生成流水线的生成-执行阶段。手动启动。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Python 单测生成流水线的生成-执行阶段。手动启动。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
单测生成流水线的初始化阶段:为 Python / C++ 项目扫描代码仓库并生成或增量更新 `test_cases.json` 基线文件。
单测生成流水线的生成-运行阶段:基于 `test_cases.json` 基线,为 C++ 源文件分批派发 `cpp-test-gen-agent` 子 agent 并行生成测试、编译、采集覆盖率,最后汇总结果回写基线。
| name | unit-test-python-generate-run |
| description | Python 单测生成流水线的生成-执行阶段。手动启动。 |
本 skill 是 Python 单测流水线的第二阶段。上游(unit-test-gen-init)扫描仓库产出 test_cases.json 基线,记录每个函数的 dimensions / mocks_needed / func_md5 等元数据。本 skill 负责:
generate_process.json)支持 Python(pytest + coverage.py)。
test_cases.json 是 init 阶段的产物,本 skill 永远不修改它。test/ 或 .test/ 下:本 skill(含所有子 agent、临时文件、中间产物)只能在 test/ 和 .test/ 目录下创建/修改文件,不得修改这两个目录以外的任何文件(含 /tmp/)。{}/[]/pass 的占位文件。heartbeat 用 touch 创建空文件属例外。test/generated_unit/test_cases.json 已由 init 阶段产生,如果没有生成则直接终止| 路径 | 谁写 | 说明 |
|---|---|---|
test/generated_unit/test_cases.json | init(只读) | 函数元数据基线 |
test/generated_unit/generate_process.json | dispatch.py(claim 子命令原子写) | 调度状态:文件级 status + claimed_at + 子 agent 结果 |
test/generated_unit/test_run_state.json | 主 agent(dispatch merge-state 产出) | 合并后的 cases 内容 / 状态 |
test/generated_unit/<src>/test_<name>.py | 子 agent | 生成的 Python 测试 |
.test/run_results/<slug>.json | 子 agent(runner.py run) | per-file 测试结果 + 覆盖率 shard |
.test/heartbeats/<slug>.txt | 子 agent(touch) | 判活信号,mtime 超过 stale-seconds 未更新 → stale |
.test/state_shards/<slug>.json | 子 agent(LLM 直接写入) | per-file cases shard |
.test/bug_shards/<slug>.json | 子 agent(LLM 直接写入) | per-file 源码 bug shard |
.test/source_bugs.json | 主 agent(dispatch merge-bugs 产出) | 合并后的源码疑似 bug |
.test/claim_batch/<timestamp>.json | dispatch.py(claim 子命令) | 每次 claim 的输出快照,保留所有批次历史 |
并行正确性的关键约束:.test/run_results、.test/state_shards、.test/bug_shards是每个 sub-agent 自己的 shard 目录,互不冲突。
全局单文件(test_run_state.json、.test/source_bugs.json)只在步骤 8 由主 agent 调用 dispatch.py merge-* 一次性生成。
所有脚本位于 scripts/ 下,按职责分为 3 个脚本:
| 脚本 | 子命令 | 使用者 | 说明 |
|---|---|---|---|
dispatch.py | init / claim / prepare-shard / verify-artifacts / merge-state / merge-bugs / report | 主 agent | 调度编排 + 产物校验 + shard 合并 + 报告生成 |
runner.py | run | 子 agent | 测试执行 + 覆盖率采集(支持单文件作用域、增量模式) |
validate_shard.py | — | 子 agent | shard 文件格式验证(state_shard + bug_shard) |
本 skill 启动后,将按以下流程为你的仓库自动生成 Python 单元测试:
步骤 1 环境检查(pytest / pytest-cov / pytest-xdist)
步骤 2 初始化调度状态(生成 generate_process.json)
步骤 3 确认覆盖率阈值(可与用户交互修改 generate_process.json)
步骤 4─7 循环调度(每个源文件独立派发 sub-agent):
┌─ claim 待处理文件(AIMD 自适应并发)
├─ 派发 sub-agent(每个文件一个独立的 Claude 子 agent)
│ └─ sub-agent 内部循环:
│ 生成测试代码 → 执行 → 采集覆盖率
│ → 有失败? → 硬编码分类 → 修复 → 重跑
│ → 覆盖率达标? → 返回结果
├─ 校验 sub-agent 产物(三个 shard 文件必须齐全)
└─ 全部终态? → 退出循环
步骤 8 合并所有 shard → 生成最终报告
整个过程对源码零侵入:只在 test/generated_unit/ 和 .test/ 目录下创建文件。
最终输出:
| 产出物 | 路径 | 说明 |
|---|---|---|
| 测试代码 | test/generated_unit/<src>/test_<name>.py | 每个源文件对应的 pytest 测试 |
| 覆盖率报告 | test/generated_unit/per_file_report.md | 按文件的分析报告:覆盖率、失败分类、疑似 bug、未覆盖代码、改进建议 |
| 调度状态 | test/generated_unit/generate_process.json | 每个文件的完成状态(completed / unmet / abandoned) |
| 疑似 bug | .test/source_bugs.json | 被 sub-agent 识别的源码疑似 bug 列表 |
| 运行状态 | test/generated_unit/test_run_state.json | 所有 case 的详细状态、断言结果 |
注意事项:
在派发子 agent 之前,检查测试环境是否就绪:
python3 -c "import pytest, pytest_cov, coverage, xdist; print('OK')"
如果缺少依赖,按需安装:
pip install pytest pytest-cov coverage pytest-xdist psutil -q
如果 xdist 安装失败,不影响功能,可以继续;如果其他库安装失败,则停止,告知用户原因。
如果 generate_process.json 已存在,可以直接进入步骤 3。
python .claude/skills/unit-test-python-generate-run/scripts/dispatch.py init
说明:
generate_process.json,每个源文件一条记录,初始 status 为 "pending"。dispatch.py默认读取 test/generated_unit/test_cases.json,输出到 test/generated_unit/generate_process.json。程序可通过 --baseline / --output 配置。读取 generate_process.json 的 coverage_config:
cat test/generated_unit/generate_process.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d.get('coverage_config',{}), indent=2))"
然后使用 AskUserQuestion 工具向用户确认覆盖率和并发数,示例:
AskUserQuestion:
questions:
- question: "是否需要修改覆盖率阈值配置?当前值:语句 90%、分支 80%、函数 100%"
header: "覆盖率阈值"
options:
- label: "使用当前值"
description: "不做修改,直接进入下一步"
- label: "修改阈值"
description: "修改 generate_process.json 中的 coverage_config"
multiSelect: false
- question: "并发数设置?API 限流推荐 3,不限流推荐 10"
header: "并发数"
options:
- label: "3(限流模式)"
description: "适合 API 有限流的场景"
- label: "10(不限流)"
description: "适合 API 无限流的场景"
- label: "自定义"
description: "手动输入并发数数值"
multiSelect: false
如果用户选择修改覆盖率,用 Edit 工具修改 generate_process.json 的 coverage_config 字段。不修改 test_cases.json。后续所有脚本和子 agent 均从 generate_process.json 读取阈值。
用户选择的并发数用于步骤 4 的 --number 参数。
python .claude/skills/unit-test-python-generate-run/scripts/dispatch.py claim --number <步骤3的并发数>
说明:
test/generated_unit/generate_process.json。可通过 --process 配置。--number 使用步骤 3 确认的并发数。.test/claim_batch/ 目录,保留所有批次历史。这一步同时完成选中 N 个文件并将其标注为 "running",无需再用 Edit 改 JSON。返回的 JSON:
batch_size:本轮实际 claim 的文件数claimed_at:本轮 claim 的 ISO 时间戳claim_batch_path:claim batch 文件的实际路径(步骤 5a 的 --from-claim 必须使用此字段,不要用 claimed_at 拼接)files[]:与 batch 同构,每个文件带 paths.{run_result,state_shard,bug_shard,slug}
—— 这些 shard 路径必须原样传给子 agent,否则并行时会互相覆盖status_counts:各状态计数running_count:当前处于 "running" 状态的文件数(含本轮之前已在跑的)reclaimed_stale:把超过 --stale-seconds 的 "running" 任务(子 agent 崩溃 / 超时)重新回收到本批auto_abandoned:attempt_count >= 3 且产物始终缺失,自动标记为 abandonedrecommended_concurrency:AIMD 建议的下一轮并发数(1/2/3)circuit_break:是否触发熔断(连续 2 轮全 abandoned)circuit_break_reason:熔断原因字符串,未熔断时为 nullall_done:全部进入终态时为 true主 agent 不需要手工计算并发数。dispatch.py claim 内置 AIMD 算法自动调整:
| 条件 | 并发数 |
|---|---|
| 无历史记录(冷启动) | 1 |
最近 3 个完成的 sub-agent 中 ≥2 个 effective_attempt_count > 1 | 降到 1(MD) |
最近 5 个连续 completed 且 effective_attempt_count == 1 | 恢复到 --max-number(AI) |
最近 5 个中有 last_error_category == "rate_limit" | 立即降到 1(429 触发 MD) |
| 其他 | 1 |
如果 batch_size == 0 且 all_done == true,跳到步骤 8。
用 dispatch.py prepare-shard 从 claim batch 批量生成 task_envelope,再并发派发 python-test-gen-agent。
从步骤 4 的 claim batch 文件一次性生成所有 sub-agent 所需的 task_envelope:
python .claude/skills/unit-test-python-generate-run/scripts/dispatch.py prepare-shard \
--from-claim <步骤4返回的claim_batch_path> \
--blind
说明:
--from-claim 读取 claim batch 中的 files[],从 generate_process.json 自动推导 round 和 shard 路径。.test/task_envelopes/<slug>.json。generated[] 数组中每个元素带 source_path 和 envelope_path,供步骤 5b 使用。prepare-shard 会读取现有 shard(run_result、state_shard),收集源码片段(每个函数 ±20 行)。sub-agent 只需读这一个文件即可开始工作。prepare-shard --file <source_path> --round <n> --output <path> 仍可用。--blind 启用盲测模式:前两轮 source_snippets 只含签名+docstring,第三轮起子 agent 自动转为阅读源码补测。在同一条回复里,对 generated[] 中每个 envelope 用 Agent 工具启动 python-test-gen-agent,提示词模板:
读取
<envelope_path>,按其 JSON 内容中定义的任务执行。
所有子 agent 并发启动(run_in_background: false),等待全部完成后进入步骤 6。
paths.* 中的 shard 路径必须原样传入(来自 task_envelope),不可修改——并行时子 agent 依赖各自的 shard 隔离dispatch.py claim --stale-seconds 在下一轮自动处理产物校验(硬性,不可跳过):sub-agent 结束后,调用脚本校验产物:
python .claude/skills/unit-test-python-generate-run/scripts/dispatch.py verify-artifacts \
--process test/generated_unit/generate_process.json \
--file <source_path> \
--on-missing pending
脚本会原子检查三个 shard 文件:
.test/run_results/<slug>.json.test/state_shards/<slug>.json.test/bug_shards/<slug>.json(空文件也算)返回 verified == true → 产物齐全,attempt_count 自动归零
返回 verified == false → 产物缺失,status 回退到 --on-missing(pending 或 abandoned),last_error_category 写入 "no_artifact"
校验通过后,对每个文件:
generate_process.jsonresult 字段result 将 status 改为:
"completed":unmet_reasons == [](达标)或 unmet_reasons 非空但 objective_blocker == true(dead code / 抽象方法 / 不可达分支等客观原因,备注原因)"unmet":unmet_reasons 非空且 objective_blocker == false——未达标原因是测试能力不足(迭代不够、测试方案问题等)"abandoned":所有 gap 函数都被 source_bug 阻塞,同时写入 abandon_reason = "all_source_bugs"circuit_break == true → 立即停止 dispatch,跳到步骤 8 生成报告并终止。报告需说明限流导致哪些文件 abandonedrecommended_concurrency == 1 且本轮唯一的 sub-agent 也失败 → 同样跳到步骤 8,不要继续尝试"pending" 或 stale "running" → 回到步骤 4(下一批,claim 会自动回收 stale + AIMD 调节并发数)"running" 但未超时 → 等待子 agent 完成并行 shards 由主 agent 统一合并:
# 8.1 合并所有 per-file state shards
python .claude/skills/unit-test-python-generate-run/scripts/dispatch.py merge-state \
--shards-dir .test/state_shards \
--process test/generated_unit/generate_process.json \
--output test/generated_unit/test_run_state.json
# 8.2 合并所有 per-file bug shards
python .claude/skills/unit-test-python-generate-run/scripts/dispatch.py merge-bugs \
--shards-dir .test/bug_shards \
--output .test/source_bugs.json
# 8.3 生成按文件的分析报告(从 .test/run_results 目录聚合覆盖率)
python .claude/skills/unit-test-python-generate-run/scripts/dispatch.py report \
--process test/generated_unit/generate_process.json \
--run-state test/generated_unit/test_run_state.json \
--run-results-dir .test/run_results \
--source-bugs .test/source_bugs.json \
--output test/generated_unit/per_file_report.md \
--format markdown
读取报告文件并呈现给用户。同时从 generate_process.json 汇总:
status == "completed")vs 未达标("unmet")vs 已放弃("abandoned")result.iterations_used)result.unmet_reasons)和客观阻碍标记(result.objective_blocker){
"version": "1.0",
"generated_at": "2026-04-21T12:00:00",
"baseline_ref": "test/generated_unit/test_cases.json",
"max_iterations": 3,
"shards_root": ".test",
"coverage_config": {
"statement_threshold": 90,
"branch_threshold": 80,
"function_threshold": 100,
"no_progress_rounds": 2,
"per_function_max_iterations": 3
},
"files": {
"core/parser.py": {
"file_md5": "abc123",
"test_path": "test/generated_unit/core/test_parser.py",
"functions": {
"parse_header": {
"dimensions": ["functional", "boundary", "exception"],
"line_range": [10, 50],
"signature": "def parse_header(data: bytes) -> dict",
"mocks_needed": [],
"test_optional": false,
"func_md5": "def123"
}
},
"status": "pending",
"claim_round": 0,
"attempt_count": 0,
"effective_attempt_count": 0,
"last_error_category": null,
"last_attempt_at": null,
"abandon_reason": null,
"result": null
}
}
}
| 字段 | 说明 |
|---|---|
claim_round | 已被 claim 的次数(含 stale reclaim),首次 claim 时从 0 变 1 |
attempt_count | 有效尝试次数(stale reclaim 不计入);attempt_count >= 3 且无产物 → 自动 abandoned;产物校验通过后归零 |
effective_attempt_count | 每次 claim 都 +1(含 stale reclaim),用于 AIMD 节流判断重试频率和自动 abandoned 阈值(≥3) |
last_error_category | 产物校验失败时由 verify-artifacts 写入:no_artifact |
last_attempt_at | 最近一次 claim 的 ISO 时间戳 |
abandon_reason | "exhausted_attempts"(限流/崩溃耗尽尝试)或 "all_source_bugs"(所有 gap 被源码 bug 阻塞) |
注意:coverage_config 中的 no_progress_rounds 和 per_function_max_iterations 可能在 test_cases.json 中不存在,此时 sub-agent 使用默认值(2 和 3)。claimed_at 和 claim_round 在初始状态不存在,首次 dispatch claim 时写入。
status 状态机:
pending ──(dispatch claim)──▶ running ──(子 agent 返回 + 产物校验通过)──▶ completed / unmet / abandoned
│
├─(产物缺失 + stale 超时)──▶ pending(回收重试)
│
└─(effective_attempt_count >= 3 且无产物)──▶ abandoned(熔断放弃)
| status | 含义 |
|---|---|
pending | 还没派发过,或已被回收等待重试 |
running | dispatch claim 已写入 claimed_at |
completed | 子 agent 返回 unmet_reasons == [],达到阈值 |
unmet | 未达标且原因非客观(迭代不够 / 测试方案不足),objective_blocker == false |
abandoned | effective_attempt_count >= 3 无产物(abandon_reason = "exhausted_attempts"),或所有 gap 函数都被 source_bug 阻塞(abandon_reason = "all_source_bugs") |
{
"source_path": "core/parser.py",
"test_path": "test/generated_unit/core/test_parser.py",
"functions": {
"parse_header": {
"dimensions": ["functional", "boundary", "exception"],
"coverage": {
"statement": { "target": 90, "actual": 95.5 },
"branch": { "target": 80, "actual": 88.0 },
"function": { "target": 100, "actual": 100 }
}
}
},
"unmet_reasons": [],
"objective_blocker": true,
"iterations_used": 3
}
test/generated_unit/conftest.py 新建最小实现tool_statusmd5_drifts,提醒用户重跑 initdispatch claim --stale-seconds N
会把 claimed_at 早于 now-N 秒的"running"任务自动回收进本批,reclaimed_stale
字段会列出被回收的文件路径。pip install pytest pytest-cov coveragereferences/run-state-schema.md:test_run_state.json 结构 + case 字段定义references/run-result-schema.md:.test/run_result.json 结构 + 覆盖率配置references/failure-classification.md:LLM 判定 test_code_bug / source_code_bug 的规则