阿明第一天进“老灶台”后厨当帮厨。师父没让他碰锅,先递过一张验收单:红烧肉必须色泽红亮、肥而不腻、甜咸适中,二十分钟内上桌。阿明傻眼——菜还没做,怎么就定标准?师父说:正因为菜没做,标准才能指挥你怎么做。此后每试做一次,阿明就对着验收单试吃:不合格就回灶台调火候,合格就记下手法,再把菜谱整理得更清爽。一锅锅肉越做越稳。这就是测试驱动开发(TDD):先写下“什么算做好”的测试,再写让测试通过的最小实现,最后在测试全绿的保护下重构。红灯、绿灯、重构,循环往复,好味道就在这节奏里长出来。

岗位关联

学好 TDD,你会从“写完代码再补测试”的被动者,变成“用测试倒逼设计”的设计者——在 AI 生成代码的时代,这份验收单意识比以往更重要。

目录
  1. 可视化演示
  2. 核心思想:先有验收单
  3. 红绿重构:三拍节奏
  4. 测试粒度:金字塔的取舍
  5. 设计驱动:接口先行
  6. 双派之争:真材实料还是替身
  7. AI工程应用:给黑盒立规矩
  8. 遗留代码策略:先拓片再动刀
  9. 实践陷阱:护栏要对着悬崖
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示TDD核心红-绿-重构循环、经典与伦敦学派策略对比、以及测试覆盖率随迭代增长曲线三大维度。

本节配有交互动效(进入视口后自动加载,约 3KB)。

红-绿-重构循环 红色:编写失败测试定义新行为 绿色:编写最小代码使测试通过 重构:改善代码结构保持测试绿色

核心思想:先有验收单

师父的验收单,本质是“先定义什么叫好,再动手做”。红烧肉还没下锅,“色泽红亮、入口软糯”这些标准就先立住了;代码也一样,函数还没实现,“输入 1 和 2 应返回 3”这样的行为契约先写下来。这个顺序一旦颠倒,验收就成了走过场。

TDD 的第一信念是“测试先行”:动手写实现之前,先写一个注定失败的测试。这个测试不是事后的检查员,而是行为的立法者——它规定了函数收什么、吐什么、遇到异常怎么办。测试通过的瞬间,行为契约就生效了。

为什么要把测试放最前面?因为缺陷越晚发现,修复成本越离谱。需求阶段的一个误解,改一行文字就能解决;拖到上线后,可能要动五个模块、发三次版本。测试先行等于在每个行为诞生的瞬间就验收,把 bug 摁在摇篮里。

那张验收单真正的作用不是“查菜”,而是“定菜”。后厨的每道菜因此有了灵魂,代码里的每个函数因此有了契约。这就是 TDD 的第一课:标准先于作品,作品才有方向。

Cfix∝t2\displaystyle C_{\text{fix}} \propto t^{2}
缺陷修复成本约与时间平方成正比:越晚发现,代价呈指数级上升,这正是“测试先行”的经济学理由。
  1. 修复成本 C_fix把一个缺陷从发现到修复所消耗的人力与时间折算成成本,越晚发现,涉及的模块和返工面越大。
    CfixC_{\text{fix}}
  2. 时间 t缺陷从“出生”到“被发现”之间跨过的阶段数,包括需求、设计、编码、测试、上线。
    tt
  3. 平方正比关系成本不是线性上涨,而是随阶段平方级爆炸——上线后的一个 bug 可能是需求阶段的几十倍代价。
    Cfix∝t2C_{\text{fix}} \propto t^{2}

红绿重构:三拍节奏

阿明按验收单做出了第一锅肉,端到师父面前。师父只说了三个字:“不合格,肉不够糯。”阿明没有辩解,回灶台调了十五分钟火候,再端出来——过关。短短二十分钟,他经历了两次“红灯转绿灯”。

TDD 的核心循环就叫“红-绿-重构”。红灯:写下一个新测试,运行并确认它失败,因为实现还不存在或不对。绿灯:用最少量的代码让测试通过,功能契约成立。重构:在测试全绿的保护下,清理重复、提取函数、改善命名——行为不能变,结构可以变。

红灯是整支循环的灵魂。很多新手跳过“确认失败”直接写测试再写实现,结果测试永远一次通过——这等于没有验收单就宣布菜合格。只有亲眼看见红灯,你才知道这个测试真的在守护某种行为,而不是自说自话。

师父的节奏像节拍器:失败、通过、打磨,短促而稳定。每三到五分钟一轮,设计就往前走一小步。TDD 不是一口气写完再回头修,而是用小步快跑,让代码永远处于“刚通过测试”的可发布状态。

Red→最小实现Green→消除坏味道Refactor→新测试Red\displaystyle \text{Red} \xrightarrow{\text{最小实现}} \text{Green} \xrightarrow{\text{消除坏味道}} \text{Refactor} \xrightarrow{\text{新测试}} \text{Red}
红-绿-重构是一个闭环:每个新测试先失败(红),用最小实现让它通过(绿),再优化结构(重构),然后写下一个测试重新进入红灯。
  1. Red 红灯写下一个描述期望行为的测试,立刻运行并确认它失败——失败证明测试真的在守护行为。
    Red\text{Red}
  2. 最小实现只写让当前红灯变绿的最少代码,不做超前设计,避免“你可能用不到”的过度工程。
    →最小实现\xrightarrow{\text{最小实现}}
  3. Green 绿灯测试通过,功能契约成立;此时代码可能很丑,没关系,下一步再收拾。
    Green\text{Green}
  4. 重构与新测试保持绿灯优化结构,再写下一个测试,进入新一轮红灯,循环往复形成节奏。
    →消除坏味道Refactor→新测试Red\xrightarrow{\text{消除坏味道}} \text{Refactor} \xrightarrow{\text{新测试}} \text{Red}

测试粒度:金字塔的取舍

阿明学聪明了,开始往验收单上狂加标准:肉要切 3 厘米见方、冰糖精确到 7 克、必须用第 3 号锅……师父一把抢过单子:“验收单要锁味道,不是锁手法!”测试粒度,就是这门平衡术。

软件测试世界有一座金字塔:底层是数量最多、跑得最快的单元测试,验证单个函数或类的行为;中层是集成测试,验证模块之间的协作;顶层是稀少的端到端测试,模拟用户完整的操作旅程。TDD 的主战场在底层——快、稳、一出错就知道毛病在哪。

判断粒度合不合适,标准是看“行为”还是“动作”:好测试描述“输入什么、输出什么”,坏测试断言“内部调了哪个私有方法、中间变量等于几”。前者允许你自由重构,后者让你动一行代码碎一片测试。

好的验收单有弹性:它锁住“味道”,但绝不锁死“手法”。单元、集成、端到端的配比像调料——底层打底,中层调味,顶层提香,比例对了,菜才稳。

Nunit≫Nintegration≫Ne2e\displaystyle N_{\text{unit}} \gg N_{\text{integration}} \gg N_{\text{e2e}}
测试金字塔:单元测试数量最多、速度最快,端到端测试数量最少、代价最高。比例一旦倒置,回归成本会拖垮团队。
  1. 单元测试数 N_unit直接验证单个函数或类的用例,毫秒级执行,是整个金字塔的地基。
    NunitN_{\text{unit}}
  2. 集成测试数 N_integration验证模块之间协作的用例,需要组装环境,数量居中。
    NintegrationN_{\text{integration}}
  3. 端到端测试数 N_e2e模拟用户完整流程的用例,最慢最贵,必须少而精。
    Ne2eN_{\text{e2e}}
  4. 远大于 ≫三层数量严格递减,底层越厚,回归成本越低;数量关系绝不能倒置。
    ≫\gg

设计驱动:接口先行

为了单独验收“炒糖色”这一步,阿明发现必须把糖色锅从大灶上拆出来,单独支一个小灶——原本缠在一起的后厨流程,被验收单逼着重排了。这正是 TDD 最反直觉的回报:写测试不直接产出功能,却逼迫你把设计想清楚。

因为要先写测试,你就必须先回答三个问题:这个函数叫什么?参数是什么?返回什么?接口先于实现被确定,这就是“接口先行”。实现可以后写,契约必须先立。

可测试性说到底,是依赖能不能被替换。如果函数内部直接 new 一个数据库连接、直接读全局配置,测试就没法隔离。如果依赖通过参数注入,测试就能传入替身。TDD 像水流,总往阻力小的地方走——长期下来,代码自然长成依赖注入、面向接口、低耦合的样子。

师父望着重排后的后厨感慨:“不是验收单管着菜,是验收单逼着后厨变得井井有条。”测试先行的人,往往也是架构清晰的人——设计不是画出来的,是测出来的。

Ttestable=NinjectableNtotal\displaystyle T_{\text{testable}} = \frac{N_{\text{injectable}}}{N_{\text{total}}}
可测试性 = 可注入依赖数 ÷ 总依赖数。比值越接近 1,组件越容易被隔离测试,设计就越解耦。
  1. 可注入依赖数 N_injectable能通过参数或构造器传入、测试时可替换成替身的依赖个数。
    NinjectableN_{\text{injectable}}
  2. 依赖总数 N_total被测代码直接使用的全局变量、静态方法、网络与数据库连接等外部依赖的总数。
    NtotalN_{\text{total}}
  3. 可测试性 T_testable两者相除得到一个 0 到 1 的比值,TDD 会逼着这个比值不断变大,设计随之解耦。
    Ttestable=NinjectableNtotalT_{\text{testable}} = \frac{N_{\text{injectable}}}{N_{\text{total}}}

双派之争:真材实料还是替身

后厨来了第二位师父,两位在“怎么验收”上较上了劲。大师傅说:整锅端上来,用真材实料试味道。二师父说:每步单独验收,用假糖假醋也能判断工序对不对。这正是软件界经典 TDD 与 London 学派的路线之争。

经典 TDD(又称底特律学派)主张从内到外:测试被测对象的真实行为,连它的协作者(数据库、其他类)也一起用真的,通过“状态验证”判断结果对不对。London 学派主张由外向内:先定对外接口,把每个协作者都替换成替身,通过“交互验证”检查消息有没有按预期传递。

两派的分歧,可以浓缩成一个数字:测试里替身的数量。经典派替身趋近于零,相信真实协作能暴露集成问题;London 派替身数等于协作者数,追求完全隔离,故障一发生立刻知道是哪个环节。前者更真实,后者更精准。

两位师父最终和解:小灶台用真材实料,大型宴席用标准化流程。真实项目里两种流派经常混用——核心业务用真实协作保真,外围边界用替身隔离提速。门派不重要,守住“行为有验收”才是根本。

kmock={0,经典 TDD 用真实协作者mcollab,London 学派全用替身\displaystyle k_{\text{mock}} = \begin{cases} 0, & \text{经典 TDD 用真实协作者} \\ m_{\text{collab}}, & \text{London 学派全用替身} \end{cases}
替身数量是两派的分水岭:经典 TDD 用 0 个替身做真实协作,London 学派用 m 个替身做完全隔离,m 等于被测对象的协作者总数。
  1. 替身数量 k_mock测试中用来顶替真实协作者(数据库、外部服务、其他类)的 mock/stub 个数。
    kmockk_{\text{mock}}
  2. 经典 TDD 取 0使用真实协作者组装测试,做状态验证——检查“结果对不对”。
    kmock=0k_{\text{mock}} = 0
  3. London 学派取 m_collab由外向内设计接口,把每个协作者逐一替换成替身,做交互验证——检查“消息传没传对”。
    kmock=mcollabk_{\text{mock}} = m_{\text{collab}}

AI工程应用:给黑盒立规矩

如今后厨多了一位“AI 大厨”——一个训练好的模型。它做的菜(预测结果)连师父都说不清为什么好吃。面对黑盒,TDD 的逻辑反而更值钱:无法理解它,就去验收它。

AI 工程里,TDD 落在三张验收单上。第一张给数据管线:清洗、去重、切分、单位换算是确定性的普通代码,完全可以用红绿重构驱动。第二张给模型评估:精确率、召回率、F1 达到业务阈值才算绿灯。第三张给提示词:把典型输入与期望输出写成回归测试,每次改 prompt 先跑一遍,防止旧能力退化。

在 AI 生成代码成为日常的今天,TDD 又多了一个角色——人给 AI 立的验收单。让 AI 写实现之前,人先写测试;AI 的产出通不过测试,红灯亮起,返回重写。这比肉眼 Review 更客观,也比“AI 说什么都对”更可靠。

师父看着 AI 大厨说:“黑盒不可怕,可怕的是没有验收单的黑盒。”给 AI 立规矩,TDD 就是那张规矩单。模型会迭代、prompt 会改版,有了验收单,每次变化都看得见是变好还是变坏。

F1=2×P×RP+R\displaystyle F1 = 2 \times \frac{P \times R}{P + R}
F1 是精确率 P 与召回率 R 的调和平均,综合衡量模型质量。P 或 R 任意一项偏低,F1 都会被拉低,是 AI 模型最常见的“绿灯验收线”。
  1. 精确率 P预测为正的样本里真正例占多少,对应“端上桌的菜中合格的比例”。
    P=TPTP+FPP = \frac{TP}{TP + FP}
  2. 召回率 R所有真正例中被找出的比例,对应“所有合格的菜都被端上桌的比例”。
    R=TPTP+FNR = \frac{TP}{TP + FN}
  3. F1 调和平均P 与 R 的调和平均,两项任一偏低都会拉低总分,是 AI 测试最常用的红灯/绿灯判据。
    F1=2×P×RP+RF1 = 2 \times \frac{P \times R}{P + R}

遗留代码策略:先拓片再动刀

阿明被派去接管一家老店。后厨的菜谱东一张西一张,那道传了三代的老汤,谁都不敢碰。老店不能停业重来,只能边营业边改造——这正是遗留代码的日常。

软件界给遗留代码下过一个扎心定义:没有测试的代码。改造它的安全流程分三步:第一步写特征测试,像考古拓片一样,把当前行为原样记录下来——先不评价对错,先记录味道;第二步找接缝,在不动行为的前提下找到可以插入测试点的地方;第三步增量覆盖,每改一小块就多一层测试保护。

特征测试与 TDD 的常规测试方向相反:TDD 先写期望再写实现,特征测试先读现状再记录现状。它不回答“应该是什么”,只锁定“现在是什么”。有了拓片,老汤才敢动,动了才知道有没有变味。

老店改造像考古:先拓片(特征测试),再开凿(接缝改造),最后修复(重构)。每一步都有保护网,老店越改越年轻。对遗留代码,最大的尊重不是推倒重来,而是带着测试小心翼翼地为它续命。

Bcoverage=bexecutedbtotal\displaystyle B_{\text{coverage}} = \frac{b_{\text{executed}}}{b_{\text{total}}}
分支覆盖率 = 已执行分支数 ÷ 总分支数。特征测试把老代码的分支覆盖率垫高,重构前就有了“变味报警器”。
  1. 已执行分支 b_executedif/else、switch、三目运算等分支中,被测试真实走过的分支数量。
    bexecutedb_{\text{executed}}
  2. 总分支数 b_total静态分析出的全部分支路径数量。
    btotalb_{\text{total}}
  3. 分支覆盖率 B两者相除得到覆盖率,特征测试的目标就是先把旧代码的覆盖率安全垫高。
    Bcoverage=bexecutedbtotalB_{\text{coverage}} = \frac{b_{\text{executed}}}{b_{\text{total}}}

实践陷阱:护栏要对着悬崖

阿明学了一个月,开始走火入魔:给每一行代码写测试,给每个私有方法立验收单,写测试的时间比炒菜还长。师父敲了敲他的头:“验收单不是越多越好,护栏要对着悬崖,而不是围着平地。”

TDD 的实践陷阱主要有四类。一是过度测试:为 getter、setter 和琐碎逻辑写测试,维护成本超过价值;二是测试实现细节:断言内部变量和调用顺序,重构时测试大面积崩塌;三是红绿颠倒:先写实现再补测试,测试永远绿灯,失去驱动力;四是用例腐化:测试从不维护,和代码不同步,红灯连片也没人管。

判断测试值不值的公式很朴素:维护成本约等于测试数量乘以测试与实现的耦合度。高耦合的测试最可怕——它们把重构锁死,把精力耗干。好测试是行为的纪录片,坏测试是实现细节的自传。

师父最后说:“测试不是业绩,是护栏;护栏的密度要看悬崖在哪里。”把测试用在刀刃上,TDD 是神器;用错了地方,它只是自我感动式的仪式。红绿灯要管住的是风险,不是每一寸代码。

Cmaintain∝Ntest×Scoupling\displaystyle C_{\text{maintain}} \propto N_{\text{test}} \times S_{\text{coupling}}
测试维护成本 ≈ 测试数量 × 测试与实现的耦合度。数量多不可怕,可怕的是高耦合——实现一变,测试成片碎掉。
  1. 测试数量 N_test测试用例总量,数量膨胀并不等于质量提升。
    NtestN_{\text{test}}
  2. 测试与实现耦合度 S_coupling测试断言了多少实现细节(内部变量、私有方法、调用顺序),耦合越高,实现一变测试就崩。
    ScouplingS_{\text{coupling}}
  3. 维护成本 C_maintain总维护成本约等于两者乘积——两个“高数量但低耦合”的测试,可能比二十个坏测试更轻省。
    Cmaintain∝Ntest×ScouplingC_{\text{maintain}} \propto N_{\text{test}} \times S_{\text{coupling}}

AI 应用场景

提示词回归测试:给 prompt 立验收单

阶段:模型评估与测试

解决的问题:传统方式修改 prompt 全凭手感,改完常用问题变差也没人发现。用 TDD 把“典型输入→期望输出”写成测试集,每次改动前先跑一遍,红灯亮了就知道哪类能力退化了。

具体案例:某客服机器人的 FAQ 检索 prompt 要升级措辞,团队先为 20 个高频问题写断言测试(红灯:新用例加入后当前实现部分失败)→ 调整 prompt(绿灯)→ 把公共约束抽成模板变量(重构)。此后每次 prompt 改动都自动回归,旧能力再也没悄悄丢失过。

数据管线 TDD:清洗逻辑不裸奔

阶段:数据工程

解决的问题:数据清洗、去重、单位换算等函数是普通确定性代码,一旦改错,全部下游训练数据悄悄被污染。传统做法靠人工抽查,治标不治本。

具体案例:某推荐系统团队对“时间戳统一为 UTC”“金额保留两位小数”“异常值剔除”等函数一一编写单元测试,纳入 CI 每次提交自动执行。一次某同事把时区换算写反,CI 红灯拦下,避免了一整天的错误数据灌进训练集。

遗留推荐服务的特征测试

阶段:存量系统改造

解决的问题:接手一个没人敢动的老模型服务,参数、特征、逻辑纠缠不清,任何小改动都可能让线上效果崩盘。

具体案例:团队先取 100 条历史样本,记录当前模型的预测结果作为特征测试基线;然后给特征工程代码加接缝、插测试点,把每个特征函数逐一用特征测试锁定行为;最后才开始重构。每次改动跑一遍基线,输出偏离立即红灯,改完三个迭代,老服务被稳稳地现代化了。

Python 演示

python60 行
"""TDD 实战演示: 红-绿-重构三步循环. 场景: 为"满减 + 会员折扣"的订单计价器写测试.顺序严格遵守 TDD:红灯: 测试先行(先描述行为, 此时实现尚未完成)绿灯: 补最小实现, 全部测试通过重构: 保持行为不变, 优化代码结构"""import unittest  class OrderCalculator:    """订单计价器(由 TDD 驱动构建)."""     def __init__(self, discount=0.1):        self.discount = discount  # 会员折扣率, 默认 10%     def total(self, items, is_member=False):        """先算原价, 再算会员折扣, 最后触发满减."""        raw = sum(items)        if is_member:            raw = raw * (1 - self.discount)        if raw >= 100:            raw = raw - 20        return round(raw, 2)  class TestOrderCalculator(unittest.TestCase):    """验收单: 先描述行为, 跑出红灯, 再让实现变绿."""     def setUp(self):        self.calc = OrderCalculator()     def test_plain_sum(self):        self.assertEqual(self.calc.total([10, 20, 30]), 60)     def test_minus_20_when_over_100(self):        self.assertEqual(self.calc.total([60, 60]), 100)     def test_member_discount(self):        self.assertEqual(self.calc.total([60, 60], True), 88.0)     def test_discount_before_full_reduction(self):        self.assertEqual(self.calc.total([50, 60], True), 99.0)  def run_tdd_cycle():    """模拟 TDD 三阶段: 红 -> 绿 -> 重构."""    print("第一步【红灯】: 先运行测试, 此时实现若缺失会失败")    suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestOrderCalculator)    result = unittest.TextTestRunner(verbosity=2).run(suite)    print("第二步【绿灯】: 最小实现完成, 全部用例通过")    print("第三步【重构】: 保持测试全绿, 把阈值提取为常量")    return result.wasSuccessful()  if __name__ == "__main__":    ok = run_tdd_cycle()    print("全部通过!" if ok else "存在失败用例, 回到红灯阶段")    raise SystemExit(0 if ok else 1)
目的:演示 TDD 的红-绿-重构循环——测试先行描述行为,最小实现让测试变绿,重构保持行为不变。
  1. class TestOrderCalculator(unittest.TestCase):验收单本体,用 unittest 描述“应该发生什么”,实现缺失时它会红灯。
  2. self.assertEqual(self.calc.total([60, 60], True), 88.0)覆盖“先打折再满减”的边界行为,专治顺序写反的 bug。
  3. raw = sum(items)绿灯阶段的最小实现,先满足最简单的那条纯求和用例。
  4. if raw >= 100: raw = raw - 20第二个绿灯动作:实现满减规则,让对应测试转绿。
  5. result.wasSuccessful()把测试结果变成布尔值,失败时返回 False,模拟“红灯报警”。

常见误区

误区我以为 TDD 就是把所有测试先写完,然后一次性把实现全部写出来。反正都是“先测试后实现”,顺序对了就行。

正解TDD 的精髓是小步循环:每写一个测试→看到它失败(红)→写最小实现让它通过(绿)→重构(保持绿)→再写下一个测试。一次攒一堆测试再实现,会丢掉红灯的指导,也无法获得持续反馈——就像把验收单写完却不看结果就闷头炒菜。

为何常见:把 TDD 误解成“时间顺序”而非“节奏循环”,忽略了“亲眼看见失败”才是驱动设计的燃料。

误区覆盖率高就代表测试好,我会想办法把覆盖率冲到 100%,包括给 getter/setter 也写测试。

正解覆盖率只说明哪些代码行被执行了,不说明行为有没有被正确断言。为了数字强行测私有方法和琐碎逻辑,会让测试高耦合、高维护,反而锁死重构。质量取决于断言的价值:一个断言关键行为的好测试,胜过一百个凑覆盖率的空测试。覆盖率是结果,不是目标。

为何常见:覆盖率是唯一能量化的测试指标,KPI 驱动下容易“为了数字而数字”,忘了测试的本职是守护行为。

误区我把测试写得很“深入”,断言私有变量的中间值和方法调用顺序,觉得这样才算把代码测透了。

正解测试应锁定外部可观察的行为契约,而非内部动作。测实现细节会让测试与实现强耦合:你只是把变量名改得更清楚,几十个测试跟着碎掉。行为不变时测试应当全绿——这正是重构安全网的前提。想测私有逻辑,正确的做法是把它提取成独立的公有函数再直接测它。

为何常见:把“测得多”当成“测得准”,分不清“行为”与“动作”——就像验收单写“必须用 3 号勺”而不是“味道要甜咸适中”。

误区先写测试再写实现,等于同一份功能写两遍,太浪费时间了。直接写代码跑起来再说。

正解短期看,TDD 确实多了一步;但缺陷修复成本随时间平方增长,TDD 把缺陷拦截在诞生瞬间,省下的是大量调试、返工和线上事故的时间。对复杂逻辑和高变需求,TDD 反而更快,因为它用测试把“理解”固化下来,改起来才敢下手。

为何常见:只用“敲代码的时间”衡量效率,忽略了“调试+回归+救火”的巨大隐性成本——就像阿明觉得写验收单耽误炒菜,却忘了返工重做的代价更大。

面试题

Q1. TDD 红绿重构循环中,“红灯”状态的含义是?

单选题 · easy

  • A.代码风格检查未通过
  • B.新写的测试正在失败← 正确答案
  • C.生产环境出现线上事故
  • D.CI 流水线资源不足

答案:B。红灯指新测试运行后失败的状态,此时实现尚未满足测试描述的行为。A 是代码风格问题,C 是运维事故,D 是基础设施问题,都与 TDD 中“用失败测试驱动实现”的红灯无关。

Q2. TDD 中“先写测试”的直接动机是?

单选题 · easy

  • A.提高覆盖率数字好看
  • B.用测试书写行为规格← 正确答案
  • C.取代人工代码评审
  • D.让测试部门无事可做

答案:B。测试先行是为了把“期望行为”固化成可执行规格,让实现有明确的验收标准。覆盖率是副产品而非目的,A 颠倒了因果;C 和 D 明显与 TDD 无关。

Q3. 经典 TDD(底特律学派)与 London 学派的核心分歧是?

单选题 · mid

  • A.是否采用强类型系统
  • B.测试中替身的使用程度← 正确答案
  • C.是否使用版本控制管理
  • D.能否用 AI 自动生成

答案:B。经典派倾向用真实协作者做状态验证,London 派倾向用替身隔离全部协作者做交互验证,分歧核心就是测试替身的使用程度。其余选项与两派之争无关。

Q4. 测试金字塔建议的用例数量结构是?

单选题 · mid

  • A.单元约等于集成与端到端
  • B.端到端最多集成次之单元最少
  • C.单元最多集成次之端到端最少← 正确答案
  • D.集成最多单元次之端到端最少

答案:C。金字塔底层是单元测试,最便宜最快,数量最多;越高层越贵越慢,数量越少,所以 C 正确。A 的均等结构不成立,B 是倒金字塔会拖垮回归,D 把集成放在最多也不符合成本规律。

Q5. 对遗留代码(legacy code)实施 TDD,第一步通常应做什么?

单选题 · mid

  • A.推倒全部重写代码
  • B.编写特征测试锁定当前行为← 正确答案
  • C.把覆盖率强行冲到百分百
  • D.直接删除没有测试的功能

答案:B。特征测试像考古拓片,先把当前行为原样记录下来作为安全网,是改造遗留代码的第一步。A 重写风险极高,C 直接追覆盖率容易落入无效测试,D 删除功能可能丢失业务价值。

Q6. 下列哪项最适合作为 AI 模型的“绿灯验收线”?

单选题 · hard

  • A.训练集损失降到零
  • B.F1 达到业务阈值← 正确答案
  • C.模型参数量做得最大
  • D.推理时延压到最低

答案:B。F1 综合精确率与召回率,能衡量模型在正负样本上的整体表现,适合当验收线。训练损失为 0 是过拟合信号,参数最大和时延最低只代表资源与速度,不代表质量达标。

Q7. 哪些做法违背了 TDD 的原则?

多选题 · mid

  • A.先写实现再补测试← 正确答案
  • B.测试锁定外部行为
  • C.为私有方法大量写测试← 正确答案
  • D.攒齐一批测试再写实现← 正确答案

答案:ACD。先实现后补测试(A)失去红灯驱动;为私有方法写测试(C)导致高耦合高维护;攒一批测试再实现(D)丢掉小步反馈。B 是正确的 TDD 做法:测试锁定外部行为契约。

Q8. TDD 循环的三个阶段包括?

多选题 · easy

  • A.红灯阶段测试失败← 正确答案
  • B.绿灯阶段最小实现← 正确答案
  • C.重构保持行为不变← 正确答案
  • D.发布部署上线运行

答案:ABC。TDD 循环是“红-绿-重构”:先让测试失败(A),再用最小实现让它通过(B),最后保持全绿地优化结构(C)。发布部署不属于 TDD 循环本身。

Q9. 关于测试粒度,下列说法正确的是?

多选题 · mid

  • A.单元测试应快且隔离← 正确答案
  • B.集成测试验证模块协作← 正确答案
  • C.端到端测试越多越好
  • D.粒度要锁行为而非动作← 正确答案

答案:ABD。单元测试要快、要能隔离运行(A 对);集成测试验证模块间的协作(B 对);端到端测试昂贵脆弱,“越多越好”错误(C 错);粒度应锁定外部行为而非实现动作(D 对)。

Q10. AI 工程中“提示词回归测试”的核心思路是?

单选题 · hard

  • A.改 prompt 前先跑断言测试← 正确答案
  • B.用更大的模型替换旧模型
  • C.删除所有历史 prompt 版本
  • D.发布之后才人工抽查

答案:A。提示词回归测试把典型输入与期望输出写成断言测试,每次修改 prompt 先运行一遍防回归,这正是 TDD 在 AI 场景的落地。B 是模型升级策略,C 删除历史是反向操作,D 事后抽查缺乏预防性。