2.4 三角法、参数化与三定律:让用例长大


2.4 三角法、参数化与三定律:让用例长大

本节摘要:测试从几条涨到几十条时,涨法比数量重要。三角法用第二个例子逼实现从硬编码走向泛化;参数化把同构用例收编成表格,但不该收编的坚决拆开;三定律则守着整个膨胀过程的底线。读完你应能判断"下一条测试该怎么写",让测试集随需求有节制地生长。

一、三角法:用第二个例子逼出泛化

绿灯阶段的"最小实现"常带点耍赖味道:第一条"空购物车总额为零"的测试,最忠实的实现是 return 0——写死,但它绿了。初学者对此浑身难受,觉得作弊;老手却视之为节律的一部分,名叫伪实现(fake it)。真正治伪实现的是第二条测试:往购物车里放一件十元的商品,总额该是多少?写死的天花板塌了,实现不得不泛化:

def test_empty_cart_total_is_zero(): assert Cart().total() == 0 def test_single_item_total_is_its_price(): cart = Cart() cart.add(sku="BOOK", price=Decimal("10.00"), qty=1) assert cart.total() == Decimal("10.00") # 两个例子一起看,实现被迫从写死走向归纳 def test_multiple_items_total_is_sum(): cart = Cart() cart.add(sku="BOOK", price=Decimal("10.00"), qty=2) cart.add(sku="PEN", price=Decimal("3.50"), qty=1) assert cart.total() == Decimal("23.50")

三条测试像三角形的三个顶点:第一个顶点定下行为存在,第二个顶点定下方向,第三个顶点锁死形状。实现者每多拿一个顶点,猜测空间就小一圈——这就是三角法名字的由来。Kent Beck 的建议是:概念简单时直接写真实现,概念拿不准时才用伪实现加三角法,让泛化的时机由测试推动,而不是由想象力推动。

💡 三角法最妙副产品是用例的边界感:写第二条测试时你会自然去挑最"硌"的输入——刚好一个元素、刚好零元、刚好重名。挑选硌点的直觉,就是下一个设计冒出来的地方。

二、参数化:同构用例收编成表格

密码校验器写完三条规则后,测试文件里开始出现这样的同构堆:

def test_short_password_rejected(): result = rate("aB1") assert result.valid is False def test_no_digit_rejected(): result = rate("abcdefghX") assert result.valid is False

它们结构相同、只有输入与期望不同——这正是参数化的用武之地:

@pytest.mark.parametrize("password,expect_valid", [ ("aB1", False), # 太短 ("abcdefghX", False), # 缺数字 ("alllowercase1", False),# 缺大写 ("Str0ngPass", True), # 三类齐全 ]) def test_password_validity(password, expect_valid): assert rate(password).valid is expect_valid

十条测试收编成一张四行表格,失败时 pytest 会精确报出是哪组参数红——表达力没损失,重复消失了。但收编有红线:只收编"断言同构"的用例。上面这组全是同一个问题"有效吗",所以能收;如果某条测试还要检查 reasons 内容、另一条检查 level,它们断言的是不同行为,硬塞进参数化会造出满身 if 的怪物。判断口诀:参数化后测试体里若需要分支,就该拆回去。

⚠️ 参数化的表格里给每行配注释说明"这行为什么存在"。半年后表格涨到二十行,注释是唯一能救你辨认边界用例的东西。

参数化还有个进阶用法值得知道:给参数行起可读的名字(ids 机制),失败报告从"第组参数失败"变成"缺大写的例子失败"——边界用例的存在理由由此进了报告本身,与行注释形成双重存档。两招都用上后,参数化表格就是这组行为的活文档,不只是测试。

三、三定律:膨胀期的红线

测试集越长越大的过程,也是三定律最容易破戒的过程。把定律翻译成膨胀期的检查动作:

  1. 无红灯不写码——检查动作:翻提交历史,若某次提交只有实现没有同名测试,就违了戒。测试先行不是仪式,是证据链。
  2. 只写刚好失败的测试——检查动作:新写测试若当场通过,要么删掉,要么说明它断言的是既有行为(回归测试合法,但要明说身份,别冒充新需求的红灯)。
  3. 只写刚好通过的实现——检查动作:code review 时对"顺手加的能力"提问:"哪条测试要它?"答不上就撤。

这三条执行起来最大的敌人不是懒,是急。Deadline 逼近时,人人都想"先把功能写了补测试"——历史经验是补的测试永远停在"主路径快乐案例",因为补测试时你已经知道实现长什么样,注意力全在怎么配合实现上。测试先行的价值恰恰在于它剥夺了你的先知视角,逼你从使用者的位置思考接口。

四、膨胀期的两个 warning

**信号一:测试文件比实现文件大五倍不止。**多半是参数化没用上,或断言在锁实现细节。对策:按本节的收编标准合并同构用例。**信号二:跑一遍测试要等一分钟以上。**节律对速度的依赖在 2.2 立过规矩,测试变慢等于循环变慢。对策:把慢测试标记出来隔离跑(pytest 的 marker 机制第 6 章会讲),保证主循环里只有毫秒级用例。

本节要点回顾

  • 三角法三顶点:存在、方向、形状,伪实现由第二条测试处决。
  • 参数化只收编断言同构的用例,测试体出现分支即拆回。
  • 表格行配注释,边界用例要说清存在理由。
  • 三定律的膨胀期检查动作:查提交配对、查当场通过、查无证功能。
  • 测试变慢就是节律变慢,慢用例隔离,主循环只留毫秒级。

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