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),所有断言都会执行,失败结果统一收集。这才是独立性该有的样子。

独立性还意味着:断言之间不要共享可变状态。别在一个断言里修改全局变量,然后指望另一个断言读到正确的值。你想想看,如果测试执行顺序变了,结果可能就不同了——那还叫回归测试吗?

四条原则的关系

它们不是孤立的。单一职责让断言更聚焦,可读性让意图更清晰,可维护性降低长期成本,独立性保证测试的鲁棒性。我一般用下面这个表格帮团队快速对齐:

原则核心问题一句话检查法
单一职责这个断言在检查几件事?只能回答一个“是/否”
可读性别人能一眼看懂吗?去掉注释还清晰吗?
可维护性业务变化时改哪里?是不是只改一个地方?
独立性断言之间会互相干扰吗?打乱顺序结果一样吗?

嗯,其实这四条原则不光适用于断言,写任何测试代码都可以参考。但断言是测试的“最后一公里”,尤其值得用心设计。