# moziplus v2.0 测试操作指南

> 日期：2026-06-05
> 对应设计文档：docs/test-plan-e2e-v27.md v3.0.1
> 测试代码路径：`~/.sanguo_projects/sanguo_moziplus_v2/tests/`

---

## 什么时候跑什么

| 场景 | 命令 | 耗时 | 说明 |
|------|------|------|------|
| **改了某个模块** | `pytest tests/unit/test_spawner.py` | <5s | 只跑改动的模块对应的单元测试 |
| **改了 API 层** | `pytest tests/integration/` | ~1min | 跑全部集成测试 |
| **提交前快速验证** | `pytest -m "not e2e"` | ~2min | 不跑 E2E，验证不破坏现有功能 |
| **部署前全量验证** | `RUN_INTEGRATION=1 pytest` | ~60min | 含 E2E，真实 Agent |
| **只跑 E2E 场景** | `RUN_INTEGRATION=1 pytest tests/e2e/test_e2e_scenarios.py` | ~30min | 串行，一个跑完再下一个 |
| **只跑 E2E 压力** | `RUN_INTEGRATION=1 pytest tests/e2e/test_e2e_stress.py` | ~10min | 并发测试 |

## 按标记筛选

```bash
pytest -m broadcast          # 广播相关
pytest -m mail               # 邮件相关
pytest -m state_machine      # 状态机
pytest -m classify           # Classify Outcome
pytest -m review             # 审查/Rebuttal
```

## 改了什么跑什么（速查表）

| 改的文件 | 跑什么 |
|---------|--------|
| `src/blackboard/db.py` | `pytest tests/unit/test_state_machine.py` |
| `src/daemon/spawner.py` | `pytest tests/unit/test_spawner.py tests/unit/test_classify_outcome.py` |
| `src/daemon/ticker.py` | `pytest tests/unit/test_broadcast_claim.py tests/integration/test_ticker_integration.py` |
| `src/daemon/dispatcher.py` | `pytest tests/unit/test_dispatcher.py` |
| `src/daemon/counter.py` | `pytest tests/unit/test_counter.py` |
| `src/daemon/router.py` | `pytest tests/unit/test_router.py` |
| `src/api/mail_routes.py` | `pytest tests/integration/test_api_mail.py` |
| `src/api/blackboard_routes.py` | `pytest tests/integration/ -k task` |
| `src/daemon/review.py` | `pytest tests/unit/test_review.py tests/integration/test_review_integration.py` |
| `frontend/` | 不在本范围 |

## E2E 怎么跑

### 场景测试（严格串行，一个一个来）

```bash
# 1. 确认 daemon 在线
pm2 list | grep sanguo-moziplus-v2

# 2. 逐个跑场景测试（关键：一个完成再跑下一个）
RUN_INTEGRATION=1 pytest tests/e2e/test_e2e_scenarios.py::TestS9RealAgentDispatch -v
#   → 通过？跑下一个。失败？分析根因、修复、重跑当前，不跳过

RUN_INTEGRATION=1 pytest tests/e2e/test_e2e_scenarios.py::TestS10FullChain -v
#   → 以此类推，逐个推进

# 或者一次跑全部（pytest 默认串行，但无法在失败时停下来分析）
RUN_INTEGRATION=1 pytest tests/e2e/test_e2e_scenarios.py -v --exitfirst
#   --exitfirst 遇到第一个失败就停，方便定位问题
```

**串行原则**：一个 case 跑完 → 收集结果 → 根因分析（如有问题）→ 修复 → 重跑当前 → 通过后再跑下一个。用时间换可靠性。

### 压力测试（场景全通过后）

```bash
RUN_INTEGRATION=1 pytest tests/e2e/test_e2e_stress.py -v
```

### 跑完后检查残留

```bash
curl -s http://localhost:8083/api/projects | python3 -c "
import sys, json
projs = json.load(sys.stdin)
e2e = [p['id'] for p in projs if p['id'].startswith('e2e-')]
print(f'e2e 残留: {len(e2e)} 个') if e2e else print('无残留 ✅')
"
```

## 测试数据清理

```bash
# 手动清理所有 e2e 残留
cd ~/.sanguo_projects/sanguo_moziplus_v2/data
sqlite3 registry.db "DELETE FROM projects WHERE id LIKE 'e2e-%';"
rm -rf e2e-v27-*
```

## 三层测试对照

```
单元测试（快，mock）     → 验证逻辑正确性，随便跑不污染
集成测试（中，tmp_path） → 验证 API + DB 交互，随便跑不污染
E2E（慢，真实 Agent）    → 验证完整链路，需要 RUN_INTEGRATION=1，测完要清理
```

## 关键规则

1. **只有 E2E 会 spawn 真实 Agent**，单元和集成不会
2. **不带 `RUN_INTEGRATION=1` 跑 `pytest` 是安全的**，E2E 全部 skip
3. **E2E 场景测试串行**，一个完成再下一个，失败要分析根因再继续
4. **E2E 压力测试并行**，场景测试全通过后再跑
5. **测试数据用 `e2e-` 前缀**，atexit 兜底清理，手动清理见上方
