本节摘要:测试从几条涨到几十条时,涨法比数量重要。三角法用第二个例子逼实现从硬编码走向泛化;参数化把同构用例收编成表格,但不该收编的坚决拆开;三定律则守着整个膨胀过程的底线。读完你应能判断"下一条测试该怎么写",让测试集随需求有节制地生长。
绿灯阶段的"最小实现"常带点耍赖味道:第一条"空购物车总额为零"的测试,最忠实的实现是 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 机制),失败报告从"第组参数失败"变成"缺大写的例子失败"——边界用例的存在理由由此进了报告本身,与行注释形成双重存档。两招都用上后,参数化表格就是这组行为的活文档,不只是测试。
测试集越长越大的过程,也是三定律最容易破戒的过程。把定律翻译成膨胀期的检查动作:
这三条执行起来最大的敌人不是懒,是急。Deadline 逼近时,人人都想"先把功能写了补测试"——历史经验是补的测试永远停在"主路径快乐案例",因为补测试时你已经知道实现长什么样,注意力全在怎么配合实现上。测试先行的价值恰恰在于它剥夺了你的先知视角,逼你从使用者的位置思考接口。
**信号一:测试文件比实现文件大五倍不止。**多半是参数化没用上,或断言在锁实现细节。对策:按本节的收编标准合并同构用例。**信号二:跑一遍测试要等一分钟以上。**节律对速度的依赖在 2.2 立过规矩,测试变慢等于循环变慢。对策:把慢测试标记出来隔离跑(pytest 的 marker 机制第 6 章会讲),保证主循环里只有毫秒级用例。