# TDD 红旗与合理化反驳表

> 用途：写实现代码前自检。任何一条命中 = 停手，删掉先写的代码，从失败测试重来。
> 铁律：`NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST`。违反字面 = 违反精神，没有"就这一次"。
> 适用：新功能、改 bug、重构、行为变更。例外（须问人）：一次性原型、生成代码、配置文件。
> 出处对标 obra/superpowers test-driven-development（已 curl 200 核验），本表为科研代码场景的加强与本地化。

## 一、红旗信号清单（命中即停，删码重来）

- 先写了实现代码，再补测试
- 测试首次运行就通过（→ 你在测已有行为或测了拼写，不是测新功能）
- 说不清这个测试为什么会失败
- "测试稍后补"
- "先留作参考 / 在写测试时顺手改改它"（→ 本质是 test-after，删means删）
- "已经手动测过所有边界了"
- "测试后补也能达到同样目的，重在精神不在仪式"
- "都花了 X 小时了，删掉太浪费"（→ 沉没成本）
- "TDD 太教条，我是在务实"
- "这个情况不一样，因为……"
- 测试需要 mock 一大堆才能跑（→ 不是测试问题，是耦合/设计问题）
- 测试 setup 巨大无比
- 测试名里带 "and"（validates_email_and_domain → 该拆成两个）

## 二、合理化借口 → 现实反驳表

| 借口 | 现实反驳 |
|---|---|
| "太简单了不用测" | 简单代码也会坏。一个测试 30 秒。 |
| "我待会儿补测试" | 后补的测试立刻就过，证明不了任何事——可能测错对象、测了实现而非行为、漏了你忘掉的边界。 |
| "后补测试能达到同样目的" | 不能。后补回答"它做了什么"，先写回答"它应该做什么"。后补被你的实现带偏，只验你记得的边界，不发现新边界。 |
| "已经手动测过了" | 手动是 ad-hoc：无记录、不能重跑、改代码后没人重测、压力下必漏。自动化测试每次都一样地系统跑。 |
| "删掉 X 小时的代码太浪费" | 沉没成本谬误。时间已花掉。真正的浪费是留着你不敢信的代码——没有真测试的"能跑代码"就是技术债。 |
| "留作参考，先写测试" | 你一定会去抄它适配它，那就是 test-after。删means删，从测试新写。 |
| "得先探索一下" | 可以。探索完整段扔掉，再用 TDD 从头来。探索产物不进生产。 |
| "测试很难写" | 听测试的话：难测 = 难用。是接口/设计该简化，不是测试该跳过。 |
| "TDD 拖慢我" | TDD 比事后调试快。务实 = 先写测试，少在生产里 debug。 |
| "现有代码本来就没测试" | 你正在改它，就给它补测试。 |
| "时间紧/老板要现在就要" | 系统化比瞎试快。 |

## 三、科研代码特有红旗（本地加强，参考库未覆盖）

- **"反正结果对就行，指标我跑出来看着合理"** → 没有断言固定的 fixture，下次改预处理就静默变了。给关键指标/形状/确定性划分写回归测试。
- **"用真实大数据集测"** → 太慢且不确定。先用小的合成 fixture（固定随机种子）测逻辑边界，真数据留集成测试单独跑。
- **"模型训练没法测"** → 可测的是：数据 shape/dtype、loss 单步下降、forward 输出范围、种子固定下两次前向一致、保存/加载往返一致。别测随机收敛值，测确定性契约。
- **"浮点数没法 assert 相等"** → 用 `pytest.approx` 或 `numpy.testing.assert_allclose(rtol=...)`，不是不测。
- **"加了 mock 就绿了"** → 检查你是否在测 mock 的行为而非真实代码（test-anti-pattern）。能用真实代码就别 mock。

## 四、卡住时的对照动作

| 问题 | 动作 |
|---|---|
| 不知道怎么测 | 先写出你希望的 API 调用样子；先写断言再倒推 |
| 测试太复杂 | 设计太复杂——简化接口 |
| 必须 mock 一切 | 代码耦合太重——用依赖注入 |
| setup 巨大 | 抽 helper/fixture；还复杂就是设计该简化 |
| 浮点/随机 | 固定种子 + `approx`/`assert_allclose` |

## 五、完成前验证清单（全勾才算 TDD）

- [ ] 每个新函数/方法都有测试
- [ ] 实现前**亲眼看每个测试失败过**
- [ ] 每个测试失败的原因符合预期（功能缺失，不是 typo/import 错）
- [ ] 写了让测试通过的**最简**实现（无 YAGNI 扩展）
- [ ] 全部测试通过、输出干净（无 error/warning）
- [ ] 测试用真实代码（mock 仅在不可避免时）
- [ ] 边界与异常路径有覆盖

勾不全 = 你跳过了 TDD，删码重来。
