项目上线前夜,测试群里出现这样的对话:
测试:"P0 用例全绿了,发吧?" Leader:"等一下——昨天的变更改了一个接口字段,影响范围看过了吗?" 测试:"我手动回归了主流程……" Leader:"那怎么确认没有遗漏?有没有一张表能告诉我所有需求都有对应测试?"
答不上来。因为没有。
这就是大多数项目的真实状态:测试用例写了一堆,但没人能回答"够不够"。因为测试用例是边开发边补的,需求变了几轮,测试用例有没有跟着变?不知道。某条 P0 需求是不是一条测试都没覆盖?不知道。
在 MumuSpec 体系里,有两个文档专门解决这个问题:
它们配合使用:13 写测试用例,14 用一张矩阵验证全覆盖。13 是"做事",14 是"对账"。
很多人对 13 的第一反应是"写测试用例的文档"。不对。
13 的核心问题不是"测什么",而是"怎样算通过"。
层级 | 工具 | 覆盖对象 | 一句话 |
|---|---|---|---|
L1 Unit | pytest | 单个函数/类的逻辑 | 函数是对的 |
L2 Integration | pytest + test_client | API 端点 + 存储 | 接口是对的 |
L3 Contract | pytest + schema 断言 | 响应体结构与 09 一致 | 字段是对的 |
L4 E2E | Playwright / Cypress | 完整用户旅程 | 用户走通了 |
每一层回答不同的问题。L1 说"函数没崩",L2 说"接口返回了",L3 说"返回的字段名和类型和 09 一致",L4 说"用户在页面上真的能走完全流程"。
缺任何一层,你都不知道问题出在哪。只跑 L2 可能发现"接口 200 了",但字段类型不对——L3 才能捕获。
写 TC 的时候不要"一条用例测所有",而是每个可能失败的原因拆成一条:
来源 | 计数方法 | 示例 |
|---|---|---|
正常路径 | 每个端点 happy path = 1 条 | POST /ask → 200= TC-001 |
错误码 | 每个 distinct error code = 1 条 | EMPTY_QUERY= TC-002 |
分支路径 | 每个 if/else 降级分支 = 1 条 | 无 API Key → demo = TC-004 |
类型约束 | 09 约定的字段类型 = 1 条 | llm_used必须 bool = TC-006 |
这样做的价值:失败时不看代码就知道哪里坏了。
拿 TC-M01-002 为例:
TC-ID | 层级 | 测试函数 | 断言要点 | 关联 REQ |
|---|---|---|---|---|
TC-M01-002 | Integration | test_ask_empty_query | 空 query → 400, EMPTY_QUERY | REQ-M1QA-002 |
这条失败了,不需要 debug,直接知道"POST /ask 的空 query 校验出了问题"。
13 不只是写测试用例,它还要定义什么条件才算"门禁通过":
Gate-ID | 条件 | 阻塞合并 |
|---|---|---|
G-LINT | 代码风格检查通过 | 是 |
G-UNIT | Unit 测试全绿 | 是 |
G-INT | Integration 测试全绿 | 是 |
G-AC | P0 验收标准全覆盖 | 是 |
G-PERF | response_time_ms < 5000 | 否(warning) |
G-AC 是关键:它要求 05 用户故事里所有 P0 的 AC 都有对应测试用例。如果某条 P0 的 AC 在 13 里找不到 TC → 门禁直接打回。
很多团队对 TDD 有执念。13 给出了务实的判断标准:
场景 | TDD 价值 | 建议 |
|---|---|---|
简单 CRUD(列表、删除) | 低 | 写完补测试 |
参数校验 / 错误码 | 中 | 建议 TDD |
多级降级 / 状态机 | 高 | 强烈建议 TDD |
金融计算 / 风险判定 | 极高 | 必须 TDD |
核心理念:逻辑越复杂,越应该先写测试再写代码。
如果说 13 是"写用例",那 14 就是"对账"。
14 只有一张表,但解决的问题是项目里最头疼的:
"我怎么知道没有遗漏?"
阅读方向:需求 → 用户故事 → API 端点 → 测试用例
需求编号 | 用户故事来源 | API 端点 | 测试用例 | 覆盖状态 |
|---|---|---|---|---|
REQ-M1QA-001 | US-001 会话管理 | GET/POST /sessions、DELETE /sessions/<id> | TC-M01-020+ | ❌ 待补 |
REQ-M1QA-002 | US-002 问答提交 | POST /api/v1/agent/ask | TC-M01-001 ~ 004、006 | ✅ 已实现 |
REQ-M1QA-003 | US-003 历史记录 | GET /sessions/<id>/records | TC-M01-030+ | ❌ 待补 |
REQ-M1QA-004 | US-004 能力探测 | GET /api/v1/agent/capabilities | TC-M01-005 | ✅ 已实现 |
一眼就能看出谁没测。不需要翻代码,不需要问测试"这个测了没"——矩阵上写得清清楚楚。
阅读方向:每条测试用例 → 它保证哪条需求
测试用例 | 关联需求 | 断言要点 |
|---|---|---|
TC-M01-001 | REQ-M1QA-002 | POST /ask→ 200,含 answer、llm_used、traceId |
TC-M01-002 | REQ-M1QA-002 | 空 query → 400 EMPTY_QUERY |
TC-M01-005 | REQ-M1QA-004 | GET /capabilities→ 200,字段与 09 一致 |
反过来查也一样清楚:这条测试用例为什么要写?因为它保证了某条需求。
# | API 端点 | 方法 | 关联测试用例 | 覆盖状态 |
|---|---|---|---|---|
1 | /api/v1/agent/capabilities | GET | TC-M01-005 | ✅ |
2 | /api/v1/agent/ask | POST | TC-M01-001 ~ 004、006 | ✅ |
3 | /api/v1/agent/sessions | GET | TC-M01-020+ | ❌ 待补 |
4 | /api/v1/agent/sessions | POST | TC-M01-021+ | ❌ 待补 |
5 | /api/v1/agent/sessions/<id> | DELETE | TC-M01-024+ | ❌ 待补 |
6 | /api/v1/agent/sessions/<id>/records | GET | TC-M01-030+ | ❌ 待补 |
每个 API 端点至少有一条测试。这是 14 的一条底线规则。如果发现某个端点在 14 里"待补",说明上线前必须补上。
维度 | 已覆盖 | 总数 | 覆盖率 |
|---|---|---|---|
API 端点 | 2 | 6 | 33% |
REQ 需求条目 | 4 | 5 | 按 TC 逐项核对 |
TC 测试用例 | 7 | 视 13 目标 | 待统计 |
发布前用 14 查"有没有需求没测试、有没有 API 没 owner"。这是最后一道防线。
两个文档的分工很清楚:
13 测试策略 | 14 追溯矩阵 | |
|---|---|---|
回答什么 | 怎样算通过 | 有没有遗漏 |
做啥 | 写测试用例、定义门禁 | 建 REQ↔US↔API↔TC 映射 |
谁写 | 测试 | 测试 |
输入来自 | 05 AC + 09 API | 05 + 09 + 13 |
输出给谁 | 14 追溯矩阵 + CI 门禁 | 发布评审 |
门禁作用 | G-AC: P0 全覆盖 | 行级覆盖率检查 |
写作顺序:
05 UserStory(含 AC) ↓09 API 字段定义 ↓13 写 TC,每条 TC 注明关联的 REQ 和 AC ↓14 拉全量矩阵,逐行核对覆盖状态 ↓发现"待补" → 回到 13 补 TC → 回到 14 更新状态13 是"做事",14 是"对账"。没有 13,14 是一张空表;没有 14,13 是一堆用例但没人知道够不够。
很多人认为"AI Coding 不需要写测试用例了,让 AI 自己测"。这是误解。
AI 生成代码后,测试是唯一告诉你"代码对不对"的客观依据。但前提是测试用例必须先于代码定义好。
13 的测试用例来自 05 的 AC。如果 AC 写得模糊("用户能正常使用"),13 就没办法写精确的断言。AC 是什么断言,TC 就是什么断言。
14 的矩阵依赖 13 的 TC-ID。先有 TC,再有矩阵。不能先画矩阵再补 TC——矩阵必须反映真实覆盖。
这是 14 作为"发布前对账表"的硬性要求。P0 行有 ❌ → 不能上线。
13 和 14 是 MumuSpec 体系里最"无聊"的两个文档——没有炫酷的架构图,没有令人兴奋的新功能描述。它们只是表:一张定义通过标准的表,一张核对有没有遗漏的表。
但项目上线前夜,你最需要的就是这两张表。
13 + 14 合在一起回答了一个问题:
"你说的都做完了,怎么证明?"