Chapter 11:Growing and Sustaining TDD

对齐原书 Chapter 11:向团队解释 TDD,阻断坏测试死亡螺旋与 SCUMmy cycle,通过结对、kata、dojo、覆盖率、持续集成、团队标准和社区学习让实践长期可持续。

学习目标

  • 能用反馈时间、缺陷定位、设计可变性和交付风险解释 TDD,而不是只要求团队遵守“先写测试”仪式
  • 能识别坏测试死亡螺旋与 SCUMmy cycle,按修复不稳定、缩短快层、恢复门禁和重构难测代码的顺序止损
  • 能设计结对、kata/dojo、覆盖率评审、持续集成和团队标准,明确 owner 与动作,并通过社区持续更新实践

机制总览

Chapter 11:Growing and Sustaining TDD:机制路径

  1. 1

    TDD 从个人技巧变成团队系统

    一个人可以在本地坚持红绿重构,但若主干长期红、测试随机、评审只看覆盖率、需求排期不允许小步,实践很快退化。持续 TDD 需要个人循环、共同学习、交付门禁和组织反馈共同工作。

  2. 2

    解释 TDD 要从问题和证据开始

    “测试多所以质量高”很难说服有经验的同事。选择当前痛点:一次回归要两天、修改税率常破坏旧客户、外部依赖使测试随机。展示一个小功能如何先复现、几分钟定位、在全绿下重构,并比较前后反馈距离。

  3. 3

    坏测试会形成死亡螺旋

    第一个随机失败若被简单重跑,团队学到红灯不可靠;主干长期红后,新失败无法归因;为了赶进度,开发者跳过测试并扩大改动;代码耦合又产生更慢、更脆的测试。循环会自我强化。

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

Chapter 11:Growing and Sustaining TDD:机制与证据

切换《Chapter 11:Growing and Sustaining TDD》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · TDD 从个人技巧变成团队系统

一个人可以在本地坚持红绿重构,但若主干长期红、测试随机、评审只看覆盖率、需求排期不允许小步,实践很快退化。持续 TDD 需要个人循环、共同学习、交付门禁和组织反馈共同工作。

可核验证据

保存红—绿—重构的最小提交与失败消息,用行为断言、替身交互和重复运行核对「TDD 从个人技巧变成团队系统」是否提供快速反馈。

学完《Chapter 11:Growing and Sustaining TDD》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 11:Growing and Sustaining TDD:失效与核验

TDD 从个人技巧变成团队系统

典型失效

若把「TDD 从个人技巧变成团队系统」退化成先写实现再补断言,测试会耦合内部步骤,既不能驱动设计,也无法稳定解释失败。

核验证据

保存红—绿—重构的最小提交与失败消息,用行为断言、替身交互和重复运行核对「TDD 从个人技巧变成团队系统」是否提供快速反馈。

解释 TDD 要从问题和证据开始

典型失效

若把「解释 TDD 要从问题和证据开始」退化成先写实现再补断言,测试会耦合内部步骤,既不能驱动设计,也无法稳定解释失败。

核验证据

保存红—绿—重构的最小提交与失败消息,用行为断言、替身交互和重复运行核对「解释 TDD 要从问题和证据开始」是否提供快速反馈。

坏测试会形成死亡螺旋

典型失效

若把「坏测试会形成死亡螺旋」退化成先写实现再补断言,测试会耦合内部步骤,既不能驱动设计,也无法稳定解释失败。

核验证据

保存红—绿—重构的最小提交与失败消息,用行为断言、替身交互和重复运行核对「坏测试会形成死亡螺旋」是否提供快速反馈。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

TDD 从个人技巧变成团队系统

一个人可以在本地坚持红绿重构,但若主干长期红、测试随机、评审只看覆盖率、需求排期不允许小步,实践很快退化。持续 TDD 需要个人循环、共同学习、交付门禁和组织反馈共同工作。

可持续不等于永远使用同一工具和规范。团队要监控反馈时长、失败可信度、缺陷逃逸和难测区域,定期调整入口、边界和标准。

解释 TDD 要从问题和证据开始

“测试多所以质量高”很难说服有经验的同事。选择当前痛点:一次回归要两天、修改税率常破坏旧客户、外部依赖使测试随机。展示一个小功能如何先复现、几分钟定位、在全绿下重构,并比较前后反馈距离。

问题:价格规则改动后,需要手工跑 14 个场景,平均 90 分钟
实验:选 3 个高风险规则,先写可执行例子并提取纯计算
结果:27 个快测试 140 ms,缺陷在提交前定位到具体规则
边界:数据库映射仍由 4 个集成测试证明,TDD 不替代验收

解释必须承认成本:学习拆分、维护测试和治理慢层都需要投入。主张是把调试和返工从晚期移到短反馈,不是“以后没有缺陷”。

坏测试会形成死亡螺旋

第一个随机失败若被简单重跑,团队学到红灯不可靠;主干长期红后,新失败无法归因;为了赶进度,开发者跳过测试并扩大改动;代码耦合又产生更慢、更脆的测试。循环会自我强化。

止损先恢复信号可信度:主干红立即指定 owner,隔离不稳定测试时保留 issue、复现和期限,修复共享状态与真实时间,建立秒级快层,再在护栏下拆难测代码。只要求“从今天起都先写测试”不会修复已有系统条件。

结对编程传播的是判断过程

规范文档能写规则,却难展示如何选择下一条有区分力的测试、何时用 fake、何时停止重构。结对让这些判断在真实代码中显性化。角色应频繁交换,导航者关注测试清单、设计压力和遗漏边界,驾驶者保持短步与即时运行。

pair loop
1. 共同写下下一条行为与失败预测
2. 驾驶者写最小测试,导航者检查区分力
3. 看到正确红灯后交换角色
4. 写最小实现并全绿
5. 共同决定是否重构,再更新测试清单

结对不是资深者控制键盘。目标是共享决策语言和代码所有权;可按复杂度、学习目标和远程条件安排,不要求每项工作永久双人。

kata 和 dojo 提供低风险刻意练习

代码卡塔(kata)与编码道场(dojo)把练习和生产交付分开:前者允许重复同一题磨炼小步,后者让团队共同观察、轮换和讨论判断。

Roman Numerals、Bowling Game、String Calculator 等题目的价值不在答案,而在每次设置练习目标:只练三角测量、只练 mock 约束、只练小步重构。重复同一题能把注意力从领域理解转到操作细节。

dojo 可以采用 randori 轮换或一人演示、全员讨论。时间盒结束后复盘:哪次红灯过大,何时跳过正确失败,哪些测试绑定实现。不要把 dojo 变成现场绩效评估,否则成员会隐藏困惑。

从练习迁移到真实低风险功能

只做 kata 容易形成“玩具里很好用”的印象。下一步选择边界清楚、风险可控的生产功能,由有经验成员结对,完整经过测试清单、红绿重构、CI 和评审。记录卡点并改进工具/标准,而不是因第一次慢就宣布方法不适合。

练习与生产之间应有反馈:真实代码暴露遗留依赖和构建速度问题,下一次 dojo 可针对这些技巧;练习中的有效 helper 和命名约定再进入团队标准。

覆盖率是探针,不是质量分数

100% 行覆盖仍可能没有有效断言,也可能漏掉边界组合、并发交错和性能要求。低覆盖则提示某些代码从未执行,需要按风险审查。覆盖率适合问“为什么这里没跑”,不适合直接给团队排名。

cmake -S . -B build-coverage -DENABLE_COVERAGE=ON
cmake --build build-coverage --parallel
ctest --test-dir build-coverage --output-on-failure
llvm-profdata merge -sparse build-coverage/**/*.profraw -o coverage.profdata
llvm-cov report ./build-coverage/app -instr-profile=coverage.profdata

具体工具命令会随编译器和产物布局变化,但流程应固定:带插桩构建、运行真实套件、生成报告、按变更和风险审查。不要为了覆盖不可达防御分支而写无意义测试;可用 fault injection 或说明排除理由。

mutation testing 检查测试能否看见错误

覆盖说明代码执行过,mutation testing 临时改变比较符、常量或返回值,检查测试是否变红。存活 mutation 可能表示断言弱、等价变换或不可达代码,需要人工分类。它成本较高,可先在核心规则和增量变更运行。

mutation 分数也不是目标。生成器无法理解所有 C++ 语义,模板和未定义行为还会产生噪声;把它作为断言质量探针,而非另一项排名。

持续集成保护共享事实

CI 应尽快给出首个有用反馈:格式/静态检查、编译、快速单元、集成、sanitizer、性能趋势可按成本分阶段,但 required gate 不能被吞掉。开发者本地入口与 CI 尽量一致,依赖和工具链锁定身份。

stages:
  - configure-and-build
  - fast-tests
  - integration-tests
  - sanitizers
policy:
  required: [configure-and-build, fast-tests, integration-tests]
  on_failure: stop-merge-and-assign-owner

这是流程示意,不绑定某个 CI 服务。重要的是失败状态、日志和报告可见,主干红有立即动作,长任务不会让基础反馈排队数小时。

主干红的处理速度是关键健康指标

记录 CI 首次反馈时间、全套时长、红灯恢复时间、随机失败率和隔离测试数。它们描述反馈系统,不直接考核个人。恢复时间变长说明 owner、可诊断性或回退机制有问题。

小批集成降低归因范围。一天一个大提交即使 CI 自动运行,也不是真正持续集成;优先合并独立全绿小步,使用 feature flag 隐藏未交付能力而不是长期分支。

团队标准必须包含“为什么”和例外流程

标准示例:快层 p95 低于某预算;真实时间必须注入;新缺陷先有复现;mock 只验证业务交互;quarantine 必须有 owner 与期限;主干红停止合入。每条应说明解决什么风险,以及特殊场景如何申请和复查例外。

标准不是静态手册。每次事故、升级或 retro 都可提出修改,用一个小实验验证后纳入。过多规则会让成员机械合规,应优先保留直接保护反馈可信度的约定。

社区让团队避免内部固化

测试框架、C++ 标准、sanitizer 和构建工具持续变化,外部社区提供新经验,也能挑战团队局部惯例。引入新方法前在小范围试验,比较反馈时间、可读性和维护成本,避免因流行一次性全库改写。

内部社区同样重要:维护示例仓库、短技术分享、测试诊所和跨团队 pairing。让失败经验也能被分享,而不是只有“成功案例”。

先预测:哪个指标后面需要什么动作

对四个信号先写 owner 和时限:覆盖率下降 3%、主干红 30 分钟、随机测试一周 8 次、快层 p95 从 40 秒升到 3 分钟。指标不自动给结论;为每项说明要查什么证据、何时阻止合入、怎样验证修复。

建立一条渐进采用路线

  1. 选择一个有业务价值但风险可控的功能,结对完成完整循环
  2. 固定一键本地/CI 入口,先恢复快而可信的主干信号
  3. 每周用短 kata/dojo 练一个具体技巧,并迁移到真实代码
  4. 对遗留热点加特征测试和接缝,不要求一次覆盖全库
  5. 用反馈时间、随机率、缺陷逃逸和评审质量复盘,而非只看覆盖率

采用路线应允许团队在真实约束下调整,但不能把红灯可信度、最小反馈和必要门禁变成可选。先建立一块可持续区域,再逐步扩大。

分步1 / 3

第一步:把 TDD 解释成反馈系统

用当前缺陷与延迟选择小实验,展示红灯定位、最小实现和安全重构;明确成本与边界,不承诺零缺陷。

小结

  • 可持续 TDD 是个人循环、团队学习、交付门禁和社区反馈组成的系统
  • 解释 TDD 应从当前问题和可测实验开始,并承认成本与适用边界
  • 坏测试死亡螺旋与 SCUMmy cycle 从信任下降开始,需先恢复稳定快速信号
  • 结对传播实时判断,kata/dojo 提供刻意练习,再迁移到真实低风险功能
  • 覆盖率发现未触达区域但不证明质量,CI 必须让主干失败立即可见并有 owner
  • 团队标准包含原因、动作和例外流程,社区与复盘持续更新实践

练习

  1. 问题 1:恢复红色主干。 CI 随机失败、团队靠重跑合入,快层需 12 分钟。给出止损顺序。
  1. 问题 2:解释覆盖率。 新模块达到 95% 行覆盖,但 mutation 大量存活且线上仍漏边界。应如何改进?
  1. 问题 3:制定采用计划。 新团队知道 TDD 术语却不会拆小步,只有一份规范文档。下一月怎样安排?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

可持续 TDD
让快速可信反馈与持续设计在团队和时间变化下仍能保持的实践。
解释 TDD
用当前问题、实验和边界说明 TDD 价值与成本的沟通方法。
坏测试死亡螺旋
慢脆随机测试导致少运行、少信任并进一步恶化代码的循环。
SCUMmy cycle
糟糕代码与糟糕单元测试相互强化的循环。
结对编程
两人切换驾驶导航、实时共享测试和设计判断的协作方式。
代码 kata
用于重复练习具体 TDD 技能的小型编程题。
编码 dojo
团队共同观察、轮换和复盘 kata 的集体练习。
代码覆盖率
统计测试执行到行、分支或函数的探针指标。
持续集成
每次小变更在一致环境自动构建测试并保护主干的系统。
团队标准
共同维护的测试、门禁、坏测试处理和评审约定。
社区
通过内外部交流持续获取和验证测试实践的学习网络。

原版目录概念补充核对

以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。

结对编程:机制、边界与证据

在《Chapter 11:Growing and Sustaining TDD》的官方单元 mctdd-11 中,结对编程连接本章第 3 组知识约束。学习时要同时说明它接受什么输入、改变什么状态、在何种边界失效;再以本章示例的编译诊断、固定输入输出或失败用例复核结论,不能只记术语名称。

代码覆盖率:机制、边界与证据

在《Chapter 11:Growing and Sustaining TDD》的官方单元 mctdd-11 中,代码覆盖率连接本章第 5 组知识约束。学习时要同时说明它接受什么输入、改变什么状态、在何种边界失效;再以本章示例的编译诊断、固定输入输出或失败用例复核结论,不能只记术语名称。

讨论

评论区加载中…