本节摘要:TDD 的"先测试后实现"并非 Kent Beck 发明,而是上世纪五十年代就萌芽、被反复再发现的编程节律;他做的是把它标准化成红绿重构循环,并配上 xUnit 这套趁手工具。本节沿时间线梳理 TDD 的来历与分支,帮你判断它的能力边界从何而来。读完你应能讲清 TDD、xUnit、XP、BDD 四者的关系。
最初把"先写测试"白纸黑字写下来的,并不是 Kent Beck。1957 年出版的编程教材《Digital Computer Programming》里,D.D. McCracken 就建议编程前先准备一组检验用的数据,程序写到哪儿就核对到哪儿;六十年代 NASA 水星计划的软件组更是把"测试用例先行于代码"当成纪律执行——在那个内存按字节算钱的年代,人们靠这种笨办法换正确性。这些先例说明:测试先行是个老念头,它缺的从来不是发明者,而是一套能落地的节律和工具。
把时间线摊开,你会看到"再发现"比"发明"出现得更频繁:
1957 McCracken 教材:程序之前先备好检验数据 1960s NASA 水星计划:测试用例先行的工程纪律 1989 Kent Beck 写出 SUnit,Smalltalk 的单元测试框架 1994 SUnit 思想扩散;单元测试框架家族化 1996 克莱斯勒 C3 薪酬项目:Beck 把测试先行揉进整套方法,XP 雏形出现 1997 Beck 与 Erich Gamma 在一趟航班上写出 JUnit 1999 PyUnit 问世,后进入 Python 标准库成为 unittest 2003 《Test-Driven Development: By Example》出版,TDD 正式定名 2004 pytest 前身 py.test 发布,断言即测试的流派登场
真正值得驻足的是 1996 年前后的克莱斯勒 C3 项目。Beck 被请去救一个深陷泥潭的薪酬系统,他和团队把当时散落的实践打包成"极限编程":小步发布、结对、持续集成,以及测试先行。测试在这里的角色发生了质变——它不再只是质量工具,而是 XP 全套节律的传动轴:没有密集的自动化测试,重构不敢做;没有重构,设计腐化无法逆转;设计无法改善,小步发布就是空谈。

工具在 TDD 史上不是配角。SUnit 与后来的 JUnit 定下了一整套沿用至今的语法:用例、夹具、断言、运行器,红绿两色的隐喻就来自这些框架的进度条。可以这样理解二者的关系——测试先行的哲学回答"为什么",xUnit 回答"拿什么写、按什么节奏写"。没有框架承载,"先写测试"只是个人美德;有了框架,它才成为团队可以复制的手艺。你上一节用的 pytest 同属这个家族,只是把断言简化成原生 assert,把夹具做成带名字的函数,让节律跑得更轻。
2000 年前后,两支重要旁系从 TDD 里分岔。一支是 Mock Objects:Tim Mackinnon 等人提出用替身对象把"测试先行"推进到设计层面——先写对协作者的期望,逼出接口。这支旁系在第 3 章的测试替身处会展开。另一支是 2003 年 Dan North 的 BDD(行为驱动开发):他发现学员总被"test"这个词吓住,把用例改称"行为"、把"断言"改说"应该",教学效果立刻变好。BDD 不是 TDD 的替代品,而是一次措辞校准加一层业务语言外衣——第 5 章会带你写 BDD 风格的用例。
💡 读历史的实用价值:TDD 从薪酬系统这种"规则密集、输入输出清晰"的领域长大,这解释了它为什么在业务逻辑上威力最大,而在探索性、视觉性强的领域相对吃力——1.3 节的适用性判断正是从这里推出来的。
读史只看正面会失真。2014 年,Rails 框架之父 DHH 发表《TDD 已死?》,引爆了这场手艺史上最大的一次公开论战。他批评的靶子是变味的实践: mock 层层叠叠的测试把实现细节焊死、为了"测试驱动"而写出一堆没人读懂的用例、集成测试缺位让系统行为无人验证。论战催生了几个务实的共识:其一,被批评的是僵化执行而非节律本身——把替身当信仰、把覆盖率当指标,确实是该批的病,处方也确实存在(本册第 3 章的边界线、第 7 章的度量读法都是那场论战的沉淀);其二,"测试先行"与"测试后补"之争此后更常以场景为界,而非站队;其三,BDD 与测试金字塔在论战后反而加速普及——争论逼着实践者把"为什么"讲得更清楚。
给学习者的启示很实际:历史上每次"X 已死"的唱衰,死的都是 X 的教条版本。你不需要接受任何一派的全部主张,但应该亲手把循环跑起来——带着体感去读争论,判断力完全不同。
| 维度 | 测试先行(TDD) | 实现后补测试 |
|---|---|---|
| 测试的角色 | 需求的草稿、设计的推手 | 实现的附产品 |
| 断言来源 | 使用者视角:该表现成什么样 | 实现视角:它做了什么就断什么 |
| 典型缺陷 | 无(没实现就没有伪绿空间) | 快乐路径多、锁细节多 |
| 对接口的影响 | 逼出松耦合(可替身、可注入) | 顺着既有结构写,难改设计 |
| 何时暴露理解偏差 | 写红灯的当下 | 补测试时,往往选择绕开 |
表里最要紧的一行是"断言来源"。后补测试的人已经看过实现,断言会不自觉地顺着实现写——实现错了,测试跟着错,两个错误互相印证,绿灯一盏接一盏。测试先行的核心价值就是把"使用者视角"固定在实现之前,错位被红灯当场揭穿。
时间线看到这里,背景就补齐了。下一节把镜头拉回你的工位:面对手头的需求,判断哪些值得用红绿循环伺候、哪些换个做法更划算——适用性判断是老手和新手拉开差距的地方。