接口测试中关于Pytest的断言

536 阅读9分钟

一、背景

日常测试工作中,对于测试来讲,无论是功能测试,自动化测试还是单元测试,一般都会预设一个期望的结果,而在测试执行过程中会得到一个实际结果。测试的通过与否就是拿实际的结果与预期的结果进行比较,这个比较的过程就是断言。

二、关于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断言流程图

未命名文件.png

2.3 assert 和 assume 的对比总结

特性assertassume
断言失败时行为立即中止测试,后续断言不执行继续执行测试,所有断言都将执行
测试报告清晰、即时的失败信息所有失败的信息集中在测试结束后报告
插件依赖无,Python 内建支持需要安装 pytest-assume 插件
使用场景确保某个条件必须成立,并立即中止测试需要多个断言且希望继续执行测试
调试效率高,失败后立刻报告较低,失败会推迟报告,需要仔细查看

 

三、 assert 断言为例,详解断言在实际项目中的应用

3.1 assert 断言方式

71425E9B-F24B-4FD9-AA86-8DC4E5E5ABFD.png

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 接口自动化断言的剖析,恰似绘制一幅精密测试蓝图。我们掌握的断言手段,如同给接口测试装上 “火眼金睛”,精准洞察每个细节。当下自动化测试浪潮汹涌,掌握好断言这个关键环节,只是乘势而上的起点。未来还有更多诸如优化断言效率、融合新兴技术提升测试效能的高峰待攀。如果大家有更优秀的断言经验,期待一起交流和分享。