5.1 测试驱动开发:先红灯再绿灯再重构


本节摘要:TDD 主张"先写测试让它红,再写最小代码让它绿,最后重构",用红灯牵引设计而非事后补测。本节把红-绿-重构循环讲透,并给出一个可运行的最小循环示例,以及它成立的三个前提。

为什么"先红灯"听着反直觉却很值

直觉上,测试是"写完功能再补"的。但 TDD 把顺序倒过来——先写一个会失败的测试,逼你把"我到底想要什么行为"想清楚,再让代码恰好让它绿。倒过来的好处不是玄学,而是逼你在写实现前先定契约:接口长什么样、输入输什么、边界在哪。很多"写完再补测试"的项目里,那些测试不过是在给已经写死、难改的实现盖个章,而 TDD 让测试成为塑造代码形状的模具。

红-绿-重构三步循环

这个循环很短,但每一步都有讲究,缺一不可。

第一步,红:写一个失败测试。它先失败,因为功能还不存在。这一步的关键是"最小且明确"——只测一个行为点,别一上来写个大而全的套件。

第二步,绿:写最少量的代码让测试通过。别偷懒越过测试直接上完美实现,宁可笨拙地先让它绿,绿灯是你的安全网。

第三步,重构:保持绿灯的前提下,把代码和测试都收拾干净——消重复、剔死代码、提可读。测试是你的安全网,所以重构才敢放手。

下面把循环画出来,你跟着它走就不容易乱。

05-01-fig01

最小可运行循环

用一个纯逻辑例子演示,Python + pytest。

先写红灯:

def test_should_reject_amount_less_than_zero(): assert not is_valid_order_amount(-1)

(此时 is_valid_order_amount 未定义,必然红。)

再写最小实现,让绿:

def is_valid_order_amount(amount): return amount > 0

(现在测试绿了。)最后重构:把实现和测试都收拾一下,比如补充第二个边界 should_accept_zero 并确认绿灯。整个循环几分钟内完成,你在设计、实现、验证之间反复获得即时反馈。

TDD 成立的三个前提

TDD 不是拿来就灵,它的见效依赖三个前提,缺一个都会退化成"形式红绿"。

前提一:被测代码能隔离。如果代码内部直接 new 数据库、硬编码网络,你写不出"先红"的测试——因为外部依赖太吵。所以 TDD 反过来倒逼你把依赖设计成可注入的(回看第 2.2 节),这是一环扣一环的。

前提二:需求能拆成行为点。若需求含糊到"谁知道该返回什么",就没法定出红的第一条。TDD 适合能画出输入输出契约的功能,含糊的需求先写清楚再循环。

前提三:团队愿意保持绿灯。重构阶段一旦红灯长期挂起,人就会开始绕过测试走捷径。灯绿不绿、常常跑,是循环能不能转下去的前提。

什么时候该用/不该用 TDD

TDD 的高光场景是核心逻辑、算价、状态机这种"契约清晰 + 高频改动"的地方。它不适合所有场景:简单的胶水代码、一次性脚本、纯探索性原型,硬上 TDD 反而拖慢节奏。判断的口径是这里有没有值得守护的回归风险——有,就让测试先行;没有,别为仪式感牺牲速度。

红灯也要"会点",别为了红而红

很多人第一次跑 TDD,红灯是红了,但红得没有意义——不是因为"这个行为还没实现",而是因为"断言写错"或"函数压根没写对名字"。要区分"我还没写功能"的红 与"我写错测试"的红,有一个自查:把实现换成你认为的最终答案,如果灯还是红的,就说明问题出在测试本身而不是功能。TDD 的红必须"可预期地由渐变绿",一旦红灯的来源说不清,就是在给循环埋雷。

对照身边常见的消耗:有人停在一堆假红里出不来,因为一次塞了太多行为点;有人为了图快,用"断言非空""断言不抛"这种恒真断言混过关,那叫假绿不叫真绿(回看 2.5)。真正合格的红,是"换对实现必绿、改掉实现必红",这两端都对得齐,循环才值得托付。

一个小步快走的节奏陷阱

TDD 最容易被讲成一味"快",但快有快的前提,否则会掉进"重写陷阱":一上来就想写出宏大设计,结果大步子跨太快、绿灯半天不亮,反而泄气。成熟的节奏反而是"小步"——每次只让一条断言变绿,跑一圈,绿了再迈下一步。尽管单步很小,但每一步都有即时反馈,心理负担最轻。更细的握法是把"步"拆成一红、一绿、一重构三小格,宁可多转两圈,也不要一口吞一个大改动。

现实中真正拖垮 TDD 的,往往不是步子太小,而是重构阶段被无视。很多团队只做"红-绿",把"重构"当可选项跳过,钩起的死代码、重复逻辑越积越多,测试照样绿,但代码的形状在悄悄变坏。别忘了三步循环里,重构才是让绿灯长期有含金量的那一步。

从"会 TDD"到"团队敢 TDD"

TDD 会了是一回事,团队里敢不敢长期这么写是另一回事。它有个常被忽视的软性前提:没有人会因为"先写测试"被当成磨洋工。在压在交付节奏下的团队,先写测试很容易被外行误读成"迟到",于是有人偷偷跳过红灯直接写实现。要让 TDD 站得住,与其讲道理,不如给它一个可观察的保护:把"合并前新增行为必须有配套测试"写进流水线或评审硬门槛,让"没写测试就改功能"在流程上过不去,而非靠个人自觉。

还有一层是节奏上的心理保护:TDD 的反馈是即时的,但一个需求被拆成几十步小循环时,中途的"绿"常常让不熟悉的人觉得"好像没在进展"。这时候要敢于把"这个行为还没落地、红灯正等着被转绿"本身也当成进度,而不只是把最终合拢的东西当进度。把这种心态在团队里摊开讲,比偷偷憋着更利于长期坚持,毕竟多数被放弃的 TDD,不是死于技术,而是死于没有一个愿意共同遵守节奏的环境。

三步循环之外的"心态底色"

红绿重构之外,TDD 真正的底色是那句慢道理:每一次小的、确定的、有反馈的前进,比一次莽撞的大步更靠得住。红灯亮起说明目标还没到,绿灯亮起说明你此刻是对的,重构让你把这个"对"保持得久。这三步循环之所以值得坚持,不是因为它神,而是因为它把"改代码"从赌场心态变成了一段段可回退、可验证的短行程。

本节要点回顾

  • 红-绿-重构:先写失败测试,再最小实现变绿,再重构收尾。
  • 红灯牵引设计:先定契约,让测试塑造代码形状。
  • 三前提:能隔离、能拆行为点、团队愿守绿灯。
  • 不是万能:胶水代码与探索原型别硬上。
  • 配速:契约清晰 + 改得勤的地方,最配 TDD。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U