一、背景
日常测试工作中,对于测试来讲,无论是功能测试,自动化测试还是单元测试,一般都会预设一个期望的结果,而在测试执行过程中会得到一个实际结果。测试的通过与否就是拿实际的结果与预期的结果进行比较,这个比较的过程就是断言。
二、关于pytest断言
对于自动化测试,常常会使用pytest框架,而pytest框架中有assert和assume两种断言方式,两者的区别在于:
assert:出现断言失败,则后续断言不再执行
assume:出现断言失败的情况,会继续执行该断言后续的语句及断言
2.1 工作原理
2.1.1 assert工作原理
assert 是 Python 的内置关键字,用于检查某个条件是否为 True,如果条件不成立,程序会抛出 AssertionError 异常,并且默认会终止当前测试的执行。pytest 会捕捉这些错误并输出详细的失败信息。
assert 被执行时,Python 会做如下操作:
- 评估表达式: assert 会首先计算并评估其后面的表达式。例如,assert x > 0 会判断 x 是否大于 0。
- 判断结果: 如果结果为 True,则 assert 不会引发任何异常,程序继续执行。
- 抛出异常: 如果结果为 False,assert 会引发 AssertionError 异常。如果有提供 message,该信息会作为异常的详细描述。例如,assert x > 0, "x must be greater than 0" 在 x <= 0 时会抛出 AssertionError: x must be greater than 0。
2.1.2 assume工作原理
assume 是 pytest 插件 pytest-assume 中的一个功能,该函数的工作方式与标准的 assert 类似,但与之不同的是如果某个断言失败,不会在断言失败时立即终止测试。相反,它会记录失败的断言,并继续执行后续代码。最终测试会报告所有断言的结果。
assume被执行时,Python 会做如下操作:
- 评估表达式: assume会首先计算并评估其后面的表达式。例如,assume(x > 0)会判断x是否大于 0。
- 判断结果: 如果表达式的结果为 True,assume 不会做任何事情,程序继续执行;
- 抛出异常: 如果表达式的结果为 False,assume 会记录这个失败的断言,但不会停止测试,而是继续执行后面的代码。最终,pytest 会收集所有失败的断言并报告。
2.2 关于assert与assume断言流程图
2.3 assert 和 assume 的对比总结 :
| 特性 | assert | assume |
|---|---|---|
| 断言失败时行为 | 立即中止测试,后续断言不执行 | 继续执行测试,所有断言都将执行 |
| 测试报告 | 清晰、即时的失败信息 | 所有失败的信息集中在测试结束后报告 |
| 插件依赖 | 无,Python 内建支持 | 需要安装 pytest-assume 插件 |
| 使用场景 | 确保某个条件必须成立,并立即中止测试 | 需要多个断言且希望继续执行测试 |
| 调试效率 | 高,失败后立刻报告 | 较低,失败会推迟报告,需要仔细查看 |
三、以 assert 断言为例,详解断言在实际项目中的应用
3.1 assert 断言方式
3.2 assert 在实际业务场景中的应用
3.2.1 单元素断言
若实际业务场景中,期望值与实际值均为单元素时,则可以用assert a == b方式进行断言,对实际值进行精准校验,例如返回体中code值、message值或各类自定义精确数据均可用该方式进行断言
code = 200
def test_addition():
"单元素断言"
assert 200 == code
print("正确")
3.2.2 单元素与多元素包含关系断言
若实际业务场景中,期望值为单元素与实际值为多元素时,则可以用assert a in b方式进行断言,对实际值进行包含关系校验,例如列表返回值中id 的包含关系可用该方式进行断言
expect_value = 1
actual_value_list = [1,2,3,4,5,6]
def test_addition():
"单元素与多元素包含关系断言"
assert expect_value in actual_value_list
print("正确")
3.2.3 多元素与多元素断言
若实际业务场景中,期望值与实际值均为多元素时,则结合实际情况,对期望值与实际值进行相等或包含关系的断言,例如期望值的idList与实际值的idList一致则可用该方式进行断言
expect_value = [1,2,3,4]
actual_value_list = [1,2,3,4]
def test_addition():
"多元素间关系断言"
assert expect_value == actual_value_list
print("正确")
3.2.4 断言浮动值相等
若实际业务场景中,对于浮动值(浮点数),你可以通过指定误差范围来进行断言,使用 pytest.approx兼容误差。
actual_value = 0.18954
result_value = 0.18955
def test_addition():
"浮点兼容误差断言"
assert result_value == pytest.approx(actual_value, rel=0.0001) # 使用误差范围进行浮动值比较
print("正确")
3.2.5 类型差异断言
若实际业务场景中,因期望值与实际值因类型差异导致的断言不通过时,可根据实际情况处理相关类型字段使断言通过。例如当实际值为一个为字符串类型,期望值为Int类型时,若想断言校验通过,则需要将两个字符类型转为一致,才可以进行正常断言。
actual_value = 1.0
result_value = "1"
def test_addition():
"类型不一致断言"
assert int(result_value)==int(actual_value)
print("正确")
3.2.6 字典空字符串和空值断言
若实际业务场景中,因期望值与实际值因空值和空字符不一致导致字典校验不通过时,可单独处理相关值,使空值和空字符统一,最后用等价断言使断言通过。
def test_addition():
"兼容空值和空字符串的断言"
actual_value = {"a": None, "b": ""}
result_value = {"a": "", "b": None}
actual_value = {key: None if value == "" else value for key, value in actual_value.items()}
result_value = {key: None if value == "" else value for key, value in result_value.items()}
assert result_value == actual_value
print("正确")
3.2.7 两个字典间存在包含关系断言
若实际业务场景中,因期望值与实际值均为字典且存在包含关系时,若只想比对键值对相同的数据,可使通过一个表达式来遍历 actual_value 中的每一个键值对,检查键是否在 result_value 中,并且对应的值是否相等。如果所有的键值对都满足这个条件,那么 all 函数就会返回 True,最后用等价断言使断言通过。
def test_addition():
"两个字典间key存在差异,但相同key的value一致的断言校验"
actual_value = {"a": 1, "b": 2}
result_value = {"a": 1, "b": 2, "c": 3}
all_present = all(key in result_value and result_value[key] == value for key, value in actual_value.items())
assert all_present
print("正确")
3.2.8 两个列表中含单元素的断言
若实际业务场景中,因期望值与实际值均为列表且列表中嵌套为单个元素时,若两个列表因顺序不一致导致断言失败时,可以通过处理列表顺序使列表断言一致。
def test_addition():
"两个列表存在顺序差异"
actual_value = [1,2,3,4]
result_value = [3,4,2,1]
assert sorted(actual_value)==sorted(result_value)
print("正确")
3.2.9 两个列表中嵌套字典的断言
若实际业务场景中,因期望值与实际值均为列表且列表中嵌套为字典时,若两个列表因字典顺序不一致导致断言失败时,可以通过处理列表字典顺序,将列表按字典中的某个key的value值进行排序,使列表断言一致。
def test_addition():
"两个列表的字典存在顺序差异"
actual_value = [{"a":"abc","b":123,"c":True},{"a":"bcd","b":332,"c":False}]
result_value = [{"a":"bcd","b":332,"c":False},{"a":"abc","b":123,"c":True}]
actual_value = sorted(actual_value, key=lambda x: x['a'], reverse=True)
result_value = sorted(result_value, key=lambda x: x['a'], reverse=True)
assert (actual_value)==(result_value)
print("正确")
3.2.10 兼容两个日期时间存在1s误差的断言
若实际业务场景中,因校验编辑时间时可能存在获取当前时间和入库时间存在1s的时间误差,此时要通过转换断言的比较,来兼容该项断言情况。
def test_addition():
*"两个日期字符串的时间,误差为1s"*
**actual_value = "2024-11-26 18:00:00"
result_value = "2024-11-26 18:00:01"
result_value = datetime.datetime.strptime(result_value, "%Y-%m-%d %H:%M:%S")
actual_value = datetime.datetime.strptime(actual_value, "%Y-%m-%d %H:%M:%S")
diff = result_value - actual_value
diff_ = abs(diff.total_seconds())
assert diff_ <= 1.0
print("正确")
3.2.11 自定义断言消息
若实际业务场景中,需要打印自定义断言消息,使得测试失败时输出更具可读性的信息
def test_addition():
x = 5
y = 10
assert x == y, f"Expected {x} to be equal to {y}, but got {x}." # 自定义错误消息
3.2.12 组合断言条件
若实际业务场景中,需要支持复合条件的断言,例如多个条件需要同时满足才会执行通过的情况。
def test_addition():
x = 5
y = 10
assert x < y and y == 10 # 断言两个条件都成立
print("正确")
3.2.13 断言 迭代器、生成器
若实际业务场景中,需要验证生成器、迭代器的输出,可以用assert来对比输出的结果
def test_addition():
def my_gen():
yield 1
yield 2
yield 3
gen = my_gen()
assert list(gen) == [1, 2, 3] # 断言生成器的输出是 [1, 2, 3]
3.2.14 利用 assert 调试
assert 也可以作为调试工具来验证程序状态,例如中间结果,先验证中间结果是否正常,通过后再验证完整结果是否正常
def test_addition():
x = 2
y = 3
z = 3
data = x * y + z
assert x * y == 6, f"Expected x * y to be 6, but got {x * y}" # 中断测试,检查中间值
assert data == 8, f"完整结果" # 检查完整值
四、总结
回顾这一路对 Pytest 接口自动化断言的剖析,恰似绘制一幅精密测试蓝图。我们掌握的断言手段,如同给接口测试装上 “火眼金睛”,精准洞察每个细节。当下自动化测试浪潮汹涌,掌握好断言这个关键环节,只是乘势而上的起点。未来还有更多诸如优化断言效率、融合新兴技术提升测试效能的高峰待攀。如果大家有更优秀的断言经验,期待一起交流和分享。