pytest的钩子函数简述
什么是钩子函数
在 pytest 中,钩子函数(hook function)是指一种特殊的函数,它们可以被 pytest 插件或 initial conftest 文件中定义的一系列函数所调用,用于扩展或修改 pytest 的行为。pytest 钩子函数的命名约定是以 pytest_
作为前缀的 Python 函数。
通过这些钩子我们可以对pytest 用例收集、用例执行、报告输出等各个阶段进行干预,根据需求去开发对应的插件,以满足自己的使用场景。
钩子函数的分类
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 函数接受两个参数 session
和 config
,其中 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_data
和 expected_output
分别表示输入和期望的输出数据,三个元素分别对应三个测试用例。
当 pytest 运行这些测试项目时,将会执行每个测试项目,并为这些测试项目添加了我们定义的自定义标记和参数化信息。
总之,pytest_collection_modifyitems
是非常常用的一个 hook 函数,通过它我们可以对 pytest 收集到的测试项目进行进一步的修改和调整,以达到更好的测试效果。
pytest_collection_finish
参数:session: pytest会话对象
触发时机:在收集完用例和修改收用例集之后调用
测试运行钩子
pytest_runtestloop
pytest_runtestloop
是 pytest 的一个 hook 函数,它在测试项目运行期间被多次调用,每次调用都表示一轮测试执行。
该 hook 函数接受三个参数 session
、config
和 items
,其中 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_protocol
和pytest_runtestloop
都是 pytest 提供的钩子函数,用于在测试执行期间进行自定义操作。不同之处在于它们的作用和调用时机。
pytest_runtest_protocol
主要是在每个测试用例(或 fixture)执行前后被调用,可以使用它来修改测试用例及其参数、添加或修改全局变量等。通常,我们在pytest_runtest_protocol
中使用item
参数来获取关于当前测试用例的详细信息,例如名称、标记、参数等。在处理完这些信息后,我们可以返回一个可选的nextitem
参数来控制接下来需要执行的测试用例。相比之下,
pytest_runtestloop
则是在测试集开始执行前和结束执行后被调用,可以用于修改测试配置、设置测试环境、检查测试报告等。通常,我们在pytest_runtestloop
中使用四个参数session
、config
、items
和nextitem
,分别表示 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
函数中,我们可以使用 item
、call
和 report
这三个参数来获取当前测试用例的一些信息,例如测试用例的名称、参数、执行状态等信息,并根据需要对测试结果进行自定义处理。
一般来说,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
函数中,我们可以使用 pyfuncitem
和 args
这两个参数来获取当前需要执行的测试用例或 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=True
和 hookwrapper=True
。其中,tryfirst=True
表示该钩子函数的优先级最高,会在其他钩子函数之前执行;hookwrapper=True
表示该钩子函数是一个包装器函数,即在原始的钩子函数执行前后添加一些额外的操作。
具体来说,这个钩子函数 pytest_runtest_makereport
是在每个测试用例运行结束后自动调用的,它的作用是制作测试报告。该函数接受两个参数:item
表示测试用例对象,call
表示测试用例的测试步骤。在函数内部,首先使用 yield
暂停当前函数并执行其他钩子函数,然后获取其他钩子函数的调用结果,并调用 outcome.get_result()
方法获得测试报告对象 rep
。
接下来,如果测试用例的执行步骤为 call
(即运行测试代码的阶段)且执行结果为失败,那么就将该测试用例的名称和额外信息写入文件,并使用 allure.step()
装饰器添加一个名为“添加失败截图”的步骤,同时将失败截图作为附件添加到测试报告中。
总之,这个钩子函数的作用是在每个测试用例结束后制作测试报告,并在测试用例执行失败时保存该测试用例的相关信息和截图,以便后续分析和调试。同时,使用 @pytest.hookimpl()
装饰器指定了该函数的优先级和包装器属性,以满足 pytest 测试框架的需求。
评论