Files
sanguo_moziplus_v2/docs/test-plan-e2e-v27.md
T
cfdaily a4581fef6c
Deploy / ci (push) Waiting to run
Deploy / deploy (push) Blocked by required conditions
Deploy / notify-deploy-failure (push) Blocked by required conditions
auto-sync: 2026-06-07 23:31:00
2026-06-07 23:31:00 +08:00

35 KiB
Raw Blame History

v3.0 测试体系设计

作者: 庞统(综合) + 司马懿(v30 草案 + 背靠背审查) 日期: 2026-06-05v3.0 合并版) 状态: 待主公确认 覆盖设计文档: #01 四相循环 / #07 Acquire-First / #08 Classify / #09 Rebuttal / #06 PM2 恢复 / #04 黑板协作 / #10 T3 需求探索 / #12 Pipeline / architecture-v3.0 替代: 本文档替代 docs/test-plan-e2e-v27.mdv2.7 初版,2026-05-18


〇、设计原则(主公三原则 + 仲达补充)

原则一:精准测试,最小代价覆盖修改内容

  • 改了什么模块就跑什么测试,不默认跑全量
  • 全量测试由用户手动触发
  • 测试文件命名要让人一看就知道改了什么该跑什么

原则二:从设计文档正推覆盖

  • 不是"有什么函数测什么",而是"设计定义了哪些行为就测哪些"
  • 每个设计文档对应的测试覆盖要显式标注

原则三:真实环境测试 + 测完清理

  • E2E 不搞独立 daemon、独立进程、独立数据
  • 最终汇聚点是真实 Agent,独立环境测不出真实链路
  • 正确做法:真实环境跑测试 → 测完清掉测试数据

原则四:串行场景 + 并行压力分离(主公新增)

  • 场景测试:逐个执行,一个跑完收集结果、分析根因、解决后再跑下一个。用时间换可靠性
  • 压力测试:单独一轮,多任务并行,验证并发控制、全局上限等
  • 两类测试不混在一起

一、问题诊断

1.1 现有测试的致命缺陷

缺陷 根因 后果
E2E 全部写真实 data_root TestClient 直连生产 DB,无隔离 每次跑 pytest tests/ 残留 e2e-v27 项目,PM2 重启触发广播风暴
E9-E14 cleanup 调不存在的 API /projects/{pid}/archive 未实现 异常被吞,数据不清理
无分层 单文件 62K,所有测试混在一起 改个小 bug 要跑全量
Agent 无意义响应 残留 pending 任务被 daemon 广播 浪费 token + 干扰正常工作
无数据清理能力 项目删不掉、测试邮件堆积 测试和生产数据混杂

1.2 Review 发现的额外问题

问题 说明
E14 编号冲突 设计文档 E14="AgentBusyError 分类"3 个测试),代码 E14="Rollback"1 个测试),同名不同义
设计文档严重滞后 v31 新增 9 个测试类 + test_four_phase.py ~20 个测试未入文档
E15.2/E15.3 完全缺失 Executor prompt / Reviewer prompt 无任何层级测试
E2E gate 不统一 E1-E8 无 RUN_INTEGRATION gateE9-E14 有。现已统一加 gate

二、分层架构

2.1 三层测试金字塔

          ┌─────────────────────────────────┐
          │           E2E 测试              │  marker: e2e
          │    场景测试(串行)+ 压力测试     │  gate: RUN_INTEGRATION=1
          │    真实 daemon + 真实 Agent      │  测完清理
          ├─────────────────────────────────┤
          │          集成测试                │  marker: integration
          │       API 端点 + DB 交互        │  TestClient + tmp_path 隔离
          ├─────────────────────────────────┤
          │          单元测试                │  marker: unit (default)
          │         纯逻辑·快               │  mock 所有外部依赖
          └─────────────────────────────────┘

关键规则:只有 E2E 层 spawn 真实 Agent。Unit 和 Integration 层不 spawn。

2.2 各层定位

维度 单元测试 集成测试 E2E 测试
目标 验证单个函数/类的逻辑正确性 验证 API 端点 + DB 交互 验证完整链路(真实 Daemon + Agent
数据 mock/fixture tmp_path + 独立 SQLite 真实 data_root + 测试前缀隔离
Agent 不 spawn 不 spawn 真实 spawn
清理 N/A(不产生数据) tmp_path 自动清理 清理 API teardown 删除
速度 <1s/测试 1-5s/测试 30-300s/测试
频率 每次提交 改了 API 层跑 部署前(手动触发)

2.3 改动 → 测试映射(原则一)

改了 X,应该跑哪些测试?

改动的模块 必跑测试 pytest 命令
src/blackboard/db.py(数据模型) unit/test_models + integration pytest tests/unit/test_models.py tests/integration/
src/blackboard/db.py(状态机) unit/test_state_machine + integration pytest -m state_machine
src/daemon/spawner.pyclassify unit/test_classify_outcome pytest -m classify
src/daemon/spawner.pyacquire/release unit/test_spawner + e2e/test_spawner_e2e pytest tests/unit/test_spawner.py tests/e2e/test_spawner_e2e.py
src/daemon/ticker.py(广播) unit/test_broadcast_tracker + integration pytest -m broadcast
src/daemon/ticker.py(超时) unit/test_timeout + integration pytest tests/integration/test_ticker_integration.py -k timeout
src/daemon/router.py unit/test_router pytest tests/unit/test_router.py
src/daemon/counter.py unit/test_counter pytest tests/unit/test_counter.py
src/daemon/dispatcher.py unit/test_dispatcher + integration pytest tests/unit/test_dispatcher.py tests/integration/test_dispatcher_integration.py
src/api/mail_routes.py integration/test_api_mail pytest -m mail
src/api/blackboard_routes.py integration/test_api_tasks + reviews pytest tests/integration/ -k task
src/daemon/review.py unit/test_rebuttal + integration pytest -m review
src/api/toolchain_routes.py unit/test_toolchain + integration pytest tests/unit/test_toolchain.py tests/integration/ -k toolchain

快速验证pytest -m "not e2e"~2-5 分钟,不含 E2E


三、E2E 执行策略(原则四展开)

3.1 两大类别

类别 目标 执行方式 文件
场景测试 逐个验证每个场景的正确性 严格串行:一个 case 跑完 → 收集结果 → 根因分析(如有问题)→ 修 → 再跑下一个 test_e2e_scenarios.py
压力测试 验证并发控制、全局上限、资源竞争 并行:多任务同时 broadcast/dispatch/spawn test_e2e_stress.py

3.2 场景测试流程(串行)

┌─────────────────────────────────────────────────────┐
│  对每个 E2E 场景:                                    │
│                                                      │
│  1. 创建测试任务(e2e- 前缀)                          │
│  2. 等待任务完成(轮询状态直到终态)                     │
│  3. 收集结果                                          │
│     ├── ✅ 通过 → 记录 → 清理 → 执行下一个场景          │
│     └── ❌ 失败 → 根因分析 → 修复 → 重跑当前场景         │
│  4. 清理测试数据(与诊断分离)                          │
│                                                      │
│  关键:一个场景彻底完成(通过或确认不修)才进入下一个      │
└─────────────────────────────────────────────────────┘

实现方式

  • pytest 默认串行执行(不用 xdist)
  • 每个测试函数是独立场景
  • scope="module" 的 fixture 共享 daemon 连接
  • 测试间无共享状态,各自创建/清理数据

3.3 压力测试流程(并行)

┌─────────────────────────────────────────────────────┐
│  压力测试(单独执行,场景测试全通过后):                 │
│                                                      │
│  1. 同时创建 N 个任务(e2e-stress- 前缀)              │
│  2. 验证并发行为:                                     │
│     - counter 竞争:同 agent 并发 acquire → 第二个 blocked │
│     - 全局上限:5/5 满载 → 新任务排队                   │
│     - broadcast 并发:多任务同时广播 → 无死锁           │
│     - spawn 互斥:同 session 不重复 spawn              │
│  3. 全部完成后清理                                     │
└─────────────────────────────────────────────────────┘

3.4 数据清理与诊断分离

测试执行阶段 → 结果记录 → [可选] 数据保留用于诊断 → 清理阶段

- 测试执行时:记录所有创建的资源 ID(manifest)
- 通过时:立即清理
- 失败时:保留数据供诊断,诊断完成后手动/脚本清理
- 兜底:session 结束时 atexit 全量清理 e2e- 前缀

3.5 前置依赖:数据清理 API

E2E 测试能安全运行的前提:

需求 API 状态
项目物理删除 DELETE /api/projects/{pid}?physical=true 已实现(src/api/project_routes.py
邮件批量删除 DELETE /api/mail?prefix=e2e- 已实现(src/api/mail_routes.py

四、E2E 场景清单

编号规则:S = Scenario(场景测试),ST = Stress(压力测试) 原文档 E1-E15 重新编号,解决 E14 冲突

4.1 场景测试(S1-S15,串行执行)

S1: 项目管理(4 个测试)| 对应 design: architecture-v3.0 §5

  • S1.1 创建项目(POST /api/projects
  • S1.2 项目列表(GET /api/projects
  • S1.3 自动发现(含 blackboard.db 的目录自动注册)
  • S1.4 归档项目(status=archived,目录不移动)

S2: Task CRUD + 状态机(5 个测试)| 对应 design: architecture-v3.0 §5.2

  • S2.1 创建 TaskPOST /api/projects/{pid}/tasks
  • S2.2 查询 TaskGETexpand=all
  • S2.3 合法状态转换(pending → claimed → working → review → done
  • S2.4 非法转换拒绝(409 + valid_transitions
  • S2.5 列表筛选(status/assignee/parent_task

S3: SubTask 父子关系(4 个测试)| 对应 design: architecture-v3.0 §5

  • S3.1 创建父 Task + 3 个子 Task
  • S3.2 list_subtasks 验证
  • S3.3 top_level_tasks 排除子 Task
  • S3.4 子 Task 的 stage 字段

S4: Stage 进度(3 个测试)| 对应 design: architecture-v3.0 §5

  • S4.1 带 stages_json 的父 Task → 子 Task 分配到各 stage → progress 端点
  • S4.2 空 stage 的父 Task 进度
  • S4.3 stage 分组统计验证

S5: 父 Task 状态聚合(6 个测试)| 对应 design: architecture-v3.0 §6.1

  • S5.1 all done → 父 done
  • S5.2 has review → 父 review
  • S5.3 has working → 父 working
  • S5.4 all pending → 父 pending
  • S5.5 cancelled 子 Task 排除
  • S5.6 手动状态(cancelled 父 Task)不被覆盖

S6: 依赖链(3 个测试)| 对应 design: architecture-v3.0 §6.1

  • S6.1 Task B depends_on Task AA done 后 B 自动 pendingTicker 推进)
  • S6.2 A 未完成时 B 保持 blocked
  • S6.3 多层依赖(A → B → C)

S7: 超时回收(2 个测试)| 对应 design: architecture-v3.0 §6.1

  • S7.1 claimed 超时 → pending
  • S7.2 working 超时 → failed

S8: Mail7 个测试)| 对应 design: architecture-v3.0 §12

  • S8.1 发送 Mailinform 类型 → 自动 done
  • S8.2 发送 Mailtask-assign 类型 → pending
  • S8.3 Mail 列表 + 筛选(from/to/unread
  • S8.4 Mail 详情(含 comments
  • S8.5 标记已读
  • S8.6 标记已执行
  • S8.7 Mail summary + agents 列表

S9: 真实 Agent 调度(3 个测试)| 对应 design: #01 四相循环 + architecture-v3.0 §14

  • S9.1 创建简单 Task → Ticker 调度 → Agent spawn → Agent 回写 working → 完成任务(#01
  • S9.2 创建 review 类型 Task → 调度到 simayi-challenger → 回写 review 结果(#01
  • S9.3 Guardrail 拦截(不合法任务 → Agent 拒绝执行)(architecture-v3.0 §14

Agent 任务内容:简单的"echo hello"级别任务,10-30 秒完成

S10: 全链路集成(2 个测试)| 对应 design: #01 四相循环

  • S10.1 逻辑链(创建项目 → 父 Task + 子 Task → Ticker tick → 依赖推进 → 聚合刷新 → Mail 通知 → 验证完整状态链)
  • S10.2 Agent 全链(含真实 Agent spawn + 执行 + 产出 + review

S11: Spawner Acquire-First6 个测试)| 对应 design: #07

  • S11.1 Phase 0 Pre-acquire revive:任务 timeout/failed → Phase 0 自动 revive → Phase 1 acquire 成功
  • S11.2 Phase 0 假死检测:status=running + lock PID 死 → Phase 0 自动 revive
  • S11.3 Phase 1 Counter acquire 互斥:同 agent 并发 spawn → 第二个 AgentBusyError(reason=counter_blocked)
  • S11.4 Phase 2 Session check 锁保护:counter acquire 后 → session check 在锁保护下执行 → session locked → release counter → AgentBusyError(reason=session_locked)
  • S11.5 Phase 2 Blockers 收集:多 blocker 并列收集(locked + compact)→ 返回全部 blockers
  • S11.6 Phase 3 on_checks_passed 异常回滚:on_checks_passed 抛异常 → counter 自动 release

S12: 超时统一处理(4 个测试)| 对应 design: #08 §3.7

  • S12.1 crash_limit 统一检查(working):executor crash 3 次/30min → _check_timeouts 标 failed
  • S12.2 crash_limit 统一检查(review):reviewer crash 3 次/30min → _check_timeouts 标 failed
  • S12.3 updated_at fallbackmail auto-working 无 started_at/claimed_at → updated_at fallback 生效 → 超时回收
  • S12.4 process_dead 对 review 状态:review agent 进程死 → 保持 review 状态 → 等 _dispatch_reviews 处理

S13: Compact Hanging3 个测试)| 对应 design: #07 §4.5

  • S13.1 compact_hanging release countercompact 等超限 → compact_hanging → release counter → 任务保持 working
  • S13.2 compact_hanging 后 ticker 重新 dispatchcompact_hanging → ticker _check_timeouts 检测超时 → 推回 pending → 重新 dispatch
  • S13.3 retry 遇 session busy 释放 counter_do_retry 遇 AgentBusyError → release counter → 任务保持 working → ticker 重新 dispatch

S14: AgentBusyError 分类(3 个测试)| 对应 design: #07 §2

⚠️ 原设计文档 E14="AgentBusyError 分类"与代码中 E14="Rollback" 冲突,此处统一修正

  • S14.1 counter_blockedcounter acquire 失败 → AgentBusyError(reason=counter_blocked)
  • S14.2 session_locked/running/compactingPhase 2 各种 blocker → AgentBusyError 携带具体 reason + detail.blockers
  • S14.3 Dispatcher 错误区分:dispatcher 捕获 AgentBusyError → 日志记录具体原因 → 路由决策写入 routing_decisions

S15: Crash Rollback1 个测试)| 对应 design: #07 §4.5

⚠️ 原代码中 E14="Rollback",现独立编号为 S15

  • S15.1 crash 后 current_agent 回退验证:Agent crash → current_agent 指针回退 → 任务可被重新 dispatch

S16: 广播认领扩展(2 个测试)| 对应 design: architecture-v3.0 §8.3

  • S16.1 不匹配 Agent 写 observation comment(非 NO_REPLY
  • S16.2 匹配 Agent 正常认领

S17: Pause/Resume1 个测试)| 对应 design: architecture-v3.0 §5.2

  • S17.1 paused → resumed_from 状态流转

S18: Cancelled Restart1 个测试)| 对应 design: architecture-v3.0 §5.2

  • S18.1 cancelled → pending 重启

S19: Retry Chain1 个测试)| 对应 design: #08 §3.3

  • S19.1 failed → pending retry 链路

S20: Full Lifecycle with Review1 个测试)| 对应 design: #09

  • S20.1 创建 → 认领 → 执行 → review → rebuttal → done 全链路

S21: Cache Headers2 个测试)| 对应 design: architecture-v3.0 §7

注:原代码有 3 个独立测试(html_no_cache, js_immutable, css_immutable),设计中 S21.2 将 JS+CSS 合并为"静态资源 immutable 缓存"一个场景,因为两者验证逻辑一致

  • S21.1 HTML 不缓存
  • S21.2 JS/CSS immutable 缓存(原 2 个测试合并)

S22: 工具链事件中枢(13 个测试)| 对应 design: #13 工具链与开发流程 §16

  • S22.1 PR opened → 司马懿收到 Review 请求 Mail [integration]
    • 验证:Webhook 返回 200Mail 创建成功,收件人 simayi-challenger,模板填充正确(PR号、标题、作者、分支)
  • S22.2 Review APPROVED → PR 作者收到通过通知 [integration]
    • 验证:Mail 收件人正确(PR 作者),模板填充 review result
  • S22.3 Review REQUEST_CHANGES → PR 作者收到不通过通知 [integration]
    • 验证:Mail 包含 review body,收件人正确
  • S22.4 Review COMMENTED → 不创建 Mail(忽略普通评论) [integration]
    • 验证:返回 200 但无 Mail 创建
  • S22.5 Issue assigned → 被指派人收到任务通知 [integration]
    • 验证:Mail 收件人正确(被指派人),模板含 Issue 标题/编号信息
    • 验证:assigned 给非 Agent 用户 → 忽略,返回 200
  • S22.6 CI 失败评论 → PR 作者收到 CI 失败通知 [integration]
    • 验证:新格式([CI] 前缀)和旧格式(❌ **CI 失败**)均能匹配,Mail 含错误摘要,收件人为 PR 作者
  • S22.7 非 CI 评论 → 忽略不处理 [integration]
    • 验证:不匹配 CI 评论格式的普通评论不创建 Mail,返回 200
  • S22.8 部署失败 Issue(标题含"部署失败")→ 庞统+姜维收到通知 [integration]
    • 验证:两个收件人都收到 Mailpangtong-fujunshi、jiangwei-infra),模板含 Issue 信息
  • S22.9 幂等检查:同一 delivery_id 重复投递 → 忽略 [unit]
    • 验证:第二次返回 duplicate/ignored,不重复创建 Mail
  • S22.10 签名验证:错误签名 → 403 [unit]
    • 验证:配置 secret 后,signature 不匹配返回 403secret 为空时跳过验签
  • S22.11 未知事件类型 → 忽略返回 200 [unit]
    • 验证:未知 event 不报错,返回 200,不创建 Mail
  • S22.12 畸形 payload → 返回 200 不崩溃 [unit]
    • 验证:非法 JSON / 缺少必要字段 → 返回 200,不崩溃,不创建 Mail
  • S22.13 风险级别自动判定:改动含高风险文件 → high [unit]
    • 验证:changed_files 包含 spawner/dispatcher/router 等高风险路径时 risk_level=high;普通文件为 standard

S23: 真实 Gitea Webhook 端到端验证(8 个测试)| 对应 design: #13 §16

需要 Gitea admin 权限或已配置 Webhook 的仓库,真实 PR 流程触发 Webhook 前置条件:Gitea Webhook active=true(姜维已配)、GITEA_TOKEN 已配、测试账号可用 全部完成后统一清理:关闭 PR、删除测试分支和 Issue

  • S23.1 创建真实 PR → Gitea 发送 Webhook → daemon 收到并创建 Review Mail [E2E]
    • 前置:在 moziplus-v2 仓库创建分支 + 提交(改动含高风险文件如 spawner.py+ 开 PR
    • 验证:daemon 日志有 POST /webhook/gitea 200,司马懿收到 Review 请求 Mail
    • 验证:Mail 内容含正确的 PR 号、标题、作者、分支名
  • S23.2 Gitea Review 提交 → PR 作者收到通知 [E2E]
    • 操作:在 Gitea 上用 simayi-challenger 账号提交 APPROVED review
    • 验证:PR 作者收到 Review 通过 Mail
  • S23.3 Gitea Review 驳回 → PR 作者收到通知 [E2E]
    • 操作:在 Gitea 上用 simayi-challenger 账号提交 REQUEST_CHANGES review
    • 验证:PR 作者收到 Review 驳回 Mail,包含 review body
  • S23.4 Gitea [CI] 评论 → PR 作者收到 CI 失败通知 [E2E]
    • 操作:在 Gitea PR 上发 [CI] 构建失败 格式的评论
    • 验证:PR 作者收到 CI 失败通知 Mail
  • S23.5 Gitea Issue 指派 → 被指派人收到通知 [E2E]
    • 操作:在 Gitea 创建 Issue 并 assigned 给 zhangfei-dev
    • 验证:zhangfei-dev 收到 Issue 指派 Mail
  • S23.6 风险级别判定:PR 改动含高风险文件 → risk_level=high [E2E]
    • 前置:S23.1 的 PR 改动文件包含 src/daemon/spawner.pysrc/daemon/ticker.py(高风险路径)
    • 验证:GITEA_TOKEN 已配,daemon 能调 Gitea API 获取 PR 文件列表
    • 验证:Review Mail 中标注 risk_level=high(而非 standard
  • S23.7 幂等检查真实场景:Gitea 重试 Webhook → 不重复创建 Mail [E2E]
    • 操作:对同一 PR 触发重复 Webhook(或利用 Gitea Webhook 的 Test Delivery 功能重复发送)
    • 验证:daemon 日志显示 duplicateMail 不重复创建
    • 目的:从 E2E 角度验证 S22.9 的 unit 测试,真实场景才能暴露并发竞态问题
  • S23.8 清理:关闭 PR,删除测试分支和 Issue
    • 操作:关闭所有测试 PR,删除测试分支,关闭测试 Issue
    • 验证:Gitea 仓库无测试残留(e2e- 前缀的 PR/Issue/分支均已清理)

场景测试合计:85 个测试

4.2 压力测试(ST1-ST3

ST1: 并发 Counter 竞争

  • 同时创建 3 个任务指定同一 Agent → 第 2、3 个 AgentBusyError(reason=counter_blocked)
  • 第 1 个完成后 counter release → 第 2 个 acquire 成功
  • 验证 counter release 后下一个 acquire 成功

ST2: 全局并发上限

  • 同时创建 5 个任务指定不同 Agent → 全部 acquire 成功(5/5
  • 第 6 个任务 → 被拒绝或排队
  • 验证全局计数器准确

ST3: Broadcast 并发

  • 同时广播 3 个任务 → 全部任务在 5min 内到达终态
  • 验证无死锁(通过全部终态判定)
  • 验证广播计数器正确递增

压力测试合计:3 个测试组

4.3 Prompt 集成测试(P1,延后)

以下场景需要验证 Agent prompt 行为,标 P1 延后

  • P1.1 Executor prompt 任务式指挥:executor 不再收到步骤列表,收到意图+终态+约束 → 自主决策步骤
  • P1.2 Reviewer prompt 挑战者思维:reviewer 审查时用挑战者视角 + confidence 自评

五、测试文件组织

5.1 目录结构

说明:以下为迁移完成后的目标结构。标注"待创建"的文件目前逻辑在其他文件中,迁移时需拆出。

tests/
├── conftest.py                          # 全局 fixture + marker 注册 + atexit 清理兜底
├── unit/                                # 单元测试(不 spawn 真实 Agent
│   ├── test_state_machine.py            # 状态机转换(architecture-v3.0 §5.2
│   ├── test_classify_outcome.py         # Classify 场景(#08 §3
│   ├── test_spawner.py                  # Spawner + Acquire-First#07 §2
│   ├── test_router.py                   # Router 确定性路由
│   ├── test_counter.py                  # Counter 并发控制
│   ├── test_broadcast_claim.py          # BroadcastRound 逻辑 + bug fix 回归
│   ├── test_dispatcher.py               # Dispatcher 调度逻辑
│   ├── test_review.py                   # Review + Rebuttal 逻辑
│   ├── test_inbox.py                    # Inbox JSONL 解析
│   ├── test_models.py                   # 数据模型(待创建,当前逻辑在 test_blackboard.py
│   ├── test_experience.py               # 经验蒸馏
│   ├── test_bootstrap.py                # Bootstrap 上下文
│   ├── test_health.py                   # 健康检查
│   ├── test_cli.py                      # CLI 命令
│   ├── test_skill_system.py             # Skill 匹配/推荐
│   └── test_guardrails.py               # Guardrail 规则(待创建,当前逻辑在 test_router.py + test_review_integration.py
│   ├── test_toolchain.py                # 工具链事件中枢:模板引擎 + 幂等检查 + 风险判定(#13 §16
├── integration/                         # 集成测试(不 spawn 真实 Agent
│   ├── test_api.py                      # 项目管理 + Daemon API
│   ├── test_api_mail.py                 # 邮件 APIA1-A10 防御 + 功能)
│   ├── test_main.py                     # 健康端点 + 配置
│   ├── test_dispatcher_integration.py   # Dispatcher 回滚
│   ├── test_review_integration.py       # Review pipeline + scoring
│   ├── test_ticker_integration.py       # Ticker 主循环 + 广播 + 超时 + 依赖推进
│   ├── test_sse.py                      # SSE broker + hook manager
│   ├── test_toolchain_integration.py    # 工具链 Webhook API 端点 + Mail 创建
│   └── test_v27_subtasks.py             # 子任务集成
└── e2e/                                 # E2E 测试(spawn 真实 Agent
    ├── conftest.py                      # E2E fixture:清理 API + atexit 兜底 + manifest
    ├── test_four_phase.py               # #01 四相循环
    ├── test_e2e_scenarios.py            # S1-S22 场景测试(串行)
    ├── test_e2e_stress.py               # ST1-ST3 压力测试(并行)
    └── test_spawner_e2e.py              # Spawner 进程管理(timeout kill + spawn failure

5.2 现有测试迁移映射

现有文件 去向 处理
test_e2e_v27.py E1-E8 integration/ 重写为 API 集成测试,用 tmp_path 隔离
test_e2e_v27.py E9-E14 e2e/test_e2e_scenarios.py 按 S9-S15 重新编号
test_e2e_v31.py E94-E98 e2e/test_e2e_scenarios.py 按 S16-S21 编号
test_e2e_v31.py E10c/E10d e2e/test_e2e_scenarios.py 按 S19-S20 编号
test_e2e_v31.py E15a/E15b e2e/test_e2e_scenarios.py 按 S16.1-S16.2 编号(Prompt v3 broadcast
test_four_phase.py e2e/test_four_phase.py 加 RUN_INTEGRATION gate
test_spawner_e2e.py e2e/test_spawner_e2e.py 已有 gate
test_api.py integration/ 保持
test_api_mail.py integration/ 保持
test_main.py integration/ 保持

5.3 pytest marker 定义

# conftest.py
def pytest_configure(config):
    markers = {
        "unit": "单元测试:纯逻辑,mock 外部依赖",
        "integration": "集成测试:API 端点 + 隔离数据",
        "e2e": "端到端测试:真实环境(RUN_INTEGRATION=1 触发)",
        "slow": "慢测试(>5s",
        "broadcast": "广播认领相关",
        "mail": "邮件系统相关",
        "state_machine": "状态机转换",
        "classify": "Classify Outcome 相关",
        "review": "审查/Rebuttal 相关",
    }
    for name, desc in markers.items():
        config.addinivalue_line("markers", f"{name}: {desc}")

5.4 Gate 保护

层级 Gate 说明
unit 纯 mock,安全
integration tmp_path 隔离,安全
e2e RUN_INTEGRATION=1 所有 e2e 测试统一 gate@skip_no_integration

六、设计文档 → 测试覆盖映射(原则二)

6.1 覆盖矩阵

设计文档 关键行为 单元测试 集成测试 E2E 测试
architecture-v3.0.md 状态机、数据模型、API 契约 P0 P0
#01 四相循环 spawn→execute→classify→retry/advance P1 P0S9-S10
#07 Acquire-First Phase 0-4、AgentBusyError 区分 P0 P1 P1S11-S15
#08 Classify Outcome A0-A14 + B1-B4 场景 P0 P1 P1S12
#09 Rebuttal 审查流水线、2 轮反驳 P1 P0 P1S20
#06 PM2 恢复 崩溃后状态恢复、zombie 检测 P1 P0
#04 黑板协作 @mention、comment、产出、review P1 P0 P1
#10 T3 需求探索 苏格拉底式对话、需求规格化 P1(暂无测试文件)
#12 Pipeline task_type 路由、状态机约束 P2 P2
#13 工具链与开发流程 §16 Webhook 接收、事件路由、模板填充、Mail 创建、幂等检查、签名验证 P0 P0 P1S22

6.2 模块 → 测试层级 → 优先级

模块 单元 集成 E2E 对应设计文档
状态机转换 P0 P0 architecture-v3.0 §5.2
Classify Outcome P0 P1 P1 #08 §3
广播认领 P0 P1 P0 architecture-v3.0 §8.3
邮件系统 P1 P0 P1 architecture-v3.0 §12
Acquire-First P0 P1 P1 #07 §2
四相循环 P1 P0 #01 §2
Spawner 进程管理 P0 P1 P1 architecture-v3.0 §6.3
Counter 并发控制 P0 P1 architecture-v3.0 §8.2
Router 路由 P0 P1 architecture-v3.0 §8.1
Review/Rebuttal P1 P0 P1 #09 §2
Guardrail P0 P1 architecture-v3.0 §14
数据模型 P0 P0 architecture-v3.0 §5
依赖推进 P0 P1 architecture-v3.0 §6.1
超时检测 P0 P1 P1 architecture-v3.0 §6.1
@mention / 协作 P1 P0 P1 #04
Inbox JSONL P0 P1 architecture-v3.0 §15
SSE 推送 P1 P0 architecture-v3.0 §15
Bootstrap 上下文 P1 P1 architecture-v3.0 §10
PM2 恢复 P1 P0 #06
工具链事件中枢 P0 P0 P1 #13 §16

七、E2E 数据隔离与清理

7.1 各层隔离策略

层级 策略 清理方式
单元 mock / 不涉及 IO 无残留
集成 tmp_path + 独立 SQLite fixture teardown 自动清理
E2E 真实 data_root + e2e- 前缀隔离 清理 API + atexit 兜底

7.2 E2E 清理机制

# tests/e2e/conftest.py

@pytest.fixture(scope="session")
def e2e_session_prefix():
    """Session 级唯一前缀"""
    return f"e2e-{uuid.uuid4().hex[:6]}-"

@pytest.fixture(autouse=True)
def e2e_cleanup(request):
    """每个测试后清理,与诊断分离"""
    created_pids = []
    
    def _track(pid):
        created_pids.append(pid)
    
    yield  # 测试执行
    
    # 清理阶段
    for pid in created_pids:
        requests.delete(f"http://localhost:8083/api/projects/{pid}?physical=true")

@pytest.fixture(scope="session", autouse=True)
def e2e_session_cleanup(e2e_session_prefix):
    """Session 结束时兜底清理"""
    yield
    # 批量清理所有 e2e- 前缀

7.3 atexit 兜底

# tests/conftest.py(全局)

import atexit

def _emergency_cleanup():
    """进程崩溃时的最后防线"""
    # 批量删除所有 e2e- 前缀的项目和邮件
    pass

atexit.register(_emergency_cleanup)

八、通过标准

类别 标准
场景测试(S1-S23 85 个全部通过。失败必须分析根因并修复后才继续
压力测试(ST1-ST3 3 个全部通过。验证并发控制无死锁
P0 必须全部通过 S1-S8 + S10 + S11-S14
P1 通过率 ≥ 80% S9 + S15-S21 + ST1-ST3(依赖真实 Agent,可能受网络/环境干扰)
P1 延后 P1.1/P1.2Prompt 集成,待 Agent prompt 稳定后补)
速度 单元 < 30s,集成 < 5minE2E 场景 < 60minE2E 压力 < 30min
零残留 测试运行前后,GET /projects?search=e2e- 返回 0 条

九、实施计划

Phase 0:数据清理 API(前置依赖)

任务 API 优先级
项目物理删除 DELETE /api/projects/{pid}?physical=true P0
邮件批量删除 DELETE /api/mail?prefix=e2e- P0

Phase 1:基础设施

任务 说明
conftest.py 更新 marker 注册 + atexit 清理兜底 + tmp_path fixture
E2E conftest 清理 API fixture + session 兜底 + manifest
Gate 统一 所有 E2E 文件加 @skip_no_integration

Phase 2P0 单元测试(已有 394 passed

任务 对应设计文档
test_state_machine.py architecture-v3.0 §5.2
test_classify_outcome.py #08 §3
test_spawner.py(含 Acquire-First #07 §2
test_broadcast_claim.py architecture-v3.0 §8.3
test_counter.py architecture-v3.0 §8.2
test_guardrails.py(实际在 test_router.py + test_review_integration.py architecture-v3.0 §14

Phase 3:集成测试(已有 155 passed

任务 说明
test_api_mail.py A1-A10 + 批量删除(已到位)
test_ticker_integration.py 广播 + 超时 + 依赖推进
test_spawner_e2e.py acquire/release + 监控(E2E 层)
test_dispatcher_integration.py Router + Guardrail 调度

Phase 4:E2E 场景测试(串行)

任务 覆盖设计文档
S1-S8 API 层场景 architecture-v3.0
S9-S10 Agent 调度 #01 四相循环
S11-S15 Spawner/Acquire #07 + #08
S16-S21 扩展场景 各设计文档

Phase 5:E2E 压力测试(并行)

任务 说明
ST1 Counter 竞争 同 agent 并发
ST2 全局上限 多 agent 并发
ST3 Broadcast 并发 多任务广播

十、约束和风险

10.1 约束

  1. 只有 E2E spawn 真实 AgentUnit 和 Integration 层不 spawn
  2. E2E 用真实环境:真实 daemon + 真实 Agente2e- 前缀隔离
  3. 场景测试串行:一个完成再下一个,用时间换可靠性
  4. 压力测试并行:单独一轮,场景测试全通过后执行
  5. 测试不依赖执行顺序:每个测试独立创建/清理数据
  6. 全量 E2E 手动触发RUN_INTEGRATION=1 pytest tests/e2e/
  7. 测试数据前缀规范:所有 E2E 项目名以 e2e- 开头

10.2 风险

风险 缓解
E2E 场景测试影响生产 Agent e2e- 前缀项目,串行执行减少干扰
清理 API 失败导致残留 atexit + session fixture 双保险
E2E 测试不稳定(Agent 响应慢) 充分 timeout + retry + 失败时保留数据供诊断
迁移现有测试引入回归 先迁移后验证,不删旧文件直到新测试全绿

十一、变更记录

日期 版本 变更
2026-05-18 v2.7 初版 E1-E10(仲达)
2026-06-01 v2.8 新增 E11-E15(庞统)
2026-06-05 v3.0 全面重写:三层分离 + 主公四原则 + 背靠背 review 修正 + E14 编号冲突修复 + 场景/压力分离 + 串行执行策略(庞统综合,基于仲达 v30 草案)
2026-06-05 v3.0.1 仲达 review 修订:修正幽灵文件 4 处 + 补 E15a/b 迁移映射 + 清理 API 状态改已实现 + S9.3 设计文档标注 + ST 验收标准细化 + #10 暂无文件标注 + S21 合并说明
2026-06-07 v3.0.3 新增 S23 真实 Gitea Webhook E2E8 个测试),覆盖 T02 真实投递 + T03 风险级别判定 + 幂等真实场景 + 统一清理。采纳仲达 S1(幂等 E2E)+ S2(统一清理)

v3.0 vs v2.8 关键变化

维度 v2.8 v3.0
E2E 执行策略 无明确规定 串行场景 + 并行压力分离
编号体系 E1-E15E14 冲突) S1-S21 + ST1-ST3(无冲突)
Agent spawn E1-E8 无 gate 统一 RUN_INTEGRATION gate
数据隔离 未提及 真实 data_root + 清理 API + atexit 兜底
设计覆盖 只管 E2E 全三层 + 设计文档正推映射
文件组织 两大文件 按模块拆分 + 命名规范