> ## Documentation Index
> Fetch the complete documentation index at: https://aicoding.cscitech.top/llms.txt
> Use this file to discover all available pages before exploring further.

# 03-测试审查与修复

> 为 TODO CLI 建立测试矩阵，分类失败，用 Codex 审查差异并完成最小修复、回归验证、质量门禁与安全验收。

## 本页目标

上一页已经为 Python TODO CLI 完成了 `done <序号>` 功能，并补上基础测试。
现在先不提交、不推送，而是把“看起来完成”的改动变成一份有证据、可审查、可回滚的候选交付。

本页继续使用同一个项目：

```text theme={null}
todo-cli/
├── AGENTS.md
├── todo.py
└── test_todo.py
```

`done` 的契约如下：

* 命令是 `python todo.py done <序号>`。
* 序号从 `1` 开始，与 `list` 的显示一致。
* 合法序号只删除对应待办，并打印被删除的内容。
* 非数字、缺参、`0`、负数和越界不得产生 traceback。
* 不引入第三方依赖，不改变已有 `add`、`list` 行为。

本页走完这条闭环：

```text theme={null}
确认基线 → 测试矩阵 → 分层执行 → 失败分类
→ Codex 只读审查 → 分层读 diff → 最小修复
→ 定向回归 → 全量回归 → 质量门禁 → 证据与安全验收
```

<Note>
  Codex 命令、菜单和权限文案可能随版本变化。先运行 `codex --help`，并以本机界面和官方文档为准。
</Note>

## 一、冻结范围并保存基线

### 1. 确认环境

在项目根目录执行：

```bash theme={null}
pwd
git branch --show-current
git status --short
python --version
codex --version
```

PowerShell 中可将 `pwd` 换成 `Get-Location`。
预期类似：

```text theme={null}
/path/to/todo-cli
feat/done-command
 M todo.py
 M test_todo.py
Python 3.12.4
codex-cli 0.x.x
```

确认以下事项：

* 当前目录确实是 `todo-cli`。
* 当前分支不是受保护的 `main`，或团队明确允许在此工作。
* 待审改动只包含预期文件。
* Python 与 Codex CLI 均可启动。

若状态里出现 `.env`、数据库、日志或未知文件，先停止：

```text theme={null}
?? .env
?? todos.db
?? debug.log
```

不要让 Codex “顺手清理”，也不要批量暂存。
先判断它们是否含敏感信息、是否属于他人工作。

### 2. 保存修复前补丁

把当前源码和测试差异保存到仓库外：

```bash theme={null}
git diff --binary -- todo.py test_todo.py > ../todo-before-review.patch
ls -lh ../todo-before-review.patch
```

PowerShell：

```powershell theme={null}
git diff --binary -- todo.py test_todo.py | Out-File -Encoding utf8 ..\todo-before-review.patch
Get-Item ..\todo-before-review.patch
```

预期文件大小大于 `0`。
若为 `0` 字节，说明目标文件相对 `HEAD` 没有差异，空文件不能作为备份。

<Warning>
  补丁可能包含源码和测试数据。应保存在受控目录，不上传公开网盘；测试夹具中不要放真实令牌、个人数据或客户数据。
</Warning>

### 3. 明确非目标

把边界直接交给 Codex：

```text theme={null}
本轮只做测试、审查和必要修复。
允许修改：todo.py、test_todo.py。
禁止：提交、推送、创建 PR、联网、安装依赖、修改 AGENTS.md、
重写 CLI 架构、改变 add/list/done 的公开命令、访问真实生产数据。
所有失败先分类，再提出最小修复；扩大范围前必须停止并说明理由。
```

## 二、建立测试矩阵

测试矩阵把需求、风险、验证层和证据连起来，避免只测正常路径。

### 1. 功能与边界

| 编号  | 场景      | 输入或前置状态                    | 预期结果             | 层级     | 优先级 |
| --- | ------- | -------------------------- | ---------------- | ------ | --- |
| T01 | 删除首项    | `TODOS=["A","B"]`，`done 1` | 删除 `A`，剩 `B`     | 单元     | P0  |
| T02 | 删除末项    | `TODOS=["A","B"]`，`done 2` | 删除 `B`，剩 `A`     | 单元     | P0  |
| T03 | 非数字     | `done abc`                 | 友好提示，无 traceback | 单元/CLI | P0  |
| T04 | 缺少参数    | 仅执行 `done`                 | 打印用法或参数提示        | CLI    | P0  |
| T05 | 序号为 0   | `done 0`                   | 拒绝，不误删末项         | 单元/CLI | P0  |
| T06 | 负数      | `done -1`                  | 拒绝，不误删           | 单元/CLI | P0  |
| T07 | 越界      | 一项待办，`done 2`              | 友好提示，列表不变        | 单元     | P0  |
| T08 | 空列表     | 空列表，`done 1`               | 友好提示，无崩溃         | 单元     | P1  |
| T09 | 保持 add  | 调用 `add("A")`              | 添加成功，输出不变        | 回归     | P1  |
| T10 | 保持 list | 两项待办                       | 仍从 1 开始显示        | 回归     | P1  |
| T11 | 成功输出    | 删除 `A`                     | 输出可识别 `A`        | 单元     | P1  |
| T12 | 依赖边界    | 检查 import                  | 仅使用标准库           | 静态审查   | P1  |

优先级含义：

* **P0**：崩溃、误删或命令契约破坏，失败即禁止交付。
* **P1**：重要回归或约束失守，修复后才能进入发布准备。
* **P2**：可维护性建议，可记录但不得冒充阻断缺陷。

### 2. 验证层级

| 层级     | 要回答的问题                  | 命令或方法                           |
| ------ | ----------------------- | ------------------------------- |
| 静态检查   | 是否有空白、语法和结构问题           | `git diff --check`、`py_compile` |
| 单元测试   | `done()` 的状态与边界是否正确     | `python -m unittest -v`         |
| CLI 冒烟 | 参数路由是否友好、是否泄漏 traceback | `python todo.py ...`            |
| 回归测试   | `add`、`list` 是否保持行为     | 旧测试 + 全量测试                      |
| 差异审查   | 是否有测试没表达的逻辑问题           | `git diff`、`/review`            |
| 安全验收   | 是否越权、泄密或误操作数据           | 人工清单                            |

先让 Codex 只读补漏：

```text theme={null}
只读任务，不修改文件。
读取 AGENTS.md、todo.py、test_todo.py 和当前 git diff。
审查上面的测试矩阵：
1. 补充遗漏的正常、边界、回归和安全场景；
2. 区分自动化测试与人工检查；
3. 为每项给出可观察的通过标准；
4. 不用覆盖率百分比代替行为验证。
```

它至少应注意：

* Python 支持负索引，`0 - 1` 等于 `-1`，可能误删末项。
* `done` 缺参时直接访问 `sys.argv[2]` 可能触发 `IndexError`。

若它建议引入 `pytest`、数据库或重写参数解析，回复：

```text theme={null}
超出本轮范围。保留标准库 unittest 和现有结构，
只补能证明当前契约的最小测试与必要修复。
```

## 三、运行修复前基线

### 1. 空白、语法和全量测试

```bash theme={null}
git diff --check
python -m py_compile todo.py test_todo.py
python -m unittest -v
```

前两条通过时通常无输出，退出码为 `0`。
测试理想输出类似：

```text theme={null}
test_add ... ok
test_done_invalid_number ... ok
test_done_out_of_range ... ok
test_done_removes_item ... ok
test_list_todos ... ok

----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK
```

“5 个测试全绿”不代表矩阵完整。
若缺少 `0`、负数和缺参用例，绿色只说明已有断言通过。

若语法检查出现：

```text theme={null}
Sorry: IndentationError: unexpected indent (todo.py, line 24)
```

先归类为代码/语法失败并修正；模块无法导入时，业务断言没有诊断价值。

### 2. CLI 冒烟

示例程序的 `TODOS` 只存在于当前进程，不能用两次独立命令证明“先添加再删除”。
CLI 冒烟只检查参数路由、输出与不崩溃；状态变化交给同进程单元测试。

```bash theme={null}
python todo.py
python todo.py done
python todo.py done abc
python todo.py done 0
python todo.py done -1
python todo.py done 99
```

错误分支均应：

* 输出用法或友好错误。
* 不出现 `Traceback (most recent call last)`。
* 不静默成功。
* 不访问网络或外部文件。

示例输出：

```text theme={null}
用法：python todo.py [add <内容> | list | done <序号>]
done 需要一个从 1 开始的序号
序号必须是正整数
```

若 `python todo.py done` 抛 `IndexError`，这是确定性产品缺陷，不是环境问题。
记录完整命令和首个异常位置。

## 四、分类失败

看到红色先分类，不要立刻改业务代码。

| 类别    | 典型信号              | 处理原则         | 改产品代码？ |
| ----- | ----------------- | ------------ | ------ |
| 产品缺陷  | 稳定复现且违反契约         | 补回归测试后最小修复   | 通常是    |
| 测试缺陷  | 断言错误、夹具污染状态       | 按公开契约修测试     | 通常否    |
| 环境缺陷  | Python 缺失、路径或权限错误 | 修环境或换干净环境    | 否      |
| 偶发失败  | 同一环境下时过时不过        | 查共享状态、时间、随机性 | 视根因    |
| 范围外失败 | 与当前 diff 无调用关系    | 核对基线并记录      | 未批准不改  |
| 安全阻断  | 要求生产凭据或破坏性命令      | 停止，改用替身数据    | 否      |

每个失败记录为：

```text theme={null}
失败编号：F-01
命令：python todo.py done 0
环境：Python 3.12.4 / Windows 11 / feat/done-command
期望：拒绝 0，列表不变，无 traceback
实际：删除了最后一项
稳定性：连续 3 次复现
位置：todo.py:done
分类：产品缺陷 / P0
假设：index - 1 把 0 变成负索引 -1
下一步：先补 test_done_rejects_zero，再最小修复
```

疑似偶发时只重复目标用例有限次数：

```bash theme={null}
for i in 1 2 3 4 5; do
  python -m unittest -v test_todo.TodoTests.test_done_rejects_zero || break
done
```

PowerShell：

```powershell theme={null}
1..5 | ForEach-Object {
  python -m unittest -v test_todo.TodoTests.test_done_rejects_zero
  if ($LASTEXITCODE -ne 0) { break }
}
```

只要出现一次失败，就不能作为稳定门禁；不要重复到偶然通过为止。

## 五、先补会变红的回归测试

假设确认两个缺陷：

* F-01：`done 0` 误删最后一项。
* F-02：`done` 缺参抛 `IndexError`。

给 Codex：

```text theme={null}
先只修改 test_todo.py，补两个最小回归测试：
- test_done_rejects_zero_and_preserves_items
- test_main_done_requires_index
遵循现有 unittest 风格，隔离全局 TODOS，捕获标准输出。
先运行这两个测试，确认它们因对应缺陷失败。
此阶段不要修改 todo.py，不提交、不推送。
报告命令、失败断言和退出码。
```

预期先得到红灯：

```text theme={null}
FAIL: test_done_rejects_zero_and_preserves_items
ERROR: test_main_done_requires_index

FAILED (failures=1, errors=1)
```

若新测试在修复前已通过，检查是否走到真实分支、断言是否过弱、全局状态是否污染，或缺陷假设是否错误。
不要篡改期望来制造红灯。

测试应隔离全局状态，例如：

```python theme={null}
def setUp(self):
    todo.TODOS.clear()
```

输出断言应检查有意义的契约，并同时断言列表未改变。
除非文案是公开接口，否则不要逐字锁死整句错误提示。

## 六、让 Codex 做只读审查

在项目根目录启动：

```bash theme={null}
codex
```

进入会话后输入：

```text theme={null}
/review
```

选择 **Review uncommitted changes**。
若本机没有该预设，使用：

```text theme={null}
只读审查当前未提交差异，不修改文件。
按 P0、P1、P2 排序，只报告有具体行为影响的问题。
重点检查序号 1 起始与负索引、缺参、非数字、空列表、越界、
测试间全局状态污染、add/list 兼容性、第三方依赖、网络和文件副作用。
每条发现给出文件、位置、触发输入、后果和验证方法。
不评价纯风格问题，不提交、不推送。
```

有效发现应可复现：

```text theme={null}
P0 todo.py:done
index 为 0 时，TODOS.pop(index - 1) 等于 pop(-1)，会删除末项。
用 TODOS=["A","B"]、done("0") 可复现；修复后应保留两项并提示正整数。
```

“代码可能有边界问题”没有位置、触发条件和后果，不是可执行发现。
要求补证据，或降为开放问题。

对每条意见追问：

1. 是否由当前 diff 引入，或会被当前交付影响？
2. 能否用命令、测试或代码路径复现？
3. 修复是否突破本轮范围？

把数据库重构之类长期建议记录到后续，不在本轮实施。

## 七、分五层读 diff

### 第 1 层：范围

```bash theme={null}
git status --short
git diff --stat
git diff --name-only
git diff --numstat
```

预期只有 `todo.py` 与 `test_todo.py`。
出现 `AGENTS.md`、锁文件或生成物时先查原因，范围异常本身就是阻断项。

### 第 2 层：结构

```bash theme={null}
git diff --summary
git diff -- todo.py test_todo.py
```

检查意外重命名、整文件换行变化或无关格式化。
两个边界修复不应重写整个 CLI。

### 第 3 层：逻辑

```bash theme={null}
git diff --function-context -- todo.py
```

沿着这条路径读：

```text theme={null}
sys.argv → cmd 分派 → done 参数 → 数字校验 → 范围校验
→ pop → 输出 → TODOS 最终状态
```

确认参数在访问前检查、`int()` 失败受控、显式拒绝 `< 1`、删除前检查上界、失败路径保持列表不变、成功只删除目标项。

### 第 4 层：测试

```bash theme={null}
git diff --function-context -- test_todo.py
```

确认测试隔离全局状态，同时断言输出和状态；CLI 测试应恢复 `sys.argv` 与标准输出；测试不能复制错误实现逻辑，也不能删除旧用例换取绿色。

### 第 5 层：安全与卫生

```bash theme={null}
git diff --check
git diff --word-diff=plain -- todo.py test_todo.py
```

人工搜索凭据、个人数据、绝对用户路径、生产 URL、网络访问及删除/覆盖文件操作。
可让 Codex 只读复核：

```text theme={null}
检查当前 diff 是否含凭据、个人数据、本机绝对路径、生产地址、
危险文件操作或新增网络访问。逐条引用证据；没有就明确说未发现。
不要修改文件。
```

## 八、最小修复与回归

给 Codex：

```text theme={null}
修复已确认的 F-01 和 F-02，只修改 todo.py，保留回归测试。
要求：删除前拒绝小于 1 的序号；main 读取 done 序号前检查参数数量；
不改公开命令、不引依赖、不重构 add/list。
先跑两个定向测试，再跑全量 unittest，最后运行 git diff --check。
展示命令和结果，不提交、不推送；必须扩大范围时先停止。
```

合理补丁通常只需增加下界判断和缺参判断。
若 diff 突然增长几十行，暂停并要求解释。

### 1. 定向回归

```bash theme={null}
python -m unittest -v \
  test_todo.TodoTests.test_done_rejects_zero_and_preserves_items \
  test_todo.TodoTests.test_main_done_requires_index
```

PowerShell 可写成一行。
预期：

```text theme={null}
test_done_rejects_zero_and_preserves_items ... ok
test_main_done_requires_index ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK
```

一个通过、一个失败时只处理剩余根因，不要继续堆补丁。

### 2. 邻近边界与全量回归

```bash theme={null}
python -m unittest -v \
  test_todo.TodoTests.test_done_invalid_number \
  test_todo.TodoTests.test_done_out_of_range \
  test_todo.TodoTests.test_done_rejects_zero_and_preserves_items
python -m unittest -v
python -m py_compile todo.py test_todo.py
git diff --check
```

预期测试为 `OK`，其余命令无输出且退出码为 `0`。
用例数以实际文件为准，不要为匹配示例硬凑数量。

最后重跑 CLI 冒烟：

```bash theme={null}
python todo.py
python todo.py done
python todo.py done abc
python todo.py done 0
python todo.py done -1
python todo.py done 99
```

所有错误分支都不得出现 traceback。

## 九、质量门禁

| 门禁      | 命令或检查                      | 通过标准        | 失败分支         |
| ------- | -------------------------- | ----------- | ------------ |
| G1 范围   | `git diff --name-only`     | 只有批准文件      | 停止并查明无关文件来源  |
| G2 差异卫生 | `git diff --check`         | 无输出，exit 0  | 只修报告行，不全仓格式化 |
| G3 语法   | `python -m py_compile ...` | 无输出，exit 0  | 先修首个语法/导入错误  |
| G4 定向回归 | 两个缺陷用例                     | 全部 `ok`     | 回到失败分类，不降低断言 |
| G5 全量测试 | `python -m unittest -v`    | `OK`，非 0 用例 | 相关则修；既有则验证基线 |
| G6 审查   | Codex + 人工 diff            | 无未处理 P0/P1  | 每次修复后重跑门禁    |
| G7 安全   | 人工清单                       | 无泄密、越权和副作用  | 不确定即阻断       |

“Ran 0 tests”不是通过：

```text theme={null}
Ran 0 tests in 0.000s

OK
```

此时检查发现规则：

```bash theme={null}
python -m unittest discover -v
python -m unittest -v test_todo
```

若全量测试出现疑似既有失败，可在干净 worktree 对同一 `HEAD` 建基线：

```bash theme={null}
git worktree add ../todo-baseline HEAD
cd ../todo-baseline
python -m unittest -v
```

完成后回原仓库，确认路径再移除：

```bash theme={null}
git worktree remove ../todo-baseline
```

基线也失败则记录为既有问题；基线通过则当前改动是首要嫌疑。
不得用“看起来无关”口头跳过。

## 十、证据记录

“我跑过了”不是证据。最小记录应包含时间、`HEAD`、环境、命令、退出码和摘要。

| 时间    | 阶段  | 命令                 | 结果   | 摘要                 |
| ----- | --- | ------------------ | ---- | ------------------ |
| 10:05 | 基线  | `git diff --check` | PASS | 无输出，exit 0         |
| 10:06 | 基线  | `py_compile`       | PASS | 无输出，exit 0         |
| 10:07 | 修复前 | 两个回归测试             | FAIL | 1 failure, 1 error |
| 10:14 | 修复后 | 两个回归测试             | PASS | Ran 2, OK          |
| 10:15 | 修复后 | 全量测试               | PASS | Ran N, OK          |
| 10:18 | 审查  | Codex `/review`    | PASS | 无未处理 P0/P1         |
| 10:20 | 安全  | 人工清单               | PASS | 未发现敏感信息或副作用        |

记录环境与差异指纹：

```bash theme={null}
git rev-parse HEAD
git status --short
python --version
git diff --stat
git diff --check
```

不要在公开记录中粘贴令牌、用户名目录、内部主机名或客户数据。

让 Codex 输出最终报告时使用：

```text theme={null}
不要再修改文件。只基于实际结果报告：
修改文件及目的；修复前红灯；修复后定向、全量、语法和 diff 检查；
/review 的 P0/P1 及处理状态；安全结果；未验证项与残余风险；
明确说明未提交、未推送。不得声称未执行的检查已通过。
```

仍需人工对照终端输出，不要只看 Codex 复述。

## 十一、失败分支与停止条件

### 无法复现

要求 Codex 使用同一目录、命令和版本：

```text theme={null}
先打印当前目录、python --version 和 git status --short，
再原样执行 python todo.py done 0。
若仍无法复现，停止修改并列出环境差异。
```

无法复现时不要做“可能性修复”。

### 需要真实凭据或生产数据

立即停止，改用内存替身、脱敏夹具或专用测试账号。
无法安全验证时标记 `BLOCKED`，写明所需授权与隔离条件，不要把真实密钥贴进会话。

### 修复开始扩张

以下情况应停止：

* 为两个边界判断引入新框架。
* 改变公开命令格式。
* 修改无关的 `add`、`list`。
* 删除测试、降低断言或跳过失败用例。
* 新增网络、数据库或文件副作用。

返回测试矩阵，重新要求最小方案。

## 十二、回滚

回滚前先区分本轮改动与用户原有工作，禁止使用 `git restore .` 或 `git reset --hard`。

先检查补丁适用方向：

```bash theme={null}
git apply --check ../todo-before-review.patch
git apply --check -R ../todo-before-review.patch
```

根据当前状态选择一个方向：

```bash theme={null}
# 在匹配基线时恢复保存的补丁
git apply ../todo-before-review.patch

# 在当前状态确实包含该补丁时反向移除
git apply -R ../todo-before-review.patch
```

不要把两条当作固定配方连续执行。
只撤销 Codex 刚做的 hunk 时，优先使用 IDE diff 逐块回退或手工编辑。

只有确认目标文件不含任何需保留的旧改动时，才可执行：

```bash theme={null}
git restore --source=HEAD -- todo.py test_todo.py
```

回滚后仍要验证：

```bash theme={null}
git status --short
git diff --check
python -m unittest -v
```

本页没有提交，因此不需要 `git revert`，更不要改写历史。

## 十三、安全验收

进入下一页前，由人逐项确认：

* [ ] 当前目录、分支和身份正确。
* [ ] diff 只包含批准文件。
* [ ] 修复前回归测试确实变红。
* [ ] 修复后定向测试与全量测试通过，且不是 0 个用例。
* [ ] `py_compile` 与 `git diff --check` 通过。
* [ ] 缺参、非数字、0、负数和越界均无 traceback。
* [ ] `add`、`list` 行为未改变。
* [ ] Codex `/review` 无未处理 P0/P1。
* [ ] 人工按范围、结构、逻辑、测试、安全五层读过 diff。
* [ ] 无真实凭据、个人数据、生产地址和本机绝对路径。
* [ ] 无新增网络、第三方依赖和外部数据副作用。
* [ ] 已记录命令、环境、结果、失败分类和残余风险。
* [ ] 已准备仓库外补丁或明确的逐块回滚方法。
* [ ] 未提交、未推送、未创建 PR、未合并、未发布。

任何一项不满足，就使用 `BLOCKED` 或 `PARTIAL`，并写清缺少的证据。

## 最终通过标准

合格结论应类似：

```text theme={null}
结论：PASS，可进入提交准备，但尚未提交或推送。
范围：todo.py、test_todo.py。
修复：拒绝 0/负数，done 缺参返回友好提示。
证据：
- 修复前回归测试为 1 failure + 1 error；
- 修复后定向测试 Ran 2, OK；
- 全量测试 Ran N, OK；
- py_compile 与 git diff --check 通过；
- 本地 Codex /review 无未处理 P0/P1；
- 人工安全检查未发现凭据、生产数据、网络或文件副作用。
残余风险：TODO 仅内存存储，未验证跨进程持久化，因为不属本轮需求。
回滚：已保存仓库外补丁 todo-before-review.patch。
```

## 小结

本页用四个问题收口：

1. **证明什么**：矩阵覆盖正常、边界、回归和安全风险。
2. **失败说明什么**：区分产品、测试、环境、偶发、范围外与安全阻断。
3. **改动是否可信**：Codex 只读审查，人按五层 diff 复核。
4. **能否进入下一阶段**：定向与全量回归、门禁、证据和回滚全部到位。

在这个 TODO CLI 中，关键风险是 `0` 被负索引解释成末项，以及缺参直接崩溃。
先让回归测试稳定变红，再用最小修复变绿，最后证明没有破坏相邻行为。

下一页进入 Git 提交、发布与复盘。此时仍保持未提交、未推送，并带上本页的测试证据和安全验收清单。

参考资料：`参考/codex/34-capstone.md`、`参考/codex/14-workflows.md`、`参考/codex/26-git-github.md`。动态信息以本地 `--help` 与官方文档为准。
