• Pytest 框架执行用例流程浅谈


    背景:

      根据以下简单的代码示例,我们将从源码的角度分析其中的关键加载执行步骤,对pytest整体流程架构有个初步学习。

    代码示例:

    import pytest
     
    def test_add():
     
    assert 1 + 1 == 2
     
    def test_sub():
     
    assert 2 - 1 == 1

      通过 pytest test_example.py 运行此代码示例后,会触发pytest的入口函数main(),这个函数定义在src/pytest/__main__.py中,它的作用是创建一个PytestConfig对象,并调用其

    do_configure()和do_main()方法。PytestConfig对象是pytest的核心配置类,它负责解析命令行参数、读取配置文件、注册插件、创建Session对象等。PytestConfig对象定义在

    src/_pytest/config/__init__.py中,它继承了pluggy.HookimplMarker类,也就是说它可以作为一个插件管理器,调用各种hook函数。

    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
    35
    36
    ```python
    # src/pytest/__main__.py
     
    def main():
        # 创建PytestConfig对象
        config = PytestConfig()
        # 调用config.do_configure()方法
        config.do_configure()
        # 调用config.do_main()方法
        config.do_main()
    ```
     
    ```python
    # src/_pytest/config/__init__.py
     
    class PytestConfig(pluggy.HookimplMarker):
        def __init__(self):
            # 解析命令行参数
            self.parse_args()
            # 读取配置文件
            self.read_config_files()
            # 注册插件
            self.register_plugins()
            # 创建Session对象
            self.session = Session(self)
         
        def do_configure(self):
            # 调用hook函数pytest_configure
            self.hook.pytest_configure(config=self)
         
        def do_main(self):
            # 调用hook函数pytest_sessionstart
            self.hook.pytest_sessionstart(session=self.session)
            # 调用Session对象的main()方法
            self.session.main()
    ```
     

      Session对象是pytest的核心上下文类,它负责管理整个测试过程的信息,包括收集测试用例、执行测试用例、生成测试报告等。Session对象定义在src/_pytest/main.py中,它继承了

    Collector类,也就是说它可以作为一个测试用例收集器。Session对象的main()方法是执行测试用例的主要入口,它会调用perform_collect()方法来收集测试用例,并返回一个列表items;然后

    调用runtestloop()方法来循环执行items中的每个Item对象;最后调用hook函数pytest_sessionfinish来结束测试会话,并返回一个退出码exitstatus。

    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
    35
    36
    37
    38
    39
    40
    41
    ```python
    # src/_pytest/main.py
     
    class Session(Collector):
        def __init__(self, config):
            # 初始化一些属性和状态信息
         
        def main(self):
            # 调用perform_collect()方法收集测试用例,并返回items列表
            items = self.perform_collect()
            # 调用runtestloop()方法循环执行items中的每个Item对象,并返回退出码exitstatus
            exitstatus = self.runtestloop(items)
            # 调用hook函数pytest_sessionfinish来结束测试会话,并返回退出码exitstatus
            self.hook.pytest_sessionfinish(session=self, exitstatus=exitstatus)
            return exitstatus
         
        def perform_collect(self):
            # 调用hook函数pytest_collectstart表示开始收集测试用例
            self.hook.pytest_collectstart(collector=self)
            # 调用自身的collect()方法来递归遍历指定的测试文件或目录,并返回一个列表items
            items = self.collect()
            # 调用hook函数pytest_collectreport表示收集测试用例结束,并生成收集报告
            self.hook.pytest_collectreport(report=CollectReport(self, "passed", items))
            # 调用hook函数pytest_collection_modifyitems允许对收集到的Item对象进行修改
            self.hook.pytest_collection_modifyitems(session=self, config=self.config, items=items)
            # 调用hook函数pytest_deselected表示从收集到的Item对象中筛选出需要执行的Item对象
            self.hook.pytest_deselected(items=self.deselected)
            # 调用hook函数pytest_collection_finish表示收集和筛选测试用例完成,并返回最终要执行的Item对象列表
            self.hook.pytest_collection_finish(session=self)
            return items
         
        def runtestloop(self, items):
            # 调用hook函数pytest_runtestloop表示开始循环执行测试用例
            self.hook.pytest_runtestloop(session=self)
            # 遍历items列表,依次取出每个Item对象
            for item in items:
                # 调用Item对象的runtestprotocol()方法来执行单个测试用例的协议
                item.runtestprotocol()
            # 返回退出码0表示成功
            return 0
    ```

      

      Item对象是pytest的核心测试类,它负责封装和执行单个测试用例的信息,包括名称、位置、参数化信息、标记信息等。Item对象定义在src/_pytest/python.py中,它继承了Node类,也

    就是说它可以作为一个测试节点。Item对象的runtestprotocol()方法是执行单个测试用例的主要入口,它会调用hook函数pytest_runtest_logstart来开始记录日志信息;然后调用runtest()方法

    来执行测试用例的前置、主体和后置部分;最后调用hook函数pytest_runtest_logfinish来结束记录日志信息,并生成日志报告。

     

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    ```python
    # src/_pytest/python.py
     
    class Item(Node):
        def __init__(self, name, parent, config, session):
            # 初始化一些属性和状态信息
         
        def runtestprotocol(self):
            # 调用hook函数pytest_runtest_logstart表示开始记录日志信息
            self.ihook.pytest_runtest_logstart(nodeid=self.nodeid, location=self.location)
            # 调用runtest()方法来执行测试用例的前置、主体和后置部分,并返回一个列表reports
            reports = self.runtest()
            # 调用hook函数pytest_runtest_logfinish表示结束记录日志信息,并生成日志报告
            self.ihook.pytest_runtest_logfinish(nodeid=self.nodeid, location=self.location)
            return reports
         
        def runtest(self):
            # 创建一个空列表reports
            reports = []
            # 调用_setup()方法来执行测试用例的前置操作,并将返回的报告添加到reports列表中
            reports.append(self._setup())
            # 如果前置操作没有失败或跳过,则调用_call()方法来执行测试用例的主体部分,并将返回的报告添加到reports列表中
            if reports[-1].passed:
                reports.append(self._call())
            # 调用_teardown()方法来执行测试用例的后置操作,并将返回的报告添加到reports列表中
            reports.append(self._teardown())
            # 返回reports列表
            return reports
         
        def _setup(self):
            # 调用hook函数pytest_runtest_setup表示开始执行前置操作,并返回一个报告setup_report
            setup_report = self.ihook.pytest_runtest_setup(item=self)
            return setup_report
         
        def _call(self):
            # 调用hook函数pytest_runtest_call表示开始执行主体部分,并返回一个报告call_report
            call_report = self.ihook.pytest_runtest_call(item=self)
            return call_report
         
        def _teardown(self):
            # 调用hook函数pytest_runtest_teardown表示开始执行后置操作,并返回一个报告teardown_report
            teardown_report = self.ihook.pytest_runtest_teardown(item=self)
            return teardown_report
    ```

     

    总结:

    Pytest的加载流程大致如下:

    - Pytest首先会解析命令行参数,确定要执行的测试文件、测试目录、测试类、测试函数等,以及一些配置选项。
    - Pytest会根据配置文件(pytest.ini、setup.cfg、tox.ini等)和命令行参数,创建一个Config对象,用于存储配置信息。
    - Pytest会创建一个Session对象,用于管理整个测试过程的上下文信息,包括收集测试用例、执行测试用例、生成测试报告等。
    - Pytest会调用hook函数pytest_sessionstart,表示测试会话开始。
    - Pytest会调用hook函数pytest_collectstart,表示开始收集测试用例。
    - Pytest会根据Config对象中的信息,递归遍历指定的测试文件或目录,寻找符合pytest约定的测试用例(以test_开头的函数或方法,以Test开头的类等)。
    - Pytest会将找到的测试用例封装成Item对象,并添加到Session对象的items列表中。Item对象包含了测试用例的名称、位置、参数化信息、标记信息等。
    - Pytest会调用hook函数pytest_collectreport,表示收集测试用例结束,并生成收集报告。
    - Pytest会调用hook函数pytest_collection_modifyitems,允许对收集到的Item对象进行修改,例如重新排序、添加或删除标记等。
    - Pytest会调用hook函数pytest_deselected,表示从收集到的Item对象中筛选出需要执行的Item对象,并将不需要执行的Item对象放入Session对象的deselected列表中。
    - Pytest会调用hook函数pytest_collection_finish,表示收集和筛选测试用例完成,并返回最终要执行的Item对象列表。
    - Pytest会根据是否使用多进程或多线程模式,创建相应的WorkerController对象,用于管理多个Worker对象。Worker对象负责执行具体的测试用例,并将结果返回给WorkerController对象。
    - Pytest会调用hook函数pytest_runtestloop,表示开始循环执行测试用例。
    - Pytest会遍历Session对象中的items列表,依次取出每个Item对象,并调用hook函数pytest_runtest_protocol,表示开始执行单个测试用例的协议。
    - Pytest会调用hook函数pytest_runtest_logstart,表示开始记录单个测试用例的日志信息。
    - Pytest会调用hook函数pytest_runtest_setup,表示开始执行单个测试用例的前置操作(例如setup函数或方法)。
    - Pytest会调用hook函数pytest_runtest_call,表示开始执行单个测试用例的主体部分(例如测试函数或方法)。
    - Pytest会调用hook函数pytest_runtest_teardown,表示开始执行单个测试用例的后置操作(例如teardown函数或方法)。
    - Pytest会调用hook函数pytest_runtest_logfinish,表示结束记录单个测试用例的日志信息,并生成日志报告。
    - Pytest会调用hook函数pytest_runtest_makereport,表示根据单个测试用例的执行结果,生成测试报告(包括setup、call和teardown三个阶段的报告)。
    - Pytest会重复上述步骤,直到所有的Item对象都被执行完毕。
    - Pytest会调用hook函数pytest_sessionfinish,表示测试会话结束,并生成最终的测试报告(包括所有Item对象的报告)。
    - Pytest会调用hook函数pytest_terminal_summary,表示在终端输出最终的测试结果和统计信息。

  • 相关阅读:
    视频分析【video analytics】的项目的关键因素 -- 如何选择合适的摄像头,存储设备,以及AI推理硬件?
    【CKS】考试之 kube-bench CIS 基准测试
    LNMP动态网站
    springboot项目中使用minio进行对象存储
    java8 新特性 -Optional的常见用法
    redis悲观锁和乐观锁
    2022-12-01 mysql列存储引擎-将exists子查询转换为between操作-分析
    诗诺克科技引领数字资产智能交易革命
    【CSS】font-weight设置为500显示不出加粗效果
    C++ const修饰符
  • 原文地址:https://www.cnblogs.com/beyond-tester/p/17665993.html