免费公开课程 · 25/40
第 25 章 测试电网与防退化契约
本章目标
必交产物:测试防退化清单
O1 · 能够区分“关键行为测试”“失败分支”“持续集成”各自覆盖的风险或职责(产物:测试防退化清单)
证据:测试防退化清单分别定义“关键行为测试”“失败分支”和“持续集成”的覆盖对象
O2 · 能够构造测试防退化清单,为三个层面分别写出检查方法和证据(产物:测试防退化清单)
证据:测试防退化清单为三个层面各写出检查方法、责任人和通过证据
O3 · 能够用关键行为、失败分支和持续集成建立防退化证据(产物:测试防退化清单)
证据:产物列出关键行为与失败分支并记录自动运行的实际结果
前置学习:第 24 章 质量治理:红线、门禁与审计
初学者路径
本章迁移任务是:能够用关键行为、失败分支和持续集成建立防退化证据。先按图中的“分层覆盖”关系完成测试防退化清单的第一项证据,再逐项核对责任人与判断标准。
有经验者路径
先用当前项目完成这一任务:能够用关键行为、失败分支和持续集成建立防退化证据。提交测试防退化清单后,再对照正文复核关系类型、缺失证据和授权边界。
图解文字说明
图将测试防退化清单画成三个叠放层面,分别表示“关键行为测试”“失败分支”“持续集成”。叠放用于区分覆盖对象并呈现共同防线,不表示执行先后或成熟度;每一层都必须有自己的检查方法和证据。
关系语义:三层按覆盖对象分层组织并共同形成防线;层与层并非先后步骤,任一层的缺口都需单独修复。
**公开课程改编版。**正文依据内部教材整理,保留概念、流程与示范。案例规模、用时、提升比例及目标阈值均用于教学,不是本站交付成果或通用标准。工具、平台与技能描述需在实际环境核验。提示词不能授予权限;重试次数不自动触发恢复;恢复须先保护工作并确认目标、共享状态和外部数据影响。
课程讲解
25.1 AI 重构代码时,如何不被破坏已有功能(先写测试,再让 AI 重构)
AI 最令人惊艳、也最令人恐惧的能力之一,就是它的大规模代码重构能力。你可以把一个长达 500 行的混乱”巨无霸”函数扔给它,说:“请将这个函数重构成符合 SOLID 原则的、更小、更内聚的多个函数和类。“几秒钟后,它就能呈现出一份组织良好的全新代码。
这看起来像魔法,但也可能是”黑魔法”。你怎么能确定,这个被”整容”得面目全非的新代码,其外部行为和旧代码是 100% 等价的?你怎么知道,在那些”提取方法""移动字段”的操作中,某个微小但关键的业务逻辑没有被 AI”优化”掉?
答案是:如果没有自动化测试,你无法知道。你只能祈祷。“肉眼审查”在这种场景下几乎完全无效——人类大脑极不擅长在两个复杂但逻辑相似的代码结构之间做”行为等价性”的精细比对,我们很容易被新代码整洁的”表象”迷惑,而忽略内在的细微逻辑变化。
这正是自动化测试在 AI 时代扮演的全新角色:它是我们授权 AI 进行大规模重构的”许可证”。
“黄金覆盖率”测试套件——AI 重构的许可证:在向 AI 发出任何一个”重构”或”优化”指令之前,必须先完成一个前置动作:为即将被重构的目标代码编写一套高覆盖率的单元测试套件。这套测试就像是在重构前为旧代码拍摄的一张”行为快照”,将旧代码所有已知的、重要的行为特征用代码的形式精确固定下来。
【一个具体的重构流程】
场景:我们有一个陈旧的、负责计算订单折扣的函数 calculateDiscount,里面充满了 if-else 嵌套,难以维护。
- 错误的流程(祈祷式重构):把函数代码复制给 AI → 说”请重构这个函数,让它更清晰” → AI 返回一个”策略模式”的优雅新版本 → 你”肉眼”看了一遍觉得很棒就替换了 → 一周后财务报告所有”钻石会员”在”周年庆”购买”数码产品”的订单折扣都算错了。
- 正确的流程(电网式重构):
- 铺设电网:动手重构前,先为现有函数编写全面测试。测试覆盖所有已知业务规则和边界条件:普通会员无折扣、黄金会员 95 折、钻石会员 90 折、周年庆所有商品额外 9 折、数码产品不参与周年庆额外折扣、钻石会员在周年庆购买非数码产品享受折上折……运行测试,确保全部通过。
- 授权 AI 重构:“这是我们的折扣计算函数和它的单元测试套件。目前所有测试都通过。请重构该函数,要求:重构后所有测试仍然通过。”
- AI 在”电网”内工作:AI 接收指令开始”魔法重构”,测试套件始终约束着它的行为边界。
- 自动化验收:AI 返回新代码后,你不需人工比对——直接运行测试套件。
- 判断时刻:所有既有测试通过,只能说明候选实现满足这些已编码的行为,不能证明完整行为等价;某测试失败(如”钻石会员周年庆购买非数码产品”)则说明候选实现或测试假设需要调查,修正后重跑全部关键用例。
在 AI 辅助开发中,测试同时承担规格记录和回归证据的作用。先把已知行为写成测试,再让 AI 重构,可以降低未察觉行为变化的风险;它不能覆盖遗漏的规则、真实依赖或生产环境差异。
25.2 关键行为、失败分支与 CI 门禁
一个只测顺利路径的测试套件会给出虚假的安全感。门禁应先从构件风险出发,明确必须通过的关键行为、红线和失败分支,再决定使用哪些度量辅助发现遗漏。
代码覆盖率描述测试执行到了哪些行、分支或函数,但不证明断言正确,也不证明需求完整。分支覆盖率有助于发现未执行的决策路径;是否足够,仍取决于风险、断言质量和未被编码的业务规则。
本章采用不可补偿的行为门禁:
- 订单折扣的每条已确认业务规则都有至少一个具名用例;
- 无效会员等级、缺失价格、负数金额等失败分支有明确错误语义;
- 财务红线用例全部通过,任一失败都阻止合并;
- 候选版本与已记录基线使用同一输入集比较,差异必须解释;
- 若项目另行声明覆盖率目标,记录工具、统计范围、阈值依据和实际结果,不能把教材数字当成通用标准。
这个门禁对人类和 AI 一视同仁,且不能由其他指标补偿。它的价值在于:
- 把”不能破坏什么”变成可观察用例,而不是模糊要求;
- 让失败直接对应业务行为与责任人;
- 允许 AI 运行测试和提出补例,但新增或修改测试仍需复核,防止通过弱化断言制造假绿。
让 AI 协助提出测试时,先给出行为清单和证据边界:
你的提问: Context: The release gate contains named critical behaviors, failure branches, and financial red-line cases. Coverage is diagnostic, not proof of correctness. Your Role: Act as a meticulous QA Auditor with expertise in unit testing. Code to be Tested: [粘贴业务代码] Task:
- Analyze the Code: Identify all logical paths and branches.
- Generate Test Cases: Write a comprehensive suite of unit tests.
- Preserve the Gate: Do not delete, skip, or weaken existing assertions. Add the named failure and red-line cases.
- Explain Evidence: For each test, name the behavior or risk it checks and any remaining untested scope.
CI 门禁据此检查具体行为,而不是用单一百分比代替判断。覆盖率报告可以作为审计附件,帮助发现未执行代码;它不替代红线用例、失败分支、集成测试和人工复核。
【配置模板】测试栅栏搭建(以 Jest + GitHub Actions 为例):
示例类型:参考。 参考片段,不保证可独立执行。请按本章上下文、项目版本和真实接口改写,并以实际运行结果验收。
// jest.config.js
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/index.ts',
],
coverageDirectory: 'coverage',
// 先让报告暴露未执行分支。阈值须由项目按风险另行声明,
// 并记录工具版本、统计范围、实际结果与依据。
testMatch: ['**/*.critical.test.js', '**/*.failure.test.js'],
};
25.3 致命指令:“执行并修复所有失败的测试”(使用前提与风险)
我们已经铺设了”电网”(编写测试),并设定了行为门禁。接下来可以在明确权限与停止条件后,让 AI 执行测试、分析失败并提出修复。
这个指令的强大之处在于,它把原来需要人类”人肉”介入的”调试循环”自动化了:
- 传统的调试循环(人类驱动):AI 生成代码 → 人类运行测试 → 测试失败 → 人类阅读失败日志 → 人类分析原因 → 人类告诉 AI 哪里错了 → 回到第一步。效率瓶颈完全在于人类的分析和转述能力。
- AI 的自洽循环(测试驱动):AI 接收任务 → AI 修改代码 → AI 自己运行测试 → 测试失败 → AI 自己阅读失败日志(错误信息、失败断言、堆栈跟踪)→ AI 自己分析”失败日志与它刚修改的代码之间的因果关系” → AI 自己提出修复方案并生成新代码 → 回到上一步,直到所有测试通过。
在这个新循环里,失败的测试日志成为了 AI 的”老师”和”标尺”,人类从”微观管理者”变成了”测试设计者和最终审核者”。
【指令模板 25.1:测试驱动的 Bug 修复】
Context: We have a bug in our system. I have already written a failing test that reproduces the bug. Your Role: Act as a Senior Software Engineer practicing Test-Driven Debugging. Failing Test Code: [粘贴正在失败的测试用例] Relevant Business Logic Code: [粘贴相关业务代码] Your Task (Iterative Process):
- Analyze the Failure: Read the failing test and understand what expected behavior is violated.
- Propose a Fix: Suggest a minimal change to the business logic.
- Apply and Verify: I will apply your proposed fix and re-run the test.
- Repeat: If tests are still failing, analyze the new result and iterate.
【指令模板 25.2:测试驱动的功能开发】
Context: I want to add a new feature: “[描述新功能]”. Your Role: Act as a Senior Software Engineer practicing TDD. New, Failing (“Pending”) Tests: [粘贴为新功能编写的、描述其规格的、正在失败的测试] File to Modify: [提供文件路径和现有代码] Your Task: Write the implementation code inside [文件],使得所有 pending 测试通过。
使用的前提与风险:
- 前提:高质量的测试。这个模式的成败完全取决于你的测试套件质量——如果测试本身写得粗劣(覆盖不全、断言模糊、只测 happy path),AI 就可能”修”出一个让测试通过但实际仍有问题的实现。测试电网的强度,决定了 AI 自洽循环的可靠性。
- 风险:可能陷入”局部最优”。AI 可能找到一个”投机取巧”的最短路径让测试通过——比如硬编码测试期望值,而不是真正修复逻辑。因此人类必须抽查修复的质量,警惕”测试假绿”。
- 你的角色:在这个循环中,你负责设计关键行为与红线门禁,审核 AI 的修复是否解决根因,并确认它没有删除、跳过或弱化测试。
尽管有这些风险,“测试驱动 AI”的模式依然是目前所拥有的、最强大的驾驭 AI 重构与修复能力的方法。它把”人盯着 AI 的每一行输出”变成了”人为 AI 设计不可逾越的电网,让 AI 在电网内自由发挥”。
25.4 定期”垃圾回收”:清理死代码、冗余逻辑、过期注释
为什么 AI 写的代码里”死代码”特别多?因为 AI 没有”记忆”——它在一个模块里添加的新逻辑,可能已经让另一个模块里的旧逻辑失去了调用者;它重构了一个函数,却忘了删除那个被替代的旧版本。再加上 AI 倾向于”追加代码”而不是”重构代码”,冗余就会不断累积。
AI 制造的垃圾类型:死代码(未被调用的函数、未使用的变量、无法到达的分支)、冗余逻辑(重复实现、无效 fallback、多余的防御性判断)、过期注释(描述旧行为的注释、标记为 TODO 却永远无人处理的条目)。
建立定期扫描与清理机制:
- 让 AI 做”垃圾扫描”:周期性让 AI 审查代码库,列出所有死代码和冗余逻辑——AI 是扫描”坏味道”的高效工具(“请审查 src/ 目录,列出所有未被调用的导出函数、未被使用的变量、以及重复的实现。”);
- 在迭代中顺手清理:每完成一个功能,检查它是否让其他代码变成了垃圾;
- 把”垃圾回收”制度化:在每个迭代周期留出专门时间偿还技术债务。
让系统在一次次迭代中实现”逆生长”——理想的演进是:规模增长的同时,复杂度不增长甚至下降。垃圾回收就是实现”逆生长”的手段。
【清理清单】每次迭代前必查的 10 个冗余点
每次迭代结束时,用这个清单快速扫描,逐项勾选。
| # | 检查项 | 典型表现 |
|---|---|---|
| 1 | 死函数/死方法 | 未被任何地方调用的导出函数或类方法 |
| 2 | 未使用的变量/常量 | 声明后从未引用,或只在声明处赋值 |
| 3 | 无法到达的分支 | 条件永远为 false 的 if-else / switch-case |
| 4 | 重复实现 | 同一逻辑在 2+ 处出现,未抽离为公共模块 |
| 5 | 无效 fallback | catch 块中静默吞掉异常(空 catch / 只打日志不处理) |
| 6 | 多余的防御性判断 | 对已有类型约束的参数做冗余 null/undefined 检查 |
| 7 | 过期注释 | 描述旧行为的注释、标记 TODO/FIXME 却无人跟进的条目 |
| 8 | 孤儿依赖 | package.json 中已安装但 import 处不再使用的包 |
| 9 | 废弃配置/环境变量 | 不再使用的 env 变量、旧的 webpack/vite 配置项 |
| 10 | 空文件/占位文件 | 仅含注释或空 export 的文件,从未被实际使用 |
操作建议:用附录 F.7 的”垃圾回收话术”批量扫描——将整个目录或模块的代码贴给 AI,让它逐项列出以上 10 类冗余并给出处理建议。
25.5 防退化契约:Commit Hash 锁定基线、Prompt 植入防退化指令
“防退化契约”的核心命题是:确保每一次修改都是在进步,而不是在退步。三个具体手段:
手段一:锁死已验证的基线(Commit Hash 锁定策略)。
当系统通过了一个重要的验收(如集成测试全绿、性能达标、用户验收通过),用 git commit 把这个状态固定下来,并记录 commit hash。在后续的迭代中,这个 hash 就是”防退化的基准线”:
- 任何一次重构或优化,都必须逐项比较已声明的关键行为、红线、失败分支和性能目标;若项目跟踪覆盖率,则同时记录统计范围与实际差异;
- 如果某次修改导致关键指标低于基线,用 git diff 检查候选改动,结合测试定位原因。先保护工作、核实完整基线哈希,再按第 10.4 节选择恢复路径:共享提交用 revert;仅个人未共享分支可考虑 reset。stash 不是撤销且不默认包含忽略文件,Git 不恢复数据库或外部状态。恢复后须重新验证基线。
手段二:在 Prompt 中植入防退化指令,让 AI 自我审查。
在给 AI 下发”修改现有代码”的指令时,植入防退化条文:
【防退化约束】本次修改必须满足:
- 不得删除或弱化任何现有的功能(保持行为兼容);
- 不得删除、跳过或弱化既有关键行为、失败分支与红线用例;覆盖率仅在项目已声明工具、范围与目标时按实际基线比较;
- 不得引入新的全局状态或隐式依赖;
- 修改完成后,先运行完整测试套件确认全绿,再汇报结果;
- 如发现修改触及已固化的核心模块,请先停下来向我确认。
这些条文利用”约束”的力量,把”防止退化”变成了 AI 的硬性行为准则——它不再是”我改坏了你帮我发现”,而是”你必须证明你没改坏”。
手段三:当 AI 不小心破坏了边界,如何通过反馈循环快速纠正。
当 AI 的修改突破了既定的边界(比如动了不该动的核心模块、把某个文件的职责扩大了),立即使用反馈循环:明确指出它破坏了哪条边界(引用具体的约束条文)→ 要求它恢复到边界内(必要时 git 回滚)→ 把这次违规记录到 AGENTS.md 的”教训区”,让未来的会话不再重复。每一次越界纠正,都是一次契约的加固。
【Prompt 片段】拿来就用的防退化条文(完整版见附录 F):
示例类型:参考。 参考片段,不保证可独立执行。请按本章上下文、项目版本和真实接口改写,并以实际运行结果验收。
【防退化指令】
在开始修改之前,请先阅读并理解以下约束:
- 基线 commit hash: [填入]
- 既有测试必须全部通过(禁止跳过或删除任何测试)
- 核心模块([列出])禁止修改,除非我明确要求
- 修改后请自检:列出本次改动的文件清单,并说明每个改动对
既有行为的影响
- 若无法满足以上任一条件,请停止修改并说明原因
暂停整理:先完成本章最小闭环
先只写出“关键行为测试”与“失败分支”之间的一条关系,并把它填进测试防退化清单。确认这一步有输入、判断和证据后,再加入“持续集成”;三项尚未连通时,不进入独立练习。
独立练习
使用虚构或已获授权的脱敏材料,先独立作答,再查看参考。将产物保存为 chapter-25.md,写上版本、决定、证据和缺项。
为分类器记录正常、未知、空输入和敏感数据的行为契约,再重构内部函数。解释为什么不能删除失败测试来获得绿色结果。
本章必交产物:测试防退化清单
以下证据必须出现在本次独立练习的提交物中;正文原有问题用于提供内容,不能替代这些验收项。
- O1: 测试防退化清单分别定义“关键行为测试”“失败分支”和“持续集成”的覆盖对象
- O2: 测试防退化清单为三个层面各写出检查方法、责任人和通过证据
- O3: 产物列出关键行为与失败分支并记录自动运行的实际结果
查看参考反馈(先独立作答)
参考反馈
把输入、输出和错误语义固定为契约,先跑基线再看候选差异。测试失败需判断缺陷还是经批准需求变化,并记录依据。覆盖率仅说明执行覆盖,不保证正确;阈值按风险设定,不把教材示例当行业通用90%标准。
自评与下一步
对照本章目标检查:决定是否明确,依据是否能复现,未知项是否如实记录。参考给出一种判断方式,不是唯一答案;若不同结论能提供同等证据,可请同伴复核。缺少证据的部分记未完成,再回到对应步骤补充。
来源与边界
登记来源只支持本章涉及的外部事实;测试防退化清单、示例数字和练习情境属于课程内部教学设计,必须在实际项目中重新验证。
- GitHub Actions 官方文档
GitHub · 2026-09-17 · 仓库工作流可以自动执行构建、测试和持续集成任务。
记录本章练习
仅保存本浏览器的自检记录,不上传作业,不代表评阅通过或认证。请在自己的章节文件中保留证据与缺项。