⌨️ 第五章 从设计到测试
📄 本篇摘要

TDD(测试驱动开发)=把顺序反过来:先写测试,再写实现。三拍子循环:红(写一个失败的测试)→ 绿(用最少的代码让它通过)→ 重构(整理干净,测试保持绿)。它是 5-11 重构的安全感来源,也是 XP 的核心实践。

📝 关系

上一节:重构 | 下一节:E-R 图与 DFD 图

一、顺序反过来的魔法

传统流程:写代码 → 写测试(有心情的话)。TDD 把测试提到最前:

  1. 红:写一个小测试,描述下一个要有的行为——此时必失败(功能还没有);
  2. 绿:写刚好让它通过的实现——不求完美,求通过;
  3. 重构:测试保护下整理代码——改名、拆函数、消重复。

然后回到 1,循环往复。每个循环几分钟到半小时,测试集像滚雪球一样长大。

二、为什么反过来就有效

测试优先是 TDD 的同义词(强调测试在实现之前);XP(极限编程,Extreme Programming)是 Kent Beck 提出的敏捷方法论,TDD 是它十二个实践里最出名的一个,同门还有结对编程、持续集成(5-10)、小步发布。

三、TDD 的账本

教材提醒两点平衡:

四、动手 Demo:完整走一遍红-绿-重构

# ===== 第 1 轮·红:描述最简单的需求 =====
def test_fizz_1():
    assert fizz(1) == "1"
# 此刻 fizz 不存在 -> 测试失败(红)

# ===== 第 1 轮·绿:最少的实现 =====
def fizz(n):
    return str(n)
# 通过(绿)——别嫌它简陋,【刚好通过】就是 TDD 的节奏

# ===== 第 2 轮·红:3 的倍数 =====
assert fizz(3) == "Fizz"        # 失败(当前返回 "3")

# ===== 第 2 轮·绿 =====
def fizz(n):
    return "Fizz" if n % 3 == 0 else str(n)

# ===== 第 3 轮·红:5 的倍数,再绿 =====
assert fizz(5) == "Buzz"
def fizz(n):
    if n % 15 == 0:
        return "FizzBuzz"
    if n % 3 == 0:
        return "Fizz"
    if n % 5 == 0:
        return "Buzz"
    return str(n)

# ===== 重构:命名与结构已清晰,测试全绿 =====
for n in [1, 3, 5, 15, 7, 30]:
    print(n, "->", fizz(n))

沙箱实跑输出:

1 -> 1
3 -> Fizz
5 -> Buzz
15 -> FizzBuzz
7 -> 7
30 -> FizzBuzz

看到什么算成功:六个输入全部符合 FizzBuzz 规则(3/5/15 的倍数各自正确)。

读结果:经典 FizzBuzz 题目被拆成三轮红绿——每一轮只增加一条规则,规则间的相互作用(15=3×5)在测试保护下自然浮现。注意顺序:先处理 15 再处理 3/5,条件顺序错了 15 会输出 Fizz——这正是边值思维(5-6)在 TDD 里落地。

五、30 秒实验

把 n % 15 == 0 那个分支挪到最后,重跑 fizz(15)——输出 Fizz 而不是 FizzBuzz,立刻红。TDD 下你永远不会带着这个 bug 走很远:它当场暴露。

六、常见误区

误区 真相
先写完实现再补测试=一样 补的测试倾向【顺着实现写】;先写的测试倒逼【从使用者视角设计】
TDD 拖慢进度 前期慢、后期赚:调试时间锐减,重构不再心惊
测试写完就不用维护 测试也是代码,命名与重构一样待遇(本节第三节)
自测三问(点击展开) 1. **红-绿-重构各做什么?** 答:红=写失败的新测试;绿=用最少实现让它通过;重构=保持绿的前提下整理。 2. **测试先行为什么能改善设计?** 答:写测试必须先从调用者视角定接口,难用的设计当场暴露。 3. **TDD 的主场是哪类代码?** 答:规则多、边界多、易变的业务逻辑;草稿脚本不必强上。

相关笔记