# v2.6 测试计划 **版本**: v2.6.0-test-plan **作者**: 司马懿(质量总监)🗡️ **日期**: 2026-05-17 **状态**: F1-F5 已评审,F6-F18 待编码 --- ## 一、测试基础设施 ### 1.1 当前配置 - **框架**: pytest(已有 `tests/` 目录,97 个用例全部通过) - **Fixture**: `tmp_path`(SQLite 临时数据库)、`TestClient`(FastAPI) - **Mock 策略**: 目前无 mock,全部用真实 SQLite 内存/临时文件 - **运行方式**: `python3 -m pytest tests/ -q` ### 1.2 测试分层 | 层级 | 覆盖范围 | 何时执行 | |------|---------|---------| | **单元测试** | 每个 F 的内部逻辑 | 每写完一个 F | | **集成测试** | F1-F14 端到端链路 | F14 完成后 | | **E2E 验收** | 完整系统含前端 | F18 完成后 | ### 1.3 测试规范 - 每个 F 必须有对应的 `test_*.py`,测试文件与模块同名 - 测试类按功能分组:`class TestTaskCRUD`、`class TestTransitions` 等 - P0 测试必须有,P1 测试建议有但不强阻塞 - 测试必须可重复运行(不依赖外部状态、不依赖时序) - 新增测试必须在 `tests/` 目录下,不放在 `src/` 内 --- ## 二、F1-F5 单元测试评审(已完成) ### 现有覆盖情况 | F | 测试文件 | 用例数 | P0 覆盖 | 评审 | |---|---------|--------|---------|------| | F1 骨架 | `test_main.py` | 10/10 | ✅ 启动、健康端点、配置、Swagger | 通过 | | F2 黑板 | `test_blackboard.py` | 39/39 | ✅ CRUD、状态机、并发、WAL、事件 | 通过(附 2 条意见) | | F3 多项目 | `test_registry.py` | 11/11 | ✅ 创建/删除/归档/持久化/黑板的联动 | 通过 | | F4 CLI | `test_cli.py` | 14/14 | ✅ 8 子命令 + admin + 错误处理 | 通过 | | F5 API | `test_api.py` | 23/23 | ✅ 全端点 CRUD + 状态码 + SSE 连接 | 通过(附 1 条意见) | **总计**: 97/97 通过,质量合格。 ### 评审意见(F1-F5) #### BUG-1 [P1] `/api/daemon/status` 端点重复注册 - **位置**: `main.py` 第 99-111 行直接定义了 `daemon_status` 端点,同时 `daemon_routes.py` 也定义了同名路由 - **现象**: pytest 输出 `Duplicate Operation ID daemon_status` warning - **修复**: 删除 `main.py` 中的 `/api/daemon/status` 端点定义,统一由 `daemon_routes.py` 提供 - **影响**: 不影响功能,但 Swagger 文档会混乱 #### OBS-1 [P2] `operations.py` 的 `_conn()` 每次操作都新建连接 - **位置**: `operations.py` 全文,每次方法调用 `self._conn()` → `sqlite3.connect()` - **现状**: 单次操作用完即关,连接不池化 - **评估**: v2.6 阶段可接受(SQLite WAL + busy_timeout 足够),但如果后续 F6 ticker 30s 轮询 + 多项目并发,建议 F6 阶段引入连接池或 `per-thread` 连接复用 - **行动**: 不阻塞当前进度,F6 编码时再评估性能 #### OBS-2 [P2] `queries.py` 的 `blocked_tasks_with_deps()` 和 `pending_dispatchable()` 存在 SQL 注入风险 - **位置**: `queries.py` 第 52 行和第 71 行,使用 `f-string` 拼接 SQL - **现象**: `f"SELECT ... WHERE id IN ({','.join('?' * len(deps))})"` — 虽然 `deps` 来自数据库的 `depends_on` 字段(不是用户输入),且用了参数化占位符,但 `f-string` 拼接 SQL 是坏习惯 - **评估**: 当前实际安全(因为值都来自参数化 `?`),但风格不好 - **行动**: 不阻塞,但后续统一改为纯参数化写法 --- ## 三、F6-F18 测试计划(逐模块) ### F6 Daemon Ticker — `test_ticker.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | tick 循环正常运行 | ✅ | 30s 间隔(测试中加速),不阻塞 event loop | | T2 | scan_tasks 检测 pending | ✅ | pending → 触发调度 | | T3 | 依赖推进 | ✅ | blocked → pending(依赖 done 后) | | T4 | events 写入 | ✅ | 每次 tick 写 `daemon_tick` 事件 | | T5 | 多项目轮询 | ✅ | tick 遍历所有 active 项目 | | T6 | tick 异常不中断 | P1 | 单次 tick 抛异常 → 下次 tick 继续 | | T7 | 手动 tick 端点 | P1 | POST /api/daemon/tick 触发即时 tick | ### F7 Inbox JSONL Watcher — `test_inbox.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 写入+消费 | ✅ | Agent 写 JSONL → 1s 内 Daemon 读取并处理 | | T2 | truncate | ✅ | 消费后清空文件 | | T3 | 并发写入 | ✅ | 多 Agent 同时写不同行 | | T4 | 损坏行恢复 | P1 | 非法 JSON 行跳过不崩溃 | | T5 | 空文件处理 | P1 | 空文件不触发处理 | ### F8 健康检查 — `test_health.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 正常场景 | ✅ | 有变更 → 无告警 | | T2 | 僵尸检测 | ✅ | 连续 N tick 无变更 → observation 写入 | | T3 | 恢复场景 | ✅ | 僵尸后有变更 → 告警解除 | | T4 | 多项目独立检测 | P1 | 项目 A 僵尸不影响项目 B | ### F9 Agent 调度器 — `test_dispatcher.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 三级决策树 | ✅ | Daemon task → Full task → Sub task 正确分流 | | T2 | 调度不阻塞 | ✅ | spawn 用 `asyncio.create_subprocess_exec` | | T3 | 队列满拒绝 | ✅ | active agent 数达上限 → 不调度 | | T4 | 任务优先级排序 | P1 | 高优先级先调度 | ### F9 Agent Spawner — `test_spawner.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | spawn 成功 | ✅ | 子进程启动 + session 注册 | | T2 | 超时处理 | ✅ | 超时后 kill + task_attempt 记录 | | T3 | spawn 失败 | ✅ | 命令不存在 → task_attempt 记录 spawn_failed | | T4 | session 清理 | P1 | 子进程结束后 session 归档 | ### F10 ActiveAgentCounter — `test_counter.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 全局上限 | ✅ | max_global=5 时第 6 个 acquire 失败 | | T2 | per-agent 串行 | ✅ | 同一 agent 第二个 acquire 排队 | | T3 | release 恢复 | ✅ | release 后等待中的 acquire 成功 | | T4 | 并发竞争 | P1 | 多 agent 同时 acquire 不超限 | ### F11 Bootstrap 拼装 — `test_bootstrap.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 各 role 拼装 | ✅ | 不同 role 得到不同 L0-L3 四层 | | T2 | token 估算 | ✅ | 拼装结果 < 4096 tokens | | T3 | 缺失组件降级 | P1 | 某层文件不存在 → 跳过不崩溃 | | T4 | 模板变量替换 | P1 | `{{task_id}}` 等占位符正确替换 | ### F12 审查流水线 — `test_review_flow.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 4 级分流 | ✅ | high/standard/low/research 正确路由 | | T2 | confidence 计算 | ✅ | 分值在 0-1 区间,阈值逻辑正确 | | T3 | 升级到庞统 | ✅ | confidence < 阈值 → 升级 | | T4 | 审查通过 → done | ✅ | verdict=approved → 状态变 done | | T5 | 审查拒绝 → 重新执行 | P1 | verdict=rejected → 回到 working | ### F13 Guardrail — `test_guardrail.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | L1 assert 通过 | ✅ | 合规产出 → 放行 | | T2 | L1 assert 失败 | ✅ | 不合规产出 → 拦截 | | T3 | L2 subagent 触发 | ✅ | 高风险 → 二次确认 | | T4 | 配置加载 | P1 | `guardrails.yaml` 正确解析 | | T5 | 误杀恢复 | P1 | L2 确认通过 → 放行 | ### F14 反驳权 — `test_rebuttal.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 反驳触发 | ✅ | review rejected → 反驳协商开始 | | T2 | 轮次计数 | ✅ | round 递增正确 | | T3 | 超轮次升级 | ✅ | 超过 max_rounds → 庞统裁决 | | T4 | 结果写入 | ✅ | 最终裁决写入 reviews 表 | ### F15 经验沉淀 — `test_experience.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 一级蒸馏 | ✅ | 任务完成 → experiences 表写入 | | T2 | tag 匹配 | ✅ | 相似 tag 的经验可被查询到 | | T3 | 二级触发 | P1 | N 条同类经验 → Skill 草稿生成 | | T4 | 过期清理 | P1 | deprecated 经验不被查询 | ### F16 Skill 体系 — `test_skill_loader.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 加载 + 验证 | ✅ | 四要素完整才加载 | | T2 | per-project 覆盖 | ✅ | 项目级 Skill 覆盖全局 Skill | | T3 | 缺要素拒绝 | P1 | 缺少 name/description/triggers/actions → 拒绝 | | T4 | 版本管理 | P1 | 同名 Skill 版本号正确比较 | ### F17 SSE + Hook — `test_sse.py` / `test_hooks.py` | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | SSE 连接 | ✅ | 客户端连接 → 收到初始事件 | | T2 | SSE 4 级推送 | ✅ | task/agent/daemon/system 四级 | | T3 | 断线重连 | P1 | 断线后重连 → 收到后续事件 | | T4 | 降级轮询 | P1 | SSE 不可用 → 轮询兜底 | | T5 | Hook 触发 | ✅ | 3 个触发点正确执行 | | T6 | Hook 过滤 | P1 | 条件不满足 → 不触发 | ### F18 前端 Dashboard — `test_frontend.py`(E2E) | # | 测试用例 | P0 | 验证点 | |---|---------|-----|-------| | T1 | 页面渲染 | ✅ | 5 个页面均可加载 | | T2 | API 集成 | ✅ | 页面操作 → API 调用正确 | | T3 | SSE 实时更新 | ✅ | 后端事件 → 前端 UI 更新 | | T4 | 项目切换 | ✅ | 切换项目 → 数据刷新 | --- ## 四、集成测试计划(F14 完成后) ### 端到端链路测试 ``` 用户提需求 → 庞统规划 → 任务写入黑板 → Agent 执行 → Guardrail 检查 → 审查流水线 → 反驳协商 → 完成 → 经验沉淀 ``` | # | 场景 | 关键验证 | |---|------|---------| | I1 | 正常流程(happy path) | 全链路无阻断,最终 done | | I2 | 依赖链 | A→B→C,A done 后 B 自动 pending,B done 后 C 自动 pending | | I3 | 审查拒绝 → 反驳 → 通过 | review rejected → rebuttal → verdict 翻转 | | I4 | 审查拒绝 → 反驳 → 超轮次 → 庞统裁决 | max_rounds 后升级 | | I5 | Guardrail 拦截 → L2 确认 → 放行 | L1 fail → L2 confirm → proceed | | I6 | Agent 僵尸检测 | 无 heartbeat → observation → reclaim | | I7 | 多项目并行 | 项目 A/B 同时运行,互不干扰 | | I8 | 失败重试 | failed → pending(retry_count +1)→ 再次执行 | ### 集成测试基础设施 ```python # conftest.py 需要的 fixture @pytest.fixture def full_stack(tmp_path): """完整系统:FastAPI + Ticker + Dispatcher + Spawner(mock)""" ... @pytest.fixture def mock_spawner(): """Mock spawner,模拟 Agent 执行""" ... ``` --- ## 五、E2E 验收场景(F18 完成后) | # | 场景 | 验收标准 | |---|------|---------| | E1 | 创建项目 → 提交任务 → 查看任务板 | Dashboard 实时更新 | | E2 | 全局监控页 | 所有项目状态汇总、活跃 Agent 数、最近事件 | | E3 | 产物仓库页 | 任务产出物可浏览、可下载 | | E4 | 系统配置页 | 运行时配置可查看、Skill 列表可管理 | | E5 | AI 简报页 | 项目级 AI 总结可生成 | | E6 | SSE 实时 | 所有页面操作后 SSE 实时反映 | --- ## 六、测试通过标准 ### 每个 F 的单元测试通过标准 1. **全部 P0 用例通过**(硬性要求) 2. **P1 用例通过率 ≥ 80%**(可协商) 3. **无 crash / 无未捕获异常** 4. **测试可重复运行**(跑 3 次结果一致) ### 集成测试通过标准 1. **I1-I8 全部场景通过** 2. **无死锁、无资源泄漏** 3. **并发测试无数据竞争** ### E2E 验收标准 1. **E1-E6 全部场景通过** 2. **前端无 console error** 3. **SSE 延迟 < 2s** --- ## 七、进度追踪 | F | 测试计划 | 用例设计 | 编码 | 执行 | 评审 | |---|---------|---------|------|------|------| | F1 | ✅ | ✅ 10 | ✅ | ✅ 10/10 | ✅ 通过 | | F2 | ✅ | ✅ 39 | ✅ | ✅ 39/39 | ✅ 通过(附 2 条意见) | | F3 | ✅ | ✅ 11 | ✅ | ✅ 11/11 | ✅ 通过 | | F4 | ✅ | ✅ 14 | ✅ | ✅ 14/14 | ✅ 通过 | | F5 | ✅ | ✅ 23 | ✅ | ✅ 23/23 | ✅ 通过(附 1 条意见) | | F6 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F7 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F8 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F9 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F10 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F11 | ✅ 本文档 | ⬜ 本文档 | ⬜ | — | — | | F12 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F13 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F14 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F15 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F16 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F17 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — | | F18 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — |