• pytest:如何在测试中编写和报告断言


    断言assert声明

    pytest允许我们使用python的标准断言来测试期望值和预期值。例如:

    def dunc():
        return 3
    
    
    def test_dunc():
        assert dunc() != 3, "value was odd, should be even"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    断言dunc函数返回的某个值。如果此断言失败,你就看下函数调用的返回值:

    ============================= test session starts =============================
    collecting ... collected 1 item
    
    assert_test.py::test_dunc FAILED                                         [100%]
    assert_test.py:12 (test_dunc)
    3 != 3
    
    Expected :3
    Actual   :3
    
    
    def test_dunc():
    >       assert dunc() != 3
    E       assert 3 != 3
    E        +  where 3 = dunc()
    
    assert_test.py:14: AssertionError
    
    
    ============================== 1 failed in 0.08s ==============================
    
    Process finished with exit code 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    如果我们使用这样的断言指定消息:

    def dunc():
        return 3
    
    
    def test_dunc():
        value = 4
        assert dunc() == value, f"value was {dunc()}, should be {value}"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这时候的结果返回的就是我们指定的信息。就像下面这样:

    ============================= test session starts =============================
    collecting ... collected 1 item
    
    assert_test.py::test_dunc FAILED                                         [100%]
    assert_test.py:12 (test_dunc)
    3 != 4
    
    Expected :4
    Actual   :3
    
    
    def test_dunc():
            value = 4
    >       assert dunc() == value, f"value was {dunc()}, should be {value}"
    E       AssertionError: value was 3, should be 4
    E       assert 3 == 4
    E        +  where 3 = dunc()
    
    assert_test.py:15: AssertionError
    
    
    ============================== 1 failed in 0.08s ==============================
    
    Process finished with exit code 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    关于预期异常的断言

    def test_exception_assertion():
        assert 1 == 2
    
    
    ============================= test session starts =============================
    collecting ... collected 1 item
    
    exception_assertion_test.py::test_exception_assertion FAILED             [100%]
    exception_assertion_test.py:11 (test_exception_assertion)
    1 != 2
    
    Expected :2
    Actual   :1
    
    
    def test_exception_assertion():
    >           assert 1 == 2
    E           assert 1 == 2
    
    exception_assertion_test.py:13: AssertionError
    
    
    ============================== 1 failed in 0.10s ==============================
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    上述这个例子我们断言1==2;但是实际是 1 != 2 所以会抛出断言异常,对于预期会抛出的异常,我们可以使用pytest.raises()帮助我们解决预期引发的异常断言。就像下面这样:

    import pytest
    
    
    def test_exception_assertion():
        with pytest.raises(AssertionError):
            assert 1 == 2
    
    
    
    ============================= test session starts =============================
    collecting ... collected 1 item
    
    exception_assertion_test.py::test_exception_assertion PASSED             [100%]
    
    ============================== 1 passed in 0.01s ==============================
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    因为我们做了已知异常的捕获,所以再运行就不会再抛出异常了。如果您需要访问可以使用的实际异常信息:

    import pytest
    
    
    def func():
        func()
    
    
    def test_raises():
        with pytest.raises(RuntimeError) as excinfo:
            func()
    
        print(excinfo.type)
        print(excinfo.value)
        print(excinfo.traceback)
    
        assert "maximum recursion" in str(excinfo.value)
    
    
    ============================= test session starts ==============================
    collecting ... collected 1 item
    
    raises_test.py::test_raises PASSED                                       [100%]
    
    maximum recursion depth exceeded
    [, ...]
    ============================== 1 passed in 0.05s ===============================
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    excinfo是一个异常捕获的实例,它是引发的实际异常的包装器。主要属性 .type、.value和 .traceback 。.type是获取异常的类,.value是获取异常类抛出的message信息,.traceback是获取错误的回溯信息。

    您也可以利用正则表达式匹配异常的字符串表示形式,传递给match关键字参数,然后match传递给上下文管理器:

    def func():
        raise ValueError("ValueError message is error !")
    
    
    def test_raises_match():
        with pytest.raises(ValueError, match=r".* message.*"):
            func()
    
    
    ============================= test session starts ==============================
    collecting ... collected 1 item
    
    raises_test.py::test_raises_match PASSED                                 [100%]
    
    ============================== 1 passed in 0.01s ===============================
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    正表达式中的.是匹配除换行符(\n、\r)之外的任何单个字符,* 是匹配前面的子表达式零次或多次。因为func抛出的错误中包含message字符,正则刚好也匹配到异常信息,属于我们预期捕获的异常,所以运行成功。

    也可以为 指定一个 “ raises ” 参数 pytest.mark.xfail,它以一种更具体的方式检查测试是否失败,而不仅仅是引发任何异常:

    import pytest
    
    
    def func():
        raise ValueError("ValueError message is error !")
    
    
    @pytest.mark.xfail(raises=ValueError, reason="值错误!")
    def test_raises_match():
        func()
    
    
    ============================= test session starts ==============================
    collecting ... collected 1 item
    
    raises_test.py::test_raises_match XFAIL (值错误!)                       [100%]
    @pytest.mark.xfail(raises=ValueError, reason="值错误!")
        def test_raises_match():
    >       func()
    
    raises_test.py:28: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
        def func():
    >       raise ValueError("ValueError message is error !")
    E       ValueError: ValueError message is error !
    
    raises_test.py:23: ValueError
    
    
    ============================== 1 xfailed in 0.11s ==============================
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    pytest.raises() 和 @pytest.mark.xfail 的比较:

    pytest.raises() 对于您正在测试自己的代码故意引发的异常的情况使用可能会更好。

    @pytest.mark.xfail 检查功能,更适合记录未修复的错误(测试描述“应该”发生什么)或依赖项中的错误。

    为失败的断言定义自定义的解释

    首先,我们要先写一个断言类。就像下面这样:

    class AssertTest:
        def __init__(self, val):
            self.val = val
    
        def __eq__(self, other):
            return self.val == other.val
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    利用__eq__方法来进行判断,一个是self,另一个是other,也就是用自身的属性和other对象的属性分别进行比较

    断言类写好以后,我们要在用例文件夹中新建一个conftest.py文件,该文件主要是定义一些前置后置夹具,以及钩子函数的二次扩充编写。现在我们在文件中重写钩子来实现断言,就像下面这样:

    文档提供的钩子函数源码是这样的:

    def  pytest_assertrepr_compare ( 
        config :  "Config" ,  op :  str ,  left :  object ,  right :  object 
    )  ->  Optional [ List [ str ]]: 
        """返回失败断言表达式中比较的解释。
    
        返回 None 表示没有自定义解释,否则返回字符串列表
        。字符串将由换行符连接,但任何换行符
        *in* a string 都将被转义。请注意,除了第一行之外的所有行都会
        略微缩进,目的是让第一行作为摘要。
    
        :param pytest.Config config:pytest 配置对象。
        """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    conftest.py文件中重新定义钩子函数:

    from common.assertrepr import AssertTest
    
    
    def pytest_assertrepr_compare(op, left, right):
        if isinstance(left, AssertTest) and isinstance(right, AssertTest) and op == "==":
            return [
                "比较 AssertTest 的实例:",
                "   vals: {} != {}".format(left.val, right.val),
            ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    isinstance是判断传的类型是否和预期的类型一致,如果都不满足if条件,则抛出return 。left 表示操作符左侧被比较的对象,right 表示操作符右侧被比较的对象,op 表示断言的操作符。

    上述自定义的解释断言编写好以后,开始编写测试用例。就像这样:

    from common.assertrepr import AssertTest
    
    
    def test_assertrepr_compare():
        a = AssertTest(1)
        b = AssertTest(2)
        assert a == b
    
    
    ============================= test session starts =============================
    collecting ... collected 1 item
    
    assertrepr_compare_test.py::test_assertrepr_compare FAILED               [100%]
    
    assertrepr_compare_test.py:11 (test_assertrepr_compare)
     != 
    
    Expected :
    Actual   :
    
    
    def test_assertrepr_compare():
            a = AssertTest(1)
            b = AssertTest(2)
    >       assert a == b
    E       assert 比较 AssertTest 的实例:
    E            vals: 1 != 2
    
    assertrepr_compare_test.py:15: AssertionError
    
    
    ============================== 1 failed in 0.08s ==============================
    
    Process finished with exit code 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    今天先聊到这里吧,以上总结或许能帮助到你,或许帮助不到你,但还是希望能帮助到你

    最后: 可以在公众号:伤心的辣条 ! 自行领取一份216页软件测试工程师面试宝典文档资料【免费的】。以及相对应的视频学习教程免费分享!,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。

    现在我邀请你进入我们的软件测试学习交流群:746506216】,备注“入群”, 大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路。

    喜欢软件测试的小伙伴们,如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一 键三连哦!

    软件测试工程师自学教程:

    这才是2022最精细的自动化测试自学教程,我把它刷了无数遍才上岸字节跳动,做到涨薪20K【值得自学软件测试的人刷】

    接口性能测试 — 软件测试人必会618实战场景分析

    软件测试工程师月薪2W以上薪资必学技能 — Python接口自动化框架封装.

    美团面试真题_高级测试25K岗位面试 — 软件测试人都应该看看

    测试开发之全面剖析自动化测试平台 — 软件测试人的必经之路

    软件测试必会_Jmeter大厂实战 — 仅6步可实现接口自动化测试

    Jmeter实战讲解案例 — 软件测试人必会

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    开咖啡店需要注意什么?知名咖啡店总结五点
    虹科技术 | 重磅更新!手持式PCAN-Diag FD现可扩展为J1939监控器
    并网逆变器学习笔记2---微网逆变器
    看完这篇原型设计模式,还不会,请你吃瓜
    【四】相机标定
    Intersection Observer API探索
    [异构图-论文阅读]Heterogeneous Graph Transformer
    【前端】HTML入门 —— HTML的常见标签
    Mybatis-plus的操作(新增,修改,删除)
    Django模型的使用
  • 原文地址:https://blog.csdn.net/m0_67695717/article/details/126247766