Pytest测试框架的扩展(钩子)机制 - hooks

引用可由conftest.py文件实现的所有钩子函数。

​ 钩子函数(Hooks)是一种特殊的函数,可以在执行过程中“顺带”执行一些自定义的操作。Pytest测试框架提供了许多钩子函数,可以在测试过程的不同阶段执行自定义的操作。

Hooks

初始化类型钩子函数

image-20240913135532258

conftest.py由于pytest[在启动期间发现插件的方式,此函数应仅在位于测试根目录的插件或文件中实现。

pytest_addhooks(pluginmanager) - 添加自定义钩子函数

pass

pytest_addoption(parser) - 添加参数

可以给pytest执行添加更多的额外参数,提供测试扩展信息

  • 参数

    • parser(_pytest.config.Parser) - 要添加命令行选项,请调用parser.addoption(...)。添加ini文件值调用parser.addini(...)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # conftest.py
    def pytest_addoption(parser):
    """
    parser.addoption(...) 拓展命令行参数
    """
    # 单选项
    parser.addoption("--env", action="store", default="dev", help="specify the test environment")
    # 选项组 将具有共同主题或作用的选项放在一起,使得管理更加方便
    group_up = parser.getgroup('upgrade')
    group_up.addoption('--upgrade', action='store_true', help='Upgrade location before testcase execution')
    group_up.addoption('--up-fw', action='store', help='FW version to upgrade')
    group_up.addoption('--up-branch', action='store', help='Branch name you want to get FW from')
    group_up.addoption('--up-build-id', action='store', help='Build number')

    """
    parser.addini(...) 拓展pytest.ini配置项
    """
    parser.addini("project_name", type="string", default="MyProject",
    help="Name of the project for testing")
  • 后续可以使用config.getoption() 和 config.getini() 获取对应属性值

    • config.getoption(name)可以在钩子函数中检索命令行选项的值。

      1
      2
      3
      4
      # conftest.py pytest_configure钩子函数中获取
      def pytest_configure(config):
      env = config.getoption("--env")
      print(env)
    • request.config.getoption(name) 可以在测试用例中可以使用

      1
      2
      3
      4
      class AddHooks:
      def test_01_addHooks(self, request):
      env = request.config.getoption("--env")
      print(env)
pytest_configure(config)

允许插件和conftest文件执行初始配置。在解析了命令行选项后,为每个插件和初始conftest文件调用此钩子函数。之后,在导入钩子时会调用其他conftest文件。可以使用这个钩子函数来设置一些全局的配置,注册自定义插件或者执行一些初始化操作。

  • 参数:

    • config(_pytest.config.Config) - pytest配置对象
  • 案例

    • 在conftest.py 中pytest_configure钩子函数config对象定义一个添加属性
    1
    2
    3
    4
    5
    import pytest  

    def pytest_configure(config):
    print("Running pytest_configure hook...")
    config.my_custom_setting = "Custom setting value"
    • 测试用例中使用自定义值
    1
    2
    3
    4
    5
    6
    7
    import pytest
    class TestConfig:
    @pytest.mark.config
    def test_config_setting(self,request):
    from _pytest.config import Config
    print(isinstance(request.config,Config))
    print(request.config.my_setting)
pytest_unconfigure(config)

测试会话结束之后,程序退出之前被调用,用于执行一些清理工作或结果处理逻辑

  • 参数:
    • config(_pytest.config.Config) - pytest配置对象
pytest_sessionstart(session)

Session创建对象之后以及执行收集和进入运行测试循环之前调用。函数接收一个session对象作为参数,通过session对象可以获取到测试会话的信息和配置。

  • 参数:

    • session(_pytest.main.Session) - pytest会话对象
  • 问题:

    • 在 pytest_sessionstart(session) 钩子函数中,通过 session.items 获取收集到的测试用例信息是一个正确的方法。如果你在实际使用中发现获取到的是空对象,可能是因为在 pytest_sessionstart(session) 钩子函数运行时,测试用例还没有被收集,导致 session.items 是空的。
    • 一个解决方法是将逻辑延迟到 pytest_collection_modifyitems 函数中,因为这个钩子函数会在测试用例收集之后,用例被修改之前被调用。你可以在这个钩子函数中查看收集到的测试用例信息或者自定义收集条件。
pytest_sessionfinish(session, exitstatus)

在整个测试运行完成之后,,在将退出状态返回到系统之前调用。

  • 参数:
    • session(_pytest.main.Session) - pytest会话对象
    • exitstatus(int) - pytest将返回系统的状态
pytest_plugin_registered(plugin,manager) - 注册插件

在 pytest 加载并注册插件时被调用。这个钩子允许你在插件被注册时执行一些定制的逻辑。例如,你可以使用这个钩子来监控哪些插件被注册,或者在特定插件被注册时执行一些特定的操作。

  • 参数:

    • 插件- 插件模块或实例
    • manager(_pytest.config.PytestPluginManager) - pytest插件管理器
  • 功能 - 可以拦截注册插件进行额外逻辑操作

    1
    2
    3
    4
    5
    def pytest_plugin_registered(plugin, manager):  
    print(f"Plugin '{plugin}' registered")

    if plugin == 'pytest-html':
    print("pytest-html plugin registered! Enabling HTML report...")

启动时的钩子函数

image-20240913135613514

启动时的钩子函数要求尽早注册插件(内部和setuptools插件)。

pytest_load_initial_conftests(early_config,parser,args)

在命令行选项解析之前实现初始conftest文件的加载。用于加载 pytest 的初始配置。在这个钩子中,你可以对 pytest 的配置进行一些初始的定制和设置。

  • 参数:
    • early_config(_pytest.config.Config) - pytest配置对象
    • args(list]) - 在命令行上传递的参数列表
    • parser(_pytest.config.Parser) - 添加命令行选项
pytest_cmdline_preparse(config,args)

(不推荐)在选项解析之前修改命令行参数。
此钩子被认为已弃用,将在未来的pytest版本中删除。考虑pytest_load_initial_conftests()改用。

参数:

  • config(_pytest.config.Config) - pytest配置对象
  • args(list]) - 在命令行上传递的参数列表
pytest_cmdline_parse(pluginmanager,args)

返回初始化的配置对象,解析指定的args。
在第一个非空结果处停止,请参见: firstresult:在第一个非空结果处停止

  • 参数:
    • pluginmanager(_pytest.config.PytestPluginManager) - pytest插件管理器
    • args(list]) - 在命令行上传递的参数列表
pytest_cmdline_main(config)

用于处理 pytest 命令行执行之后的逻辑。这个钩子函数允许你在 pytest 执行完命令行操作后进行一些自定义的处理,例如输出额外的信息、保存测试结果等。

  • 参数:
    • config(_pytest.config.Config) - pytest配置对象

收集用例时的钩子函数

pytest_collection(session)

执行给定会话的收集协议。用于执行测试用例的收集和组织操作。在 pytest 运行过程中,该函数会组织并收集所有的测试用例,以便后续执行测试。

  • 参数:
    • session(_pytest.main.Session) - pytest会话对象
pytest_ignore_collect(path,config)

用于在收集测试文件和测试函数之前决定是否忽略特定的文件或目录。该函数接受两个参数 pathconfig,其中 path 是要收集的文件或目录的路径,config 是当前的配置对象。
通过在 pytest_ignore_collect() 钩子函数中返回 TrueFalse,可以指示 pytest 是否忽略指定的文件或目录。如果返回 True,pytest 将忽略该文件或目录,不会对其进行测试用例收集。

  • 参数:
    • path(str) - 要分析的路径
    • config(_pytest.config.Config) - pytest配置对象
pytest_collect_file(path,parent)

收集测试文件时可以每个文件进行检索操作。该函数接收两个参数 pathparent,其中 path 表示要收集的测试文件的路径,parent 表示该文件的父节点对象。

  • 参数:
    • path(str) - 要收集的路径
    • parent(str) - 该文件的父节点对象
pytest_pycollect_makemodule(path,parent)

收集测试模块时可以对每个测试模块进行操作。该函数接收两个参数 pathparent,其中 path 表示要收集的测试文件的路径,parent 表示该文件的父节点对象。

  • 参数:
    • path(str) - 要收集的路径
    • parent(str) - 该文件的父节点对象
pytest_generate_tests(metafunc)

用于生成测试用例。当 pytest 收集到测试函数时,会调用 pytest_generate_tests 钩子函数来生成实际的测试用例,这样可以动态地生成测试用例,比如根据参数化的数据生成多个测试用例。

  • 参数

    • metafunc - 参数是一个函数参数化对象,可以访问测试函数的签名、参数等信息,并根据需要为测试函数提供参数化数据。
  • 功能

    • metafunc.function.__name__ : 可以获取测试用例函数名

    • metafunc.module.____name__: 可以获取测试用例模块名

    • 参数化测试函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      import pytest  

      def test_addition(metafunc):
      if 'input_data' in metafunc.fixturenames:
      for x, y, expected_result in [(1, 2, 3), (4, 5, 9)]:
      metafunc.parametrize("input_data", [(x, y, expected_result)])

      x, y, expected_result = metafunc.function_args["input_data"]
      assert x + y == expected_result
pytest_make_parametrize_id(config,val,argname)

用于自定义参数化后的测试用例的 ID。它接收三个参数:config 表示 pytest 的配置对象,val 表示参数化中的值,argname 表示被参数化的参数名称。
这个钩子函数可以在参数化测试用例时自定义测试用例的 ID,以便更好地显示在测试报告中,提高测试结果的可读性。

  • 参数:
    • config(_pytest.config.Config) - pytest配置对象
    • val- 参数化值
    • argname(str) - pytest生成的自动参数名称
1
2
def pytest_make_parametrize_id(config, val, argname):  
return f"{argname}_{val}"
pytest_collection_modifyitems(session,config,items)

用例收集的所有case,这个钩子函数接收两个参数:session 表示当前的 pytest 会话对象,config 表示 pytest 的配置对象。可以实现对收集到的测试用例进行排序、过滤、添加标记等操作,从而对测试用例进行进一步的定制化处理

  • 参数:

    • session(_pytest.main.Session) - pytest会话对象
    • config(_pytest.config.Config) - pytest配置对象
    • items(List_pytest.nodes.Item]) - 项目对象列表
  • 可以处理收集的case

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # conftest.py  

    def pytest_collection_modifyitems(config, items):
    # 对测试用例按文件名进行排序
    items.sort(key=lambda x: x.fspath)

    # 根据标记选择性运行测试用例
    selected_items = []
    for item in items:
    if "slow" in item.keywords:
    selected_items.append(item)
    config.hook.pytest_deselected(items=items)
    items[:] = selected_items
pytest_deselected(items):

收集到,但没有被执行的case

  • collected 54 items / 52 deselected / 2 selected
    • collected - 总共收集到的所有case
    • deselected - 当前执行项,不被执行的case
    • selected - 执行的case
pytest_report_collectionfinish(config, startdir, items)

收集到且需要执行的用例的case

pytest_collection_finish(session)

这个钩子函数接收一个参数 session,表示当前的 pytest 会话对象。
该钩子函数会在测试用例收集过程的最后被调用

  • 参数

    • session(_pytest.main.Session) - pytest会话对象
  • 案例

    1
    2
    3
    def pytest_collection_finish(session):
    total_tests = len(session.items)
    print(f"Total number of collected tests: {total_tests}")

执行用例时的钩子函数

pytest_runtestloop(session)

执行主运行测试循环(收集完成后)。

  • 参数:
    • session(_pytest.main.Session) - pytest会话对象
pytest_runtest_protocol(item,nextitem)

用于当前单个执行测试用例的运行协议。这个钩子函数接收两个参数:item 表示当前要运行的测试用例对象,nextitem 表示下一个要运行的测试用例对象。
可以在测试用例执行过程中进行一些自定义的操作或修改行为。例如,可以在执行测试用例前后记录日志、统计执行时间、处理异常等。

  • 参数:
    • item- 为其执行运行测试协议的测试项目。
    • nextitem- 预定下一个测试项目(如果这是我朋友的结束,则为无)。这个论点被传递给了
      pytest_runtest_teardown()
  • 在执行测试用例时,pytest 将会按照以下流程执行 pytest_runtest_protocol 钩子函数:
    1. 执行当前测试用例 item 的所有 fixture 函数(如 setup 函数)。
    2. 执行当前测试用例 item 的测试函数。
    3. 执行 pytest_runtest_protocol 钩子函数,并传入当前测试用例 item 和下一个测试用例 nextitem
    4. 根据需要,执行下一个测试用例 nextitem 的 fixture 函数。
pytest_runtest_logstart(nodeid,location)

用于记录当前该测试用例开始执行时的日志信息。这个钩子函数在测试用例开始执行时被调用,可以用来记录当前测试用例的开始执行信息。

  • 参数:

    • nodeid(str) - 项目的完整测试路径

      1
      '''test/test_01/test_02_configset.py::TestConfig::test_config_setting'''
    • location- (filename,linenum,testname)

      1
      '''('test\\test_01\\test_02_configset.py', 12, 'TestConfig.test_config_setting')'''
pytest_runtest_logfinish(nodeid,location)

用于记录当前该测试用例执行后的日志信息。这个钩子函数在每个测试用例执行之后被调用,可以用来记录当前测试用例的执行之后的信息。

  • 参数:
    • nodeid(str) - 项目的完整测试路径
    • location- (filename,linenum,testname)
pytest_runtest_setup(item)

通过使用这两个钩子函数,我们可以在测试用例的设置和执行阶段进行一些自定义的操作,以满足特定的测试需求或进行定制化的处理。在setup测试夹具之前执行

  • 参数:
    • item: 测试项的信息
pytest_runtest_call(item)

通过使用这两个钩子函数,我们可以在测试用例的设置和执行阶段进行一些自定义的操作,以满足特定的测试需求或进行定制化的处理。在测试夹具之后,用例执行之前执行

  • 参数:
    • item: 测试项的信息
def pytest_runtest_teardown(item,nextitem)

每个测试用例出结果后执行,teardown测试夹具之前执行

  • 参数:
    • item: 测试项的信息
    • nextitem- 计划下一个测试项目(如果没有安排其他测试项目,则为None)。这个参数可以用来执行精确的拆卸,即调用足够的终结器,以便nextitem只需要调用setup-functions
pytest_runtest_makereport(item,call)

该钩子函数会在每个测试用例的pytest_runtest_setup(item),pytest_runtest_call(item),def pytest_runtest_teardown(item,nextitem) 三个钩子函数之后执行

  • 参数
    • item: 测试项的信息
    • call:记录了每个执行状态信息
      • <CallInfo when='setup' result: None> setup 之后执行
      • <CallInfo when='call' result: None> call 之后执行
      • <CallInfo when='teardown' result: None> teardown之后执行

生成测试结果时的钩子函数

与会话报告相关的钩子函数
pytest_collectstart(collector) / pytest_make_collect_report(collector)

查找用例模块。pytest_collectstart(collector)先执行,pytest_make_collect_report(collector)随后执行

  • 参数
    • collector
pytest_itemcollected(item)

查找所有用例

itemcollected

pytest_deselected(items)

收集了但未被执行的用例

pytest_report_teststatus(report, config)

该钩子函数会在每个测试用例的pytest_runtest_setup(item),pytest_runtest_call(item),def pytest_runtest_teardown(item,nextitem) 三个钩子函数之后执行,且在pytest_runtest_makereport(item,call)之后。

  • 参数
    • report - 返回pytest_runtest_setup(item),pytest_runtest_call(item),def pytest_runtest_teardown(item,nextitem) 3个钩子函数执行状态
    • config - pytest配置对象
pytest_terminal_summary(terminalreporter,exitstatus,config)

用于在测试运行结束后生成测试结果的总结信息并显示在终端中。

  • 参数
    • terminalreporter: 表示 pytest 的终端报告器对象,可以使用其中的方法来输出信息到终端。
    • exitstatus: 表示测试运行的退出状态,通常为 0 表示测试运行成功,非 0 表示测试运行失败。
    • config: 表示包含了 pytest 配置信息的对象,可以获取和设置 pytest 的配置选项。
pytest_fixture_setup(fixturedef,request)

执行Fixture前置处理方法设置执行,调用fixture函数的返回值,在pytest_runtest_setup(item) 之后,setup夹具方法之前执行,每一个前置处理器都会执行一次

pytest_fixture_post_finalizer(fixturedef,request)

执行Fixture后置处理方法设置执行,调用fixture函数的返回值,在后置处理函数 之后,pytest_runtest_makereport方法之前执行,每一个后置处理器都会执行一次

pytest_runtest_logreport(report)

pytest_runtest_setup(item),pytest_runtest_call(item),def pytest_runtest_teardown(item,nextitem) 三个钩子函数之后执行,且在pytest_report_teststatus后执行

断言相关钩子函数
pytest_assertrepr_compare(config,op,left,right)

断言失败后调用

pytest_assertion_pass(item, lineno, orig, expl)

断言通过时调用

  • 使用前提 - 需要开启配置enable_assertion_pass_hook

    1
    2
    [pytest]
    enable_assertion_pass_hook=true
  • 参数

    • 参数:
      • item(_pytest.nodes.Item) - 当前测试的pytest项目对象
      • lineno(int) - 断言语句的行号
      • orig(string) - 带有原始断言的字符串
      • expl(string) - 带有断言解释的字符串

调试/交互钩子函数

pytest_internalerror(excrepr,excinfo)

内部出错时调用。

pytest_keyboard_interrupt(excinfo)

键盘中断时调用。

pytest_exception_interact(node,call,report)

在引发异常时调用,可以交互式处理。只有在引发的异常不是内部异常, 如skip.Exception时才会调用此钩子函数。

pytest_enter_pdb(config,pdb)

调用pdb.set_trace()时,插件可以使用插件在python调试器进入交互模式之前采取特殊操作。

  • 参数:

    • config(_pytest.config.Config) - pytest配置对象

    • pdb(pdb.Pdb) - Pdb实例

整个hooks运行流程顺序

  • 初始化

image-20240912174846499

  • 命令行解析

image-20240912174906721

  • 运行用例

image-20240912175144375

  • 结束

image-20240912175201730

声明新的钩子函数

使用第三方插件的钩子函数