陷阱三:断言过于宽泛——测试通过了,但真的测对了吗?
说实话,这个坑我踩过不止一次。
有一次,我负责一个支付系统的接口测试。写了个断言,大概长这样:assert response.status_code == 200。测试跑通了,绿油油一片,心里美滋滋。结果上线当天,用户反馈说「支付成功了,但余额没扣」。
你猜怎么着?接口确实返回了200,但返回的JSON里有个 "success": false。我的断言根本没检查这个字段。
嗯,这就是典型的「断言过于宽泛」。测试通过了,但真的测对了吗?
什么是「宽泛断言」?
说白了,就是你的断言只覆盖了结果的一小部分。就像你检查一个人有没有出门,只看他穿了鞋,却没看他是不是还在床上躺着。
常见的宽泛断言有这些:
- 只检查HTTP状态码(200就完事了)
- 只检查返回不为空(有数据就行,不管对不对)
- 只检查某个字段存在(不检查值是否正确)
- 用
assert True这种毫无意义的断言
我个人习惯,把断言分成三个层次:
| 层次 | 检查内容 | 示例 |
|---|---|---|
| 第一层 | 状态码/协议层 | assert response.status_code == 200 |
| 第二层 | 业务状态码/错误码 | assert data["code"] == 0 |
| 第三层 | 核心业务数据 | assert data["balance"] == expected_balance |
很多测试只做到第一层,甚至第二层。第三层才是真正「测对了」的关键。
一个真实的例子
我在项目中遇到过这样一个场景:测试一个用户注册接口。
宽泛的写法是这样的:
def test_register():
response = client.post("/register", json={"username": "test", "password": "123456"})
assert response.status_code == 200
# 完了,就这一句
这个测试能发现什么问题?几乎什么都发现不了。就算后端返回了 {"error": "用户名已存在"},只要状态码是200,测试照样绿。
那应该怎么写?我建议这样:
def test_register_success():
response = client.post("/register", json={"username": "test_user_001", "password": "123456"})
assert response.status_code == 200
data = response.json()
assert data["code"] == 0 # 业务成功码
assert data["data"]["username"] == "test_user_001"
assert data["data"]["user_id"] is not None
assert len(data["data"]["token"]) > 0 # 确保生成了token
你看,这才叫「测对了」。每个关键字段都验证了,任何一个环节出问题,测试都会红。
为什么我们会写出宽泛断言?
原因其实挺简单的:
- 懒——写详细的断言确实要多花几分钟
- 急——项目排期紧,先跑通再说
- 不懂业务——不知道哪些字段是关键字段
- 过度信任代码——觉得「应该不会出问题吧」
但你想过没有?一个宽泛的断言,等于没断言。它给你一种「测试通过了」的错觉,实际上漏洞百出。
如何写出精准的断言?
我总结了几个原则,你可以参考:
- 每个关键字段都要断言——不是所有字段,但业务核心字段必须覆盖
- 断言值的正确性,而非存在性——别只检查字段有没有,要检查值对不对
- 考虑边界和异常——正常情况测了,异常情况呢?空值、超长值、特殊字符呢?
- 使用数据驱动——同一个接口,多组数据,多组断言
举个例子,测试一个查询商品列表的接口:
def test_product_list():
response = client.get("/products?page=1&size=10")
assert response.status_code == 200
data = response.json()
assert data["code"] == 0
assert len(data["data"]["items"]) <= 10 # 分页大小正确
assert data["data"]["total"] >= 0 # 总数非负
# 检查第一个商品的关键字段
if data["data"]["items"]:
item = data["data"]["items"][0]
assert "id" in item
assert "name" in item
assert "price" in item
assert isinstance(item["price"], (int, float))
assert item["price"] > 0 # 价格必须大于0
核心原则:断言要精确到「如果业务逻辑错了,测试一定能发现」的程度。而不是「只要没报错就算过」。
避坑指南
我曾经接手过一个项目,里面有2000多个测试用例,跑起来全绿。结果一上线,核心功能直接崩了。
为什么?因为大部分断言都是 assert response.status_code == 200。接口返回了200,但返回的是个空壳数据。
那次之后,我定了个规矩:每个接口测试,至少要有3个以上的具体断言。少了就驳回。
一个小技巧:写断言的时候,可以问自己一个问题——「如果这个断言删掉,测试还能发现这个bug吗?」
如果能,说明这个断言不够。如果不能,说明这个断言是必要的。
用一张图总结
下面这张图,展示了断言从宽泛到精准的演进路径:
从这张图可以看得很清楚:宽泛断言虽然维护成本低,但发现bug的能力也低。精准断言虽然要多写几行代码,但它是真正能拦住问题的防线。
最后说两句
写测试不是为了「跑绿」,而是为了「发现问题」。一个永远绿的测试,如果什么都发现不了,那它就是个摆设。
我个人习惯,每次写完测试,都会故意改错一行代码,看看测试能不能发现。如果发现不了,说明断言还不够精准。
嗯,这个习惯帮我拦下了不少线上事故。你也可以试试。
公众号:蓝海资料掘金营,微信deep3321