pytest的钩子函数简述

什么是钩子函数

在 pytest 中,钩子函数(hook function)是指一种特殊的函数,它们可以被 pytest 插件或 initial conftest 文件中定义的一系列函数所调用,用于扩展或修改 pytest 的行为。pytest 钩子函数的命名约定是以 pytest_ 作为前缀的 Python 函数。

通过这些钩子我们可以对pytest 用例收集、用例执行、报告输出等各个阶段进行干预,根据需求去开发对应的插件,以满足自己的使用场景。

hook.png

钩子函数的分类

pytest中的钩子函数按功能一共分为6类:引导钩子,初始化钩子、用例收集钩子、用例执行钩子、报告钩子、调试钩子。

引导钩子

pytest_load_initial_conftests

pytest_load_initial_conftests 是 pytest 的一个钩子函数,用于加载 initial conftest 文件。initial conftest 文件是指位于项目根目录下的 conftest.py 文件,它会影响整个项目的测试配置和行为。

当 pytest 启动时,会先加载 initial conftest 文件,然后再逐层向下搜索其他目录中的 conftest 文件,形成整个测试环境。而 pytest_load_initial_conftests 就是在加载 initial conftest 文件时被调用的钩子函数。

如果我们需要在 initial conftest 文件加载前或加载后做一些特定的操作,例如修改测试配置、动态加载插件等,就可以使用 pytest_load_initial_conftests 钩子函数来实现。

def pytest_load_initial_conftests(early_config, parser, args):

参数:
early_config:pytest 配置对象。
args:命令行上传递的参数。
parser:命令行添加的选项。
触发时机:当在命令行通过pytest执行命令时,会先执行该钩子函数
默认作用:加载conftest.py文件
注意点:该钩子函数只有定义在插件中才会调用,在conftest定义则不会调用

pytest_cmdline_main

pytest_cmdline_main 是 pytest 中的一个内部函数,用于执行 pytest 命令行参数解析和测试执行。该函数负责将命令行参数转换为 pytest 的配置选项,并使用这些选项来执行各种测试任务,包括测试收集、fixture 注册、测试执行、结果报告等等。

触发时机:执行运行主命令后执行
默认作用:调用命令解析钩子pytest_cmdline_parse和执行runtest_mainloop
参数:
config:pytest 配置对象

pytest_cmdline_parse

pytest_cmdline_parse 是 pytest 中的一个内部函数,用于解析 pytest 命令行参数并返回相应的 Config 对象。它会根据传入的命令行参数,解析对应的 pytest 配置选项,并将这些选项封装到 Config 对象中,以便后续在 pytest 执行过程中使用。

args:命令行上传递的参数。
pluginmanager :插件管理器
默认作用:用来初始化配置对象,解析指定的参数
注意点:该钩子函数只有定义在插件中才会调用,在conftest定义则不会调用

初始化钩子

初始化钩子用来调用插件和conftest.py文件的初始化

pytest_addoption(重点

pytest_addoption 是 pytest 中的一个 hook 函数,用于扩展 pytest 的命令行选项。它可以帮助用户定义自己的命令行参数,并将这些参数保存在 pytest 的 Config 对象中,以供后续在测试用例中使用。

# content of conftest.py

# 添加一个运行参数:--name
def pytest_addoption(parser):
  parser.addoption(
     "--name",
     action="store",
     dest="name",
     default="World",
     help='参数的帮助提示信息',
 )

这段代码是在 pytest 中通过 pytest_addoption 函数向 pytest 添加了一个新的命令行选项 --name,并将其保存在 Config 对象中。

具体来说,代码中的四个参数含义如下:

  • parser: 表示当前正在处理的参数解析器实例,用于添加新的命令行选项;

  • --name: 表示要添加的新的命令行选项;

  • action="store": 表示该选项的值将被存储在 Config 对象中;

  • dest="name": 表示该选项的值将被存储在 Config 对象中的 name 属性中;

  • default="World": 表示该选项的默认值为 "World"

  • help='参数的帮助提示信息': 表示该选项的帮助信息,在运行 pytest 时通过 --help-h 参数显示。

当用户在命令行中指定 --name 参数时,pytest 将会将该参数的值存储在 Config 对象中的 name 属性中。我们可以在测试用例中通过 request.config.getoption('name') 来获取该参数的值。

例如,如果我们在命令行中运行 pytest --name "Shibuyu",则 pytest 将会将 name 属性的值设为 "Shibuyu"。我们可以在测试用例中使用如下代码来获取该值:

def test_hello_world(request):
    name = request.config.getoption('name')
    print(f'Hello, {name}!')

以上代码中,request.config.getoption('name') 方法返回了命令行参数 --name 的值,并将其存储在 name 变量中。最后我们打印了一条带有名字的问候语。

testcases\test_demo01.py Hello, shibuyu!

总之,这段代码通过 pytest_addoption 函数向 pytest 添加了一个新的运行参数,为测试提供了更多的灵活性和定制性。用户可以通过指定不同的参数值,在不同的测试场景中进行测试。

在 pytest 中,session.config.getoption()request.config.getoption() 都是用于获取命令行选项的值。

  • session.config 是一个 Config 对象,代表整个测试会话(即整个测试过程)

  • request.config 是一个 Config 对象,代表当前测试用例对应的测试运行上下文。这两个方法都返回一个 Option 对象,该对象包含选项的名称、默认值和当前选项的值。你可以通过 getoption() 方法来获取选项的值,该方法的参数是选项的名称。

  • 这两种方法的主要区别是作用域不同。session.config.getoption() 获取的是在整个测试会话期间指定的选项值,而 request.config.getoption() 获取的是在当前测试用例环境中指定的选项值。

pytest_configure

pytest_configure 函数是一个 pytest 插件钩子函数,用于在 pytest 执行过程中进行配置和初始化。该函数需要一个 config 参数,它是一个 pytest.config.Config 实例,代表了 pytest 的配置对象。

具体来说,pytest_configure 函数会在 pytest 的主机启动时调用一次,并允许 pytest 插件在此期间向配置对象添加自定义选项、修改默认值等。此外,还可以利用该函数来读取环境变量、加载配置文件、连接数据库等操作。

除了上述示例之外,pytest_configure 还可以用于执行其他一些操作,例如:

  • 加载配置文件并设置 pytest 的默认配置。

  • 将测试数据或通用的测试辅助类注入到 pytest 中。

  • 连接测试所需的数据库。

  • 向 pytest 添加自定义的 fixture 以供测试用例使用。

  • 加载自定义插件或 hook 以扩展 pytest 的功能。

需要注意的是,pytest_configure 函数只会在 conftest.py 或者是 plugins 目录下的插件模块中被调用。如果你要使用该函数,请确保将它定义在正确的位置,并且符合 pytest 的插件命名和组织规范。

pytest_unconfigure

pytest_unconfigure 是 pytest 的钩子函数之一,它在 pytest 运行结束后被调用,用于在测试会话结束时进行资源清理和相关操作。具体来说,pytest_unconfigure 通常用于在测试会话结束时关闭数据库连接、清理临时文件、关闭网络连接等操作。

pytest_sessionstart

pytest_sessionstart 是 pytest 的一个插件钩子,它在整个测试会话开始时被调用。它提供了一个机会来执行一些初始化工作,例如打开配置文件、建立数据库连接等等。

参数
session:pytest 会话对象
触发时机:在创建Session对象之后、执行收集测试用例之前调用

pytest_sessionfinish

pytest_sessionfinish 是 pytest 的一个插件钩子,在整个测试会话结束时被调用。它提供了一个机会来执行一些清理工作,例如关闭数据库连接、删除临时文件等等

参数
session: pytest 会话对象
exitstatus: pytest 将返回系统的状态
触发时机:在整个测试运行完成后调用,就在将退出状态返回给系统之前

pytest_plugin_registered

pytest_plugin_registered 是 pytest 的一个插件钩子,在注册插件时被调用。它提供了一个机会来监视和处理 pytest 中的插件注册过程。

参数
plugin : 插件模块或实例
manager : pytest 插件管理器
作用:注册一个新的插件

以下是一个简单的示例,展示了如何在 pytest_plugin_registered 中检查已注册的插件:

# content of conftest.py

import pytest

def pytest_plugin_registered(plugin, manager):
    # 检查已注册的插件
    print(f"Plugin {plugin} registered with manager {manager}")

在这个示例中,我们定义了 pytest_plugin_registered 钩子函数,用于在插件注册过程中检查已经注册的插件。该函数接受两个参数:plugin 表示已注册的插件对象,manager 表示插件管理器对象。

在函数体内,我们使用 print 函数输出已经注册的插件对象以及插件管理器对象。这些信息可以帮助我们更好地了解插件的注册过程,以便在需要的时候进行调试和优化。

pytest_addhooks

pytest_addhooks 是 pytest 的一个插件钩子,在插件注册时被调用。它提供了一个机会来向 pytest 添加新的自定义钩子函数。

参数
pluginmanager :插件管理器
触发时机:注册插件时调用,添加钩子函数到执行列表
默认作用:调用 pluginmanager.add_hookspecs(module_or_class, prefix) 注册插件

用例收集钩子

pytest默认的用例收集流程

1、以 session作为初始收集器 ,按照下面的流程,收集所有测试用例

  • 执行pytest_collectstart(collector)开始收集

  • 执行report = pytest_make_collect_report(collector),创建一个收集报告对象

  • 收集过程中,如果出现交互异常,则执行pytest_exception_interact(collector, call, report)

  • 对收集的节点进行判断,如果是用例执行pytest_itemcollected(item),如果是收集器则进行递归处理。

  • 执行pytest_collectreport(report),处理收集的报告

2、对收集到的用例进行修改。

  • 执行pytest_collection_modifyitems(session, config, items)

3、整理收集到的测试用例。

  • 执行pytest_collection_finish(session)

4、将收集的用例保存到session.items中。

5、将收集的用例数量设置为 session.testscollected 属性。

pytest_collection

设置pytest收集用例执行的流程,这个钩子函数一般不需要重写,除非你想自己制定pytest用例收集的流程。

参数:session:pytest 会话对象

触发时机:收集用例之前执行,执行该钩子进行用例收集

以下是一个简单的示例,展示了如何在 pytest_collection 中实现一个自定义测试用例筛选器:

# content of myplugin.py

def pytest_collection(session):
    selected_items = []
    deselected_items = []

    # 遍历所有的测试用例
    for item in session.items:
        # 如果文件名以 "_ignore_" 开头,则跳过该测试用例
        if item.fspath.basename.startswith("_ignore_"):
            deselected_items.append(item)
        else:
            selected_items.append(item)

    # 更新 session.items 列表
    session.items = selected_items
    session.items_deselected.extend(deselected_items)

在这个示例中,我们定义了一个 Python 模块 myplugin.py,用于实现一个名为 pytest_collection 的自定义钩子函数。该函数通过遍历所有测试用例,并根据文件名是否以 "ignore" 开头来筛选测试用例。

如果测试用例的文件名以 "ignore" 开头,则将其添加到 deselected_items 列表中,并在最后更新 session.items 列表来排除这些测试用例。

pytest_ignore_collect

参数collection_path: 路径、config: pytest配置对象

触发时机:对文件和目录进行收集之前会执行改钩子函数

返回值:布尔值(会根据返回值为True还是False来决定是否收集改路径下的用例)

pytest_ignore_collect 是 pytest 的一个插件钩子,在测试用例收集阶段被调用。它提供了一个机会来忽略 pytest 收集到的测试用例。

以下是一个简单的示例,展示了如何在 pytest_ignore_collect 中实现一个忽略指定目录或文件的测试用例:

# content of myplugin.py

import os

def pytest_ignore_collect(path, config):
    # 如果当前路径是指定目录或文件,则忽略该路径下的所有测试用例
    if path.startswith(os.path.join(os.getcwd(), "tests", "ignore")):
        return True

在这个示例中,我们定义了一个 Python 模块 myplugin.py,用于实现一个名为 pytest_ignore_collect 的自定义钩子函数。该函数通过检查当前路径是否为指定目录或文件来决定是否忽略 pytest 收集到的测试用例。

如果路径以指定目录或文件开头,则返回 True 来忽略该路径下的所有测试用例。否则,返回 None 表示不忽略任何测试用例。

pytest_collect_file

搜索测试文件路径的钩子函数

参数:file_path : 收集的路径、parent : 父级目录路径

触发时机:对每个路径进行收集之前会执行改钩子函数

返回值:布尔值(会根据返回值为True还是False来决定是否收集该路径下的用例)

pytest_pycollect_makemodule

收集测试模块的钩子函数,每个测试模块都会调用该钩子函数进行收集

参数:module_path : 模块路径

触发时机:搜索测试模块触发的钩子函数

返回值:模块

pytest_pycollect_makeitem

收集模块中用例的钩子函数,对模块中的用例进行收集

参数:collector: 模块对象、name: 名称、obj: 对象

触发时机:对文件和目录进行收集之前会执行改钩子函数

pytest_generate_tests

根据用例参数化传入的参数数量生成测试用例,生成测试用例

参数:metafunc : 元函数

触发时机:对用例方法进行参数化,生成用例

pytest_make_parametrize_id

参数化生成用例时,生成parametrize_id(默认情况下参数化生成的用例名由原用例名和parametrize_id组成),可以通过该钩子函数修改生成用例的方法名。

参数:config : pytest 配置对象、val : 参数化值、argname: pytest 生成的自动参数名称

触发时机:对用例方法进行参数化,生成用例名称

返回参数化的id

pytest_markeval_namespace

收集用例时 评估 被xfail或skipif标记用例的条件,改变测试跳过的钩子:

参数:config : pytest 配置对象

触发时机:收集的用例被xfail或skipif标记用例时触发

pytest_collection_modifyitems(重要)

用例收集完成后,可以通过该钩子函数修改用例的顺序,删除或以其他方式修改测试用例。

参数:session: pytest会话对象、config : pytest 配置对象、items: 测试用例列表

触发时机:用例收集完后调用该钩子函数

pytest_collection_modifyitems 是 pytest 的一个 hook 函数,用于在测试项目收集完毕并经过了初步的解析后,进一步修改和调整测试项目列表。

该 hook 函数接受两个参数 sessionconfig,其中 session 表示 pytest 的 session 对象,config 表示 pytest 的配置对象。其主要作用是对初始的测试项目列表进行修改和排序,或者为每个测试项目添加一些自定义的标记或属性。

下面是一个示例,展示了如何使用 pytest_collection_modifyitems 来为测试项目添加 custommark 标记和参数化信息:

def pytest_collection_modifyitems(session, config, items):
    for item in items:
        # 为每个测试项目添加custommark标记
        item.add_marker(pytest.mark.custommark)

        # 为每个测试项目添加参数化信息
        if item.parent.name == 'test_the_module':
            item.parametrize(
                'input_data, expected_output',
                [
                    ('abc', 'ABC'),
                    ('def', 'DEF'),
                    ('123', '123'),
                ]
            )

在上面的代码中,我们首先使用 add_marker 方法为每个测试项目添加了名为 custommark 的自定义标记。然后,我们为测试模块 the_module 中的测试用例添加了参数化信息,其中 input_dataexpected_output 分别表示输入和期望的输出数据,三个元素分别对应三个测试用例。

当 pytest 运行这些测试项目时,将会执行每个测试项目,并为这些测试项目添加了我们定义的自定义标记和参数化信息。

总之,pytest_collection_modifyitems 是非常常用的一个 hook 函数,通过它我们可以对 pytest 收集到的测试项目进行进一步的修改和调整,以达到更好的测试效果。

pytest_collection_finish

参数:session: pytest会话对象

触发时机:在收集完用例和修改收用例集之后调用

测试运行钩子

pytest_runtestloop

pytest_runtestloop 是 pytest 的一个 hook 函数,它在测试项目运行期间被多次调用,每次调用都表示一轮测试执行。

该 hook 函数接受三个参数 sessionconfigitems,其中 session 表示 pytest 的 session 对象,config 表示 pytest 的配置对象,items 则是当前轮次需要执行的测试项目列表。

def pytest_runtestloop(session, config):
    print("Starting test run...")

    for i in range(3):
        print(f"Round {i+1} of testing...")
        for item in session.items:
            if item.parent.name != 'test_something':
                continue
            item.config.hook.pytest_runtest_protocol(item=item, nextitem=None)
            if session.shouldstop:
                raise session.Interrupted(session.shouldstop)

    print("Test run finished!")

在上面的代码中,我们首先在每轮测试开始前打印了一条信息 "Starting test run..."。然后,我们设置了一个简单的测试循环,该循环执行了三轮测试。在每轮测试开始前,我们使用 print 函数打印出当前测试轮次的编号。接着,我们遍历测试项目列表,只对测试模块名为 'test_something' 的测试项目进行测试执行,并使用 pytest_runtest_protocol hook 函数来执行该测试项目。最后,在每轮测试完成后,我们使用 print 函数打印出一句话 "Test run finished!"

当 pytest 运行这个例子时,将会依次进行三轮测试,每轮测试都会针对名为 'test_something' 的测试模块进行测试执行,并输出相应的测试轮次编号。例如,输出可能如下所示:

Starting test run...
Round 1 of testing...
Round 2 of testing...
Round 3 of testing...
Test run finished!

总之,pytest_runtestloop 可以用于自定义 pytest 的测试执行流程和操作方式,例如针对特定的测试用例子集进行测试、在测试运行前后打印日志、在测试失败时发送邮件等。

pytest_runtest_protocol

pytest_runtest_protocol 是 pytest 的一个 hook 函数,它在每次测试执行前被调用,其主要作用是根据测试项目的类型(function、method、class)来调用不同类型的执行函数,并在测试执行过程中产生相应的测试报告和结果。

该 hook 函数接受一个参数 item,表示当前需要执行的测试项目。在 pytest_runtest_protocol 中,我们可以向 item 中添加自定义属性或修改其属性值,以控制测试执行的流程和方式。例如,我们可以通过修改 item.callspec.params 来动态修改测试用例的参数;或者通过修改 item.config 来在测试结束后发送邮件等。

def pytest_runtest_protocol(item, nextitem):
    # 执行测试用例前打印一条信息
    print(f"Running test case: {item.name}...")

    # 调用默认的执行函数来执行测试用例
    default_run_test_protocol(item, nextitem)

    # 执行测试用例后打印一条信息
    print(f"{item.name} execution finished!")

在上面的代码中,我们覆盖了 pytest_runtest_protocol 函数,添加了一些自定义的行为。首先,我们使用 print 函数输出了当前需要执行的测试用例的名称。然后,我们调用了默认的执行函数 default_run_test_protocol 来执行测试用例。最后,在测试用例执行完成后,我们再次使用 print 函数输出测试用例的名称以表示执行结束。

当 pytest 运行这个例子时,每次执行测试用例前后都会输出相应的信息,例如:

Running test case: test_add...
test_add execution finished!
Running test case: test_subtract...
test_subtract execution finished!

总之,pytest_runtest_protocol 可以用于自定义 pytest 的测试执行流程和操作方式,例如在测试执行前后打印日志、在测试失败时发送邮件等。

pytest_runtest_protocolpytest_runtestloop 都是 pytest 提供的钩子函数,用于在测试执行期间进行自定义操作。不同之处在于它们的作用和调用时机。

pytest_runtest_protocol 主要是在每个测试用例(或 fixture)执行前后被调用,可以使用它来修改测试用例及其参数、添加或修改全局变量等。通常,我们在 pytest_runtest_protocol 中使用 item 参数来获取关于当前测试用例的详细信息,例如名称、标记、参数等。在处理完这些信息后,我们可以返回一个可选的 nextitem 参数来控制接下来需要执行的测试用例。

相比之下,pytest_runtestloop 则是在测试集开始执行前和结束执行后被调用,可以用于修改测试配置、设置测试环境、检查测试报告等。通常,我们在 pytest_runtestloop 中使用四个参数 sessionconfigitemsnextitem,分别表示 pytest 的全局会话、配置、测试用例列表和当前需要执行的测试用例。在处理完这些信息后,我们可以返回一个可选的 nextitem 参数来控制接下来需要执行的测试用例。

因此,可以看出,pytest_runtest_protocol 更加专注于单个测试用例的执行过程,而 pytest_runtestloop 则更加关注整个测试集的管理和监控。两者都是 pytest 的重要钩子函数,可以让我们更加灵活地定制和掌控 pytest 的测试执行过程。

pytest_runtest_logstart

pytest_runtest_logstart 是 pytest 的一个钩子函数,它在每个测试用例(或 fixture)开始执行前被调用,可以用于自定义日志记录操作。

具体来说,当 pytest 运行测试用例时,会先调用 pytest_runtest_logstart 函数来输出测试用例的名称、参数、标记等基本信息,并创建对应的日志文件。这个过程发生在测试用例开始执行前,以便我们能够准确地记录测试用例的执行情况和结果。

pytest_runtest_logstart 函数中,我们可以使用 nodeid 参数获取当前需要执行的测试用例的标识符,例如 mymodule.py::test_myfunction[param1],然后根据这个标识符创建对应的日志文件。一般来说,我们可以使用 Python 自带的 logging 模块来实现日志记录和管理功能,例如:

import logging

def pytest_runtest_logstart(nodeid, location):
    # 根据 nodeid 创建对应的日志文件
    log_file = f"{nodeid.replace('::', '_').replace('[','').replace(']','')}.log"

    # 配置日志记录器
    logger = logging.getLogger(nodeid)
    logger.setLevel(logging.DEBUG)
    handler = logging.FileHandler(log_file)
    formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    # 在控制台打印日志开始信息
    logger.info(f"Test case {nodeid} starts executing...")

在上面的代码中,我们使用 nodeid 创建了一个相应的日志文件,并配置了一个名为 nodeid 的日志记录器。然后,我们设置了日志记录器的日志级别、文件处理器和格式化器,并将其添加到日志记录器中。最后,在日志记录器中输出了测试用例开始执行的信息。

当 pytest 运行这个例子时,会在当前目录下创建多个日志文件,例如 mymodule_py_test_myfunction_param1.log,其中记录了对应测试用例的执行情况和结果。

总之,pytest_runtest_logstart 可以用于自定义 pytest 的日志记录和管理操作,可以让我们更好地分析和追踪测试执行过程中的问题和异常。

pytest_runtest_logfinish

pytest_runtest_logfinish 是 pytest 的一个钩子函数,它在每个测试用例(或 fixture)结束执行后被调用,可以用于自定义测试结果的日志记录操作。

具体来说,当 pytest 执行完一个测试用例时,会先调用 pytest_runtest_logfinish 函数来输出测试用例的执行结果,并关闭对应的日志文件。这个过程发生在测试用例结束执行后,以便我们能够及时地记录测试用例的执行情况和结果。

pytest_runtest_logfinish 函数中,我们同样可以使用 nodeid 参数获取当前已经执行完毕的测试用例的标识符,例如 mymodule.py::test_myfunction[param1],然后根据这个标识符关闭对应的日志文件。一般来说,我们可以使用 Python 自带的 logging 模块来实现日志记录和管理功能,例如:

import logging

def pytest_runtest_logfinish(nodeid):
    # 关闭对应的日志文件
    logger = logging.getLogger(nodeid)
    for handler in logger.handlers:
        handler.close()
        logger.removeHandler(handler)

    # 在控制台打印日志结束信息
    logger.info(f"Test case {nodeid} finishes executing.")

在上面的代码中,我们使用 nodeid 获取了对应的日志记录器,并通过循环关闭了所有处理器。然后,在日志记录器中输出了测试用例执行结束的信息。

当 pytest 运行这个例子时,会在当前目录下创建多个日志文件,例如 mymodule_py_test_myfunction_param1.log,其中记录了对应测试用例的执行情况和结果。

总之,pytest_runtest_logfinish 可以用于自定义 pytest 的日志记录和管理操作,可以让我们更好地分析和追踪测试执行过程中的问题和异常。

pytest_runtest_setup

pytest_runtest_setup 是 pytest 的一个钩子函数,它在每个测试用例(或 fixture)的 setup 过程中被调用,可以用于自定义测试过程中的操作。

具体来说,当 pytest 执行测试用例时,会先调用测试用例关联的 fixture 的 setup 方法,以准备测试执行所需的环境和数据。在这个过程中,pytest 会依次调用所有 fixture 的 setup 方法,并在每个 fixture 的 setup 方法执行前,调用 pytest_runtest_setup 函数,以便我们能够在测试过程中进行自定义操作。

pytest_runtest_setup 函数中,我们可以使用 item 参数获取当前需要执行的测试用例的相关信息,例如测试用例的名称、参数、标记等信息,然后根据需要进行自定义操作。

一般来说,pytest_runtest_setup 函数经常被用于测试前的初始化,例如文件创建、数据准备、网络连接等操作。下面是一个示例,演示了如何在 pytest_runtest_setup 函数中创建一个临时文件并在 fixture 中使用它:

import tempfile

def pytest_runtest_setup(item):
    # 在临时目录中创建一个文件
    with tempfile.NamedTemporaryFile(delete=False) as f:
        item.funcargs['tempfile'] = f.name  # 将文件路径保存到 fixture 的参数中

def test_using_tempfile(tempfile):
    # 使用 fixture 中的参数
    with open(tempfile, 'w') as f:
        f.write('hello world')

    with open(tempfile, 'r') as f:
        assert f.read() == 'hello world'

在上面的例子中,我们使用 tempfile 模块创建了一个临时文件,然后将它的路径保存到 fixture 参数中。接下来,我们定义了一个测试用例函数 test_using_tempfile,并在其中使用了 fixture 中保存的文件路径。具体来说,我们通过读写文件的方式,将字符串 'hello world' 写入临时文件,然后再次读取这个文件,并断言读取的内容与写入的内容一致。

当 pytest 运行这个例子时,会在临时目录中创建一个临时文件,并执行测试用例 test_using_tempfile,最终输出测试结果。

总之,pytest_runtest_setup 可以用于自定义 pytest 的测试过程中的操作,例如进行测试前的初始化或准备工作。

pytest_runtest_call

pytest_runtest_call 是 pytest 的一个钩子函数,它在每个测试用例(或 fixture)的执行过程中被调用,可以用于自定义测试执行的操作。

具体来说,当 pytest 执行测试用例时,会先调用测试用例关联的 fixture 的 setup 方法,然后执行测试用例函数本身。在这个过程中,pytest 会依次调用所有 fixture 的 setup 方法,并在测试用例函数执行前,调用 pytest_runtest_call 函数,以便我们能够进行自定义操作、记录测试执行时间等。

pytest_runtest_call 函数中,我们可以使用 item 参数获取当前需要执行的测试用例的相关信息,例如测试用例的名称、参数、标记等信息,然后根据需要进行自定义操作。

一般来说,pytest_runtest_call 函数经常被用于测试执行前后的操作,例如打印日志、记录执行时间等操作。下面是一个示例,演示了如何在 pytest_runtest_call 函数中记录测试执行时间:

import time

def pytest_runtest_call(item):
    start_time = time.monotonic()  # 记录测试执行开始时间
    item.runtest()  # 执行测试用例
    end_time = time.monotonic()  # 记录测试执行结束时间
    execution_time = end_time - start_time  # 计算测试执行时间
    print(f"Test case {item.nodeid} executed in {execution_time:.3f}s")

在上面的例子中,我们使用 time 模块记录了测试执行开始时间和结束时间,并计算了测试执行时间。然后,在控制台输出了测试执行时间信息。这个例子只是一个简单的示例,实际中我们可能需要将测试执行时间记录到数据库、文件等存储介质中,以便后续的分析和查询。

当 pytest 运行这个例子时,会在控制台输出类似于以下的信息:

Test case mymodule.py::test_myfunction[param1] executed in 0.012s
Test case mymodule.py::test_myfunction[param2] executed in 0.011s

总之,pytest_runtest_call 可以用于自定义 pytest 的测试执行过程中的操作,例如记录测试执行时间、打印日志等。

pytest_runtest_teardown

pytest_runtest_teardown 是 pytest 的一个钩子函数,它在每个测试用例(或 fixture)的 teardown 过程中被调用,可以用于自定义测试过程中的操作。

具体来说,当 pytest 执行测试用例时,会先执行测试用例函数本身,然后再调用测试用例关联的 fixture 的 teardown 方法以清理测试环境。在这个过程中,pytest 会依次调用所有 fixture 的 teardown 方法,并在每个 fixture 的 teardown 方法执行后,调用 pytest_runtest_teardown 函数,以便我们能够进行自定义操作。

pytest_runtest_teardown 函数中,我们可以使用 item 参数获取当前需要执行的测试用例的相关信息,例如测试用例的名称、参数、标记等信息,然后根据需要进行自定义操作。

一般来说,pytest_runtest_teardown 函数经常被用于测试结束后的清理操作,例如关闭打开的文件、断开网络连接等操作。下面是一个示例,演示了如何在 pytest_runtest_teardown 函数中关闭文件:

def pytest_runtest_teardown(item, nextitem):
    # 获取 fixture 中保存的文件对象并关闭它
    if 'file_obj' in item.funcargs:
        file_obj = item.funcargs['file_obj']
        if not file_obj.closed:
            file_obj.close()

在上面的例子中,我们首先判断当前测试用例是否使用了名为 file_obj 的 fixture。如果是,就获取这个 fixture 中保存的文件对象,并检查它是否已经关闭。如果文件对象还没有关闭,就调用 close 方法来关闭它。

当 pytest 运行这个例子时,会在每个测试用例执行结束后检查文件对象是否已经关闭,并在需要时关闭它。

总之,pytest_runtest_teardown 可以用于自定义 pytest 的测试执行结束后的清理操作,例如关闭文件、断开网络连接等。

pytest_runtest_makereport

pytest_runtest_makereport 是 pytest 的一个钩子函数,它在每个测试用例(或 fixture)执行结束后被调用,可以用于自定义测试结果的处理。

具体来说,当 pytest 执行测试用例时,会先调用测试用例关联的 fixture 的 setup 方法,然后执行测试用例函数本身。在测试用例函数执行结束后,pytest 会调用测试用例关联的 fixture 的 teardown 方法以清理测试环境,并在这个过程中调用 pytest_runtest_makereport 函数,以便我们能够对测试结果进行自定义处理。

pytest_runtest_makereport 函数中,我们可以使用 itemcallreport 这三个参数来获取当前测试用例的一些信息,例如测试用例的名称、参数、执行状态等信息,并根据需要对测试结果进行自定义处理。

一般来说,pytest_runtest_makereport 函数经常被用于测试结果的统计、分析和记录,例如统计测试通过率、记录测试结果到数据库等操作。下面是一个示例,演示了如何在 pytest_runtest_makereport 函数中记录测试结果:

def pytest_runtest_makereport(item, call, report):
    # 如果测试用例执行时出错,就记录错误信息到日志中
    if report.failed:
        logger.error(f"Test case {item.nodeid} failed with {report.longrepr}")
    # 记录测试结果到数据库中
    record_test_result(item, call, report)

在上面的例子中,我们首先判断当前测试用例是否执行失败,如果是,就使用日志记录错误信息。然后,我们调用 record_test_result 函数将测试结果记录到数据库中。

当 pytest 运行这个例子时,会在测试执行过程中记录测试结果,并将测试结果保存到指定的数据库中。

总之,pytest_runtest_makereport 可以用于自定义 pytest 的测试结果处理操作,例如记录测试结果、统计测试通过率等。

pytest_pyfunc_call

pytest_pyfunc_call 是 pytest 的一个钩子函数,它会在执行测试用例或 fixture 函数之前被调用,可以用于自定义测试用例或 fixture 的参数解析和执行。

一般来说,pytest 会根据测试用例或 fixture 函数的参数注解(Annotations)来解析参数,并将解析后的参数传递给测试用例或 fixture 函数。但是,在有些情况下,我们可能需要对参数进行自定义处理,例如根据参数值不同执行不同的逻辑,或者从外部源获取参数等。这时就可以使用 pytest_pyfunc_call 钩子函数来实现自定义参数解析和执行。

pytest_pyfunc_call 函数中,我们可以使用 pyfuncitemargs 这两个参数来获取当前需要执行的测试用例或 fixture 的相关信息和参数值。然后,根据需要对参数进行自定义处理,最后使用 pyfuncitem 执行测试用例或 fixture 函数即可。

下面是一个示例,演示了如何在 pytest_pyfunc_call 函数中对参数进行自定义处理:

def pytest_pyfunc_call(pyfuncitem):
    # 获取测试用例或 fixture 函数相关信息和参数值
    testargs = pyfuncitem._make_testargs()
    args, kwargs = testargs.args, testargs.kwargs

    # 对参数进行自定义处理
    if "my_param" in kwargs:
        if kwargs["my_param"] == "foo":
            my_func(args[0], "foo")
        elif kwargs["my_param"] == "bar":
            my_func(args[0], "bar")

    # 执行测试用例或 fixture 函数
    pyfuncitem.obj(*args, **kwargs)

在上面的例子中,我们首先使用 pyfuncitem_make_testargs() 方法获取当前需要执行的测试用例或 fixture 的相关信息和参数值。然后,我们判断参数中是否包含名为 my_param 的参数,并根据 my_param 参数值的不同执行不同的逻辑。

最后,我们使用 pyfuncitem.obj() 方法执行测试用例或 fixture 函数。

当 pytest 运行这个例子时,会在执行测试用例或 fixture 函数之前对参数进行自定义处理,并根据参数值不同执行不同的逻辑。

总之,pytest_pyfunc_call 钩子函数可以用于自定义 pytest 的测试用例或 fixture 的参数解析和执行操作,例如根据参数值不同执行不同的逻辑、从外部源获取参数等。

@pytest.hookimpl()装饰器

@pytest.hookimpl() 是 pytest 中的一个装饰器,用于定义 pytest 钩子函数(hook function)。它支持多个参数,以实现自定义 pytest 行为的目的。

下面是 @pytest.hookimpl() 可以接受的参数列表及其含义:

  • trylast:指定钩子函数的优先级是否最低,默认为 False。

  • tryfirst:指定钩子函数的优先级是否最高,默认为 False。

  • optionalhook:指定钩子函数是否可选,即在 pytest 插件中是否必须定义该钩子函数,默认为 False。

  • hookwrapper:指定钩子函数是否是一个包装器函数(wrapper function),即在原始钩子函数执行前后添加一些附加操作,默认为 False。

  • firstresult:指定钩子函数返回值是否应该是第一个非 None 的值,如果为 True,则只返回第一个非 None 的值。默认为 False。

  • historic:指定钩子函数是否可以被记录在历史记录中,默认为 False。

  • warn_on_impl:指定是否在运行 pytest 时警告用户,如果发现其他插件也定义了相同名称、相同钩子函数的实现,默认为 False。

  • hookrel:指定钩子函数在插件之间的相对关系,默认为 0。在同一插件中定义的钩子函数没有定义顺序,但在不同插件中,可以使用该参数定义相对执行顺序。

  • name:指定钩子函数的名称,默认为当前函数名。

  • spec:指定钩子函数的签名(Signature)对象,用于检查钩子函数的参数和返回值类型。默认为 None。

  • project_name:指定项目名称,用于标识该插件所属的项目,默认为 None。

在使用 @pytest.hookimpl() 装饰器时,通常会指定其中一些参数,以实现自定义 pytest 行为的目的。例如,可以指定 trylast=True 选项将钩子函数的优先级设为最低,或者指定 hookwrapper=True 选项将钩子函数包装成一个包装器函数,以添加一些额外的操作。

总之,@pytest.hookimpl() 装饰器支持多个参数,可用于定义 pytest 钩子函数,并指定一些选项,以实现自定义 pytest 行为的目的。不同的参数组合可以实现不同类型和优先级的钩子函数,以满足测试过程中的各种需求。

# content of conftest.py
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """
      每个测试用例执行后,制作测试报告
      :param item:测试用例对象
      :param call:测试用例的测试步骤
               执行完常规钩子函数返回的report报告有个属性叫report.when
                先执行when=’setup’ 返回setup 的执行结果
                然后执行when=’call’ 返回call 的执行结果
                最后执行when=’teardown’返回teardown 的执行结果
      :return:
    """
    # 执行所有其他钩子以获取报告对象
    # 获取常规钩子方法的调用结果,返回一个result对象 
    outcome = yield
    # 获取调用结果的测试报告,返回一个report对象, report对象的属性包括when(steup, call, teardown三个值)、nodeid(测试用例的名字)、outcome(用例的执行结果,passed,failed)
    rep = outcome.get_result() #  # 从钩子方法的调用结果中获取测试报告
    # rep.when表示测试步骤,仅仅获取用例call 执行结果是失败的情况, 不包含 setup/teardown
    if rep.when == "call" and rep.failed:
        mode = "a" if os.path.exists("../../failures") else "w"
        with open(root_path+"failures", mode) as f:
            if "tmpdir" in item.fixturenames:
                extra = " (%s)" % item.funcargs["tmpdir"]
            else:
                extra = ""
            f.write(rep.nodeid + extra + "\n")
        with allure.step('添加失败截图...'):
            allure.attach(driver.get_screenshot_as_png(), "失败截图", allure.attachment_type.PNG)

这个函数使用了 @pytest.hookimpl() 装饰器,并指定了两个参数 tryfirst=Truehookwrapper=True。其中,tryfirst=True 表示该钩子函数的优先级最高,会在其他钩子函数之前执行;hookwrapper=True 表示该钩子函数是一个包装器函数,即在原始的钩子函数执行前后添加一些额外的操作。

具体来说,这个钩子函数 pytest_runtest_makereport 是在每个测试用例运行结束后自动调用的,它的作用是制作测试报告。该函数接受两个参数:item 表示测试用例对象,call 表示测试用例的测试步骤。在函数内部,首先使用 yield 暂停当前函数并执行其他钩子函数,然后获取其他钩子函数的调用结果,并调用 outcome.get_result() 方法获得测试报告对象 rep

接下来,如果测试用例的执行步骤为 call(即运行测试代码的阶段)且执行结果为失败,那么就将该测试用例的名称和额外信息写入文件,并使用 allure.step() 装饰器添加一个名为“添加失败截图”的步骤,同时将失败截图作为附件添加到测试报告中。

总之,这个钩子函数的作用是在每个测试用例结束后制作测试报告,并在测试用例执行失败时保存该测试用例的相关信息和截图,以便后续分析和调试。同时,使用 @pytest.hookimpl() 装饰器指定了该函数的优先级和包装器属性,以满足 pytest 测试框架的需求。