auto-sync: 2026-05-17 05:42:52
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
# 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 | ✅ 本文档 | ⬜ 待设计 | ⬜ | — | — |
|
||||
Reference in New Issue
Block a user