2 · 断言设计原则
单一职责 · 可读性 · 可维护性 · 独立性
写断言这件事,看着简单,其实门道不少。我见过太多测试代码里塞满了又长又臭的断言——一个assert里面恨不得把整个系统的状态都检查一遍。结果呢?报错了你根本不知道是哪个条件挂了。说白了,断言也需要设计,而且得遵循几条硬原则。
我个人习惯把断言设计原则归纳为四个词:单一职责、可读性、可维护性、独立性。下面咱们一个一个聊。
单一职责原则
一个断言只做一件事。听起来像废话?但我见过有人在一个assert里写了三个and,还混着or。你想想看,如果失败了,你根本不知道是左边条件不满足还是右边逻辑没通过。
✅ 推荐写法:
# 每个断言只验证一个点
assert response.status_code == 200
assert response.json()['state'] == 'active'
assert len(response.json()['items']) > 0
❌ 反面教材:
# 一个断言塞了三个条件,失败后一脸懵
assert response.status_code == 200 and response.json()['state'] == 'active' and len(response.json()['items']) > 0
我在项目中遇到过好几次,因为偷懒把多个检查写在一起,结果CI挂了,排查花了大半天。后来我强制团队:一个assert只对一个事实负责。谁违反谁请喝咖啡。
可读性原则
断言就是可执行的文档。好的断言,别人一看就知道你在测什么。我经常跟团队说:你写的断言,要让新来的同事半分钟内看懂。
- 用有意义的变量名:别用
a、b、tmp。 - 断言消息要写人话:比如
assert x > 0, "库存数量必须大于0"。 - 避免魔法数字:把200写成
HTTPStatus.OK不香吗?
💡 我个人习惯在断言失败消息里加上预期和实际值,这样日志里一眼就能看出偏差。比如:assert actual == expected, f"期望 {expected},实际 {actual}"。
可维护性原则
断言不是写一次就完事了。回归测试中,业务逻辑经常变,断言也得跟着改。如果断言代码到处重复,改起来就是噩梦。
我曾经在一个遗留项目里看到,同样的“用户权限校验”断言在50个测试用例里复制粘贴。后来权限规则变了,改到想哭。所以,抽取公共断言方法是必须的。
# 抽取成辅助函数,一处修改,全局生效
def assert_user_has_permission(user, permission):
assert user.is_active, f"用户 {user.id} 未激活"
assert permission in user.permissions, f"用户缺少权限 {permission}"
另外,断言本身也要保持简洁。如果一个断言函数超过10行,你就该考虑拆分了。
独立性原则
每个断言应该独立于其他断言。也就是说,断言A失败不应该影响断言B的执行。这一点在软断言模式里尤其重要。
⚠️ 我曾经踩过一个坑:在同一个测试方法里,前面的断言抛异常,后面的断言根本没机会跑。后来改用软断言框架(比如Python的 pytest-check),所有断言都会执行,失败结果统一收集。这才是独立性该有的样子。
独立性还意味着:断言之间不要共享可变状态。别在一个断言里修改全局变量,然后指望另一个断言读到正确的值。你想想看,如果测试执行顺序变了,结果可能就不同了——那还叫回归测试吗?
四条原则的关系
它们不是孤立的。单一职责让断言更聚焦,可读性让意图更清晰,可维护性降低长期成本,独立性保证测试的鲁棒性。我一般用下面这个表格帮团队快速对齐:
| 原则 | 核心问题 | 一句话检查法 |
|---|---|---|
| 单一职责 | 这个断言在检查几件事? | 只能回答一个“是/否” |
| 可读性 | 别人能一眼看懂吗? | 去掉注释还清晰吗? |
| 可维护性 | 业务变化时改哪里? | 是不是只改一个地方? |
| 独立性 | 断言之间会互相干扰吗? | 打乱顺序结果一样吗? |
嗯,其实这四条原则不光适用于断言,写任何测试代码都可以参考。但断言是测试的“最后一公里”,尤其值得用心设计。