Pytest如何重写断言assert语句的报错信息

【原文链接】Pytest如何重写断言assert语句的报错信息

一、默认的报错信息

首先观察如下测试代码,这里面有两个测试用例,一个是判断1是否等于2,第二个是判断字符串“hello”是否等于整数10.

def test_demo01():
    assert 1==2

def test_demo02():
    assert "hello" == 10

执行结果如下

$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\redrose2100-book\ebooks\Pytest企业级应用实战\src
collected 2 items

test_demo.py FF                                                                                                                                                   [100%]

=============================================================================== FAILURES =============================================================================== 

test_demo.py:2: AssertionError
_____________________________________________________________________________ test_demo02 ______________________________________________________________________________ 

    def test_demo02():
>       assert "hello" == 10
E       AssertionError: assert 'hello' == 10

test_demo.py:5: AssertionError
======================================================================= short test summary info ======================================================================== 
FAILED test_demo.py::test_demo01 - assert 1 == 2
FAILED test_demo.py::test_demo02 - AssertionError: assert 'hello' == 10
========================================================================== 2 failed in 2.24s ===========================================================================

对于许多测试人员,可能习惯于看中文描述或者更加准确的中文描述,而当看到上述报错时,虽说从报错描述来说已经很清晰了,但是从多年的自动化测试经历中发现,其实很多测试人员或者编写自动化测试脚本的测试人员,调试或者执行自动化用例的时候看到上述报错的时候仍然是一脸茫然,他们更加的希望看到中文描述,甚至是更加人性化的中文描述,当然pytest的断言也提供了在脚本中自定义报错信息的方式,比如如下的方式。

def test_demo01():
    assert 1==2,"期望值与实际值不一致,期望值为2,实际值为1"

但是在实际测试开发中发现,虽然这种方式给自动化脚本开发人员自己定义的权利,但是在进行脚本开发的过程中如果每个断言都按照这种方式增加断言报错信息,又很占用时间,感觉不方便,甚至有很多断言报错信息其实都是类似的,比如判断两个变量是否相等,其实断言信息都是类似的,而在脚本中每次断言都加断言报错信息又会显得很冗余,因此,另外一种在conftest.py中重写断言报错信息的处理方式就显得非常好用了。

二、重写判断是否相等的断言的报错信息

这里先需要使用一个conftest.py文件,这个文件的详细的作用以及使用在后面继续展开,这里仅仅需要知道当conftest.py文件和测试用例文件放在同一个目录时,在执行测试用例文件之前会自动先加载执行conftest.py,因此本小节正是利用这一点,在conftest.py文件中重写pytest_assertrepr_compare方法。
conftest.py文件中代码如下,即在进行判断两个变量是否相等时,这里首先判断一下这两个变量是否为同一类型,如果不是同一类型,直接报错,并在报错中提示类型错误,这种情况比较常见的是比如在判断两个变量是否相等时,期望值为数字1,而实际值为字符串1,当打印调试的时候,会发现完全一样,但是结果就是报错,这种情况很是困扰人,因此可以在pytest_assertrepr_compare直接重写并判断,当类型一致时,在进行是否相等的判断,如果不相等,则采用中文描述详细的描述清楚。

def pytest_assertrepr_compare(op, left, right):
    if op == "==":
        if not isinstance(right,type(left)):
            return [
                f"断言错误,期望值与实际值的数据类型不一致,期望值数据类型为:{type(right)}, 实际值为:{type(left)}",
                f"期望值为:{right}, 实际值为:{left}",
            ]
        else:
            return [
                f"断言错误,期望值与实际值不一致,期望值为:{right}, 实际值为:{left}",
            ]

然后在同一目录下创建test_demo.py文件,测试脚本如下:

def test_demo01():
    assert 1==2

def test_demo02():
    assert "10" == 10

执行结果如下,可以看到此时错误描述非常简单清晰明了,一目了然。

$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0 
rootdir: D:\redrose2100-book\ebooks\Pytest企业级应用实战\src
collected 2 items

test_demo.py FF                                                                                                                                                   [100%]

=============================================================================== FAILURES =============================================================================== 
_____________________________________________________________________________ test_demo01 ______________________________________________________________________________ 

    def test_demo01():
>       assert 1==2
E       assert 断言错误,期望值与实际值不一致,期望值为:2, 实际值为:1

test_demo.py:2: AssertionError
_____________________________________________________________________________ test_demo02 ______________________________________________________________________________ 

    def test_demo02():
>       assert "10" == 10
E       AssertionError: assert 断言错误,期望值与实际值的数据类型不一致,期望值数据类型为:<class 'int'>, 实际值为:<class 'str'>
E         期望值为:10, 实际值为:10

test_demo.py:5: AssertionError
======================================================================= short test summary info ======================================================================== 
FAILED test_demo.py::test_demo01 - assert 断言错误,期望值与实际值不一致,期望值为:2, 实际值为:1
FAILED test_demo.py::test_demo02 - AssertionError: assert 断言错误,期望值与实际值的数据类型不一致,期望值数据类型为:<class 'int'>, 实际值为:<class 'str'>
========================================================================== 2 failed in 0.15s ===========================================================================

像这种细节的处理,就能显示出自动化测试的专业性了,一个资深的自动化测试专家设计出的自动化测试框架,很多时候都是体现在这些细节上。

三、重写常见的判断逻辑报错信息

如下,这里重写了 ==,in,not in 的断言报错信息,其他的比如 >, <,>=,<=,!= 等运算符重写方法类似,在企业级实战中,在设计自动化测试框架的时候,需要尽量重写全面,这里不在一一列举,有兴趣的可以自己尝试将这些运算符补全,conftest.py中编写如下代码

def pytest_assertrepr_compare(op, left, right):
    if op == "==":
        if not isinstance(right,type(left)):
            return [
                f"断言错误,期望值与实际值的数据类型不一致,期望值数据类型为:{type(right)}, 实际值为:{type(left)}",
                f"期望值为:{right}, 实际值为:{left}",
            ]
        else:
            return [
                f"断言错误,期望值与实际值不一致,期望值为:{right}, 实际值为:{left}",
            ]
    if op == "in":
        if isinstance(left,str) and isinstance(right,str):
            return [
                f"期望 {left}{right} 的子串,实际 {left} 不是 {right} 的子串,"
            ]
        elif isinstance(right,list) or isinstance(right,set) or isinstance(right,tuple):
            return [
                f"期望 {left} 是集合 {right} 中的一个元素,实际集合 {right} 中没有 {left} 元素"
            ]
        elif isinstance(right,dict):
            return [
                f"期望 {left} 是字典 {right} 中的一个key,实际字典 {right} 中没有值为 {left} 的key"
            ]
        else:
            return [
                f"期望 {left}{right} 中的一部分,实际上 {left} 并不是 {right} 的一部分"
            ]
    if op == "not in":
        if isinstance(left, str) and isinstance(right, str):
            return [
                f"期望 {left} 不是 {right} 的子串,实际 {left}{right} 的子串,"
            ]
        elif isinstance(right, list) or isinstance(right, set) or isinstance(right, tuple):
            return [
                f"期望 {left} 不是集合 {right} 中的一个元素,实际集合 {right} 中有 {left} 元素"
            ]
        elif isinstance(right, dict):
            return [
                f"期望 {left} 不是字典 {right} 中的一个key,实际字典{right}中有值为 {left} 的key"
            ]
        else:
            return [
                f"期望 {left} 不是 {right} 中的一部分,实际上 {left}{right} 的一部分"
            ]

下面在同级目录下编写了几个测试用例,用于演示上述重写的效果,具体代码如下

def test_01():
    assert 1==1
def test_02():
    assert 1==2
def test_03():
    assert "1"==1
def test_04():
    assert "aa" in "bbaa"
def test_05():
    assert "aa" in "bba"
def test_06():
    assert "aa" in ["aa","bb"]
def test_07():
    assert "aa" in ("aa","bb")
def test_08():
    assert "aa" in {"aa","bb"}
def test_09():
    assert "ab" in ["aa", "bb"]
def test_10():
    assert "ab" in ("aa", "bb")
def test_11():
    assert "ab" in {"aa", "bb"}
def test_12():
    assert "name" in {"name":"张三丰","age":100}
def test_13():
    assert "gender" in {"name":"张三丰","age":100}
def test_14():
    assert "aa" not in "bbaa"
def test_15():
    assert "aa" not in "bba"
def test_16():
    assert "aa" not in ["aa","bb"]
def test_17():
    assert "aa" not in ("aa","bb")
def test_18():
    assert "aa" not in {"aa","bb"}
def test_19():
    assert "ab" not in ["aa", "bb"]
def test_20():
    assert "ab" not in ("aa", "bb")
def test_21():
    assert "ab" not in {"aa", "bb"}
def test_22():
    assert "name" not in {"name":"张三丰","age":100}
def test_23():
    assert "gender" not in {"name":"张三丰","age":100}

执行结果如下,可以看出,经过这么重写之后,断言报错信息基本就能一眼就看出是什么问题,当然这里只是举了个例子,在设计自动化测试框架的时候,完全根据具体业务进行自定义,力求做到让团队或项目成员能最快最简单最容易的定位出错误。

$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\redrose2100-book\ebooks\Pytest企业级应用实战\src
collected 23 items

test_demo.py .FF.F...FFF.FF.FFF...F.                                                                                                                              [100%]

=============================================================================== FAILURES =============================================================================== 
_______________________________________________________________________________ test_02 ________________________________________________________________________________ 

    def test_02():
>       assert 1==2
E       assert 断言错误,期望值与实际值不一致,期望值为:2, 实际值为:1

test_demo.py:4: AssertionError
_______________________________________________________________________________ test_03 ________________________________________________________________________________ 

    def test_03():
>       assert "1"==1
E       AssertionError: assert 断言错误,期望值与实际值的数据类型不一致,期望值数据类型为:<class 'int'>, 实际值为:<class 'str'>
E         期望值为:1, 实际值为:1

test_demo.py:6: AssertionError
_______________________________________________________________________________ test_05 ________________________________________________________________________________ 

    def test_05():
>       assert "aa" in "bba"
E       assert 期望 aa 是 bba 的子串,实际 aa 不是 bba 的子串,

test_demo.py:10: AssertionError
_______________________________________________________________________________ test_09 ________________________________________________________________________________ 

    def test_09():
>       assert "ab" in ["aa", "bb"]
E       AssertionError: assert 期望 ab 是集合 ['aa', 'bb'] 中的一个元素,实际集合 ['aa', 'bb'] 中没有 ab 元素

test_demo.py:18: AssertionError
_______________________________________________________________________________ test_10 ________________________________________________________________________________ 

    def test_10():
>       assert "ab" in ("aa", "bb")
E       AssertionError: assert 期望 ab 是集合 ('aa', 'bb') 中的一个元素,实际集合 ('aa', 'bb') 中没有 ab 元素

test_demo.py:20: AssertionError
_______________________________________________________________________________ test_11 ________________________________________________________________________________ 

    def test_11():
>       assert "ab" in {"aa", "bb"}
E       AssertionError: assert 期望 ab 是集合 {'bb', 'aa'} 中的一个元素,实际集合 {'bb', 'aa'} 中没有 ab 元素

test_demo.py:22: AssertionError
_______________________________________________________________________________ test_13 ________________________________________________________________________________ 

    def test_13():
>       assert "gender" in {"name":"张三丰","age":100}
E       AssertionError: assert 期望 gender 是字典 {'name': '张三丰', 'age': 100} 中的一个key,实际字典 {'name': '张三丰', 'age': 100} 中没有值为 gender 的key

test_demo.py:26: AssertionError
_______________________________________________________________________________ test_14 ________________________________________________________________________________ 

    def test_14():
>       assert "aa" not in "bbaa"
E       assert 期望 aa 不是 bbaa 的子串,实际 aa 是 bbaa 的子串,

test_demo.py:28: AssertionError
_______________________________________________________________________________ test_16 ________________________________________________________________________________ 

    def test_16():
>       assert "aa" not in ["aa","bb"]
E       AssertionError: assert 期望 aa 不是集合 ['aa', 'bb'] 中的一个元素,实际集合 ['aa', 'bb'] 中有 aa 元素

test_demo.py:32: AssertionError
_______________________________________________________________________________ test_17 ________________________________________________________________________________ 

    def test_17():
>       assert "aa" not in ("aa","bb")
E       AssertionError: assert 期望 aa 不是集合 ('aa', 'bb') 中的一个元素,实际集合 ('aa', 'bb') 中有 aa 元素

test_demo.py:34: AssertionError
_______________________________________________________________________________ test_18 ________________________________________________________________________________ 

    def test_18():
>       assert "aa" not in {"aa","bb"}
E       AssertionError: assert 期望 aa 不是集合 {'bb', 'aa'} 中的一个元素,实际集合 {'bb', 'aa'} 中有 aa 元素

test_demo.py:36: AssertionError
_______________________________________________________________________________ test_22 ________________________________________________________________________________ 

    def test_22():
>       assert "name" not in {"name":"张三丰","age":100}
E       AssertionError: assert 期望 name 不是字典 {'name': '张三丰', 'age': 100} 中的一个key,实际字典{'name': '张三丰', 'age': 100}中有值为 name 的key

test_demo.py:44: AssertionError
======================================================================= short test summary info ======================================================================== 
FAILED test_demo.py::test_02 - assert 断言错误,期望值与实际值不一致,期望值为:2, 实际值为:1
FAILED test_demo.py::test_03 - AssertionError: assert 断言错误,期望值与实际值的数据类型不一致,期望值数据类型为:<class 'int'>, 实际值为:<class 'str'>
FAILED test_demo.py::test_05 - assert 期望 aa 是 bba 的子串,实际 aa 不是 bba 的子串,
FAILED test_demo.py::test_09 - AssertionError: assert 期望 ab 是集合 ['aa', 'bb'] 中的一个元素,实际集合 ['aa', 'bb'] 中没有 ab 元素
FAILED test_demo.py::test_10 - AssertionError: assert 期望 ab 是集合 ('aa', 'bb') 中的一个元素,实际集合 ('aa', 'bb') 中没有 ab 元素
FAILED test_demo.py::test_11 - AssertionError: assert 期望 ab 是集合 {'bb', 'aa'} 中的一个元素,实际集合 {'bb', 'aa'} 中没有 ab 元素
FAILED test_demo.py::test_13 - AssertionError: assert 期望 gender 是字典 {'name': '张三丰', 'age': 100} 中的一个key,实际字典 {'name': '张三丰', 'age': 100} 中没有值... 
FAILED test_demo.py::test_14 - assert 期望 aa 不是 bbaa 的子串,实际 aa 是 bbaa 的子串,
FAILED test_demo.py::test_16 - AssertionError: assert 期望 aa 不是集合 ['aa', 'bb'] 中的一个元素,实际集合 ['aa', 'bb'] 中有 aa 元素
FAILED test_demo.py::test_17 - AssertionError: assert 期望 aa 不是集合 ('aa', 'bb') 中的一个元素,实际集合 ('aa', 'bb') 中有 aa 元素
FAILED test_demo.py::test_18 - AssertionError: assert 期望 aa 不是集合 {'bb', 'aa'} 中的一个元素,实际集合 {'bb', 'aa'} 中有 aa 元素
FAILED test_demo.py::test_22 - AssertionError: assert 期望 name 不是字典 {'name': '张三丰', 'age': 100} 中的一个key,实际字典{'name': '张三丰', 'age': 100}中有值为 na...
==================================================================== 12 failed, 11 passed in 0.23s =====================================================================
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

redrose2100

您的鼓励是我最大的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值