Files
sanguo_moziplus_v2/docs/test-plan-e2e-v27.md
T
cfdaily 2aa50a66da
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 20:27:06
2026-06-07 20:27:06 +08:00

696 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.md`v2.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.py`classify | unit/test_classify_outcome | `pytest -m classify` |
| `src/daemon/spawner.py`acquire/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: 工具链事件中枢(12 个测试)| 对应 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
**场景测试合计:76 个测试**
### 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 定义
```python
# 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 清理机制
```python
# 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 兜底
```python
# tests/conftest.py(全局)
import atexit
def _emergency_cleanup():
"""进程崩溃时的最后防线"""
# 批量删除所有 e2e- 前缀的项目和邮件
pass
atexit.register(_emergency_cleanup)
```
---
## 八、通过标准
| 类别 | 标准 |
|------|------|
| **场景测试(S1-S22** | 76 个全部通过。失败必须分析根因并修复后才继续 |
| **压力测试(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 4E2E 场景测试(串行)
| 任务 | 覆盖设计文档 |
|------|------------|
| S1-S8 API 层场景 | architecture-v3.0 |
| S9-S10 Agent 调度 | #01 四相循环 |
| S11-S15 Spawner/Acquire | #07 + #08 |
| S16-S21 扩展场景 | 各设计文档 |
### Phase 5E2E 压力测试(并行)
| 任务 | 说明 |
|------|------|
| ST1 Counter 竞争 | 同 agent 并发 |
| ST2 全局上限 | 多 agent 并发 |
| ST3 Broadcast 并发 | 多任务广播 |
---
## 十、约束和风险
### 10.1 约束
1. **只有 E2E spawn 真实 Agent**Unit 和 Integration 层不 spawn
2. **E2E 用真实环境**:真实 daemon + 真实 Agent`e2e-` 前缀隔离
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.2 | 新增 S22 工具链事件中枢 E2E 测试设计(12 个测试),覆盖 #13 §16 事件处理矩阵 |
### 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 | 全三层 + 设计文档正推映射 |
| 文件组织 | 两大文件 | 按模块拆分 + 命名规范 |