Pytest 的钩子函数 (Hook Functions):定制你的测试流程 (Pytest 系列之五)
在前几篇文章中,我们已经学习了 Pytest 的基本语法、强大的断言、灵活的 Fixture 以及高效的参数化测试。今天,我们将深入探索 Pytest 的另一个高级特性——钩子函数 (Hook Functions)。钩子函数是 Pytest 框架在执行测试的不同阶段提供的“拦截点”,允许我们在测试流程中插入自定义的逻辑,从而实现对 Pytest 行为的精细控制和扩展。
回顾:为测试注入灵魂
正如我们在之前的文章中所见,Pytest 提供了诸多特性来简化和增强我们的测试工作。而钩子函数则更像是在 Pytest 引擎内部预留的接口,我们可以在特定的时机“挂载”我们自定义的代码,从而改变 Pytest 的默认行为,满足更复杂的测试需求。
什么是钩子函数?在 Pytest 的生命周期中“挂载”你的代码
Pytest 的执行流程可以划分为多个阶段,例如:测试用例的收集、测试的执行、测试报告的生成等等。在这些关键阶段,Pytest 会调用一些预定义的钩子函数。我们可以在我们的测试代码 (通常在 conftest.py
文件中) 中定义与这些钩子函数同名的函数,Pytest 会自动识别并执行它们。通过实现这些钩子函数,我们可以自定义 Pytest 的行为,例如:
- 自定义测试用例的收集方式。
- 在每个测试用例执行前后进行额外的 setup 和 teardown 操作。
- 修改测试报告的输出格式和内容。
- 实现自定义的测试结果处理逻辑。
常见的钩子函数及其功能
Pytest 提供了非常丰富的钩子函数,涵盖了测试流程的各个方面。以下是一些常用的钩子函数及其功能:
- 测试用例收集相关的钩子:
pytest_collect_file(path, parent)
: 在遍历测试文件时被调用。我们可以使用它来指定哪些文件应该被视为测试文件。pytest_collect_item(item, parent)
: 在收集到一个测试项 (例如一个测试函数或测试类) 时被调用。我们可以修改测试项的属性或跳过某些测试项。
- 测试执行相关的钩子:
pytest_runtest_setup(item)
: 在执行每个测试用例的 setup 阶段之前被调用。我们可以在这里进行一些额外的准备工作。pytest_runtest_call(item)
: 用于实际执行测试用例。通常我们不需要重写这个钩子,除非需要完全自定义测试用例的执行方式。pytest_runtest_teardown(item, nextitem)
: 在执行每个测试用例的 teardown 阶段之后被调用。我们可以在这里进行一些额外的清理工作。
- 会话管理相关的钩子:
pytest_sessionstart(session)
: 在整个测试会话开始时被调用。我们可以在这里进行一些全局的初始化操作。pytest_sessionfinish(session, exitstatus)
: 在整个测试会话结束后被调用。我们可以在这里进行一些全局的清理操作或生成最终报告。
- 报告相关的钩子:
pytest_terminal_summary(terminalreporter, exitstatus, config)
: 在终端报告生成结束后被调用。我们可以修改终端报告的输出内容。pytest_html_report_title(title)
: 如果使用了pytest-html
插件,这个钩子可以用于自定义 HTML 报告的标题.
- 其他常用钩子:
pytest_addoption(parser)
: 用于向命令行添加自定义选项。pytest_configure(config)
: 在 Pytest 完成初始配置后被调用。我们可以在这里进行一些全局的配置修改。pytest_exception_interact(node, call, report)
: 在交互模式下 (-x
或--maxfail
) 发生异常时被调用。pytest_report_header(config)
: 在终端报告的头部添加自定义信息.
编写和使用钩子函数:在 conftest.py
中定义
钩子函数通常在 conftest.py
文件中定义。conftest.py
是一个特殊的 Pytest 文件,Pytest 会自动识别并加载其中的 Fixture 和钩子函数。你可以将 conftest.py
放在你的测试目录结构中的任何位置,它会对该目录及其子目录下的测试生效。
示例 1:自定义命令行选项pytest_addoption
pytest_addoption(),允许用户自定义注册一个命令行参数,方便用户通过命令行参数的形式个Pytest传递不同的参数进行不同测试场景的切换;
pytest_addoption(),钩子函数一般和内置fixture pytestconfig一起配合使用,pytestconfig 通过pytest配置对象读取参数的值。
还可以与内置fixture函数request中 request.config.getoption 结合使用来读取用户注册的自定义命令行参数对应的参数值。
首先查看源码
def addoption(self, *opts: str, **attrs: Any) -> None:"""Register a command line option.:param opts:Option names, can be short or long options.:param attrs:# 与 argparse 库的 :meth:'add_argument() 相同的属性Same attributes as the argparse library's :meth:`add_argument()<argparse.ArgumentParser.add_argument>` function accepts.After command line parsing, options are available on the pytest configobject via ``config.option.NAME`` where ``NAME`` is usually setby passing a ``dest`` attribute, for example``addoption("--long", dest="NAME", ...)``."""self._anonymous.addoption(*opts, **attrs)
那么让我们去官方文档看一下,argparse 库的 :add_argument()的用法
ArgumentParser.add_argument(name or flags..., *[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest][, deprecated])
定义单个的命令行参数应当如何解析。每个形参都在下面有它自己更多的描述,长话短说有:name or flags - 一个名称或是由选项字符串组成的列表,例如 'foo' 或 '-f', '--foo'。action - 当参数在命令行中出现时使用的动作基本类型。nargs - 命令行参数应当消耗的数目。const - 被一些 action 和 nargs 选择所需求的常数。default - 当参数未在命令行中出现并且也不存在于命名空间对象时所产生的值。type - 命令行参数应当被转换成的类型。choices - 由允许作为参数的值组成的序列。required - 此命令行选项是否可省略 (仅选项可用)。help - 一个此选项作用的简单描述。metavar - 在使用方法消息中使用的参数值示例。dest - 被添加到 parse_args() 所返回对象上的属性名。deprecated - 参数的使用是否已被弃用。
- 首先,在你的测试项目根目录下创建一个
conftest.py
文件,并添加以下内容:
# conftest.py
import pytestdef pytest_addoption(parser):""":param parser:name or flags - 自定义命令行参数的名字,例如 'foo' 或 '-f', '--foo'。action - 指明应该如何处理一个参数'store', 'store_const', 'store_true', 'append', 'append_const', 'count', 'help', 'version'nargs - 参数可以使用的次数。const - 参数用于保存不从命令行中读取但被各种 ArgumentParser 动作需求的常数值default - 默认值为 Nonetype - 命令行参数应当被转换成的类型。int, float, argparse.FileType('w') 或可调用函数choices - 命令行参数应当从一组受限的值中选择。required - 此命令行选项是否可省略 ;T必选、F省略help - 一个此选项作用的简单描述。metavar - 在使用help方法消息中使用的参数值。dest - 被添加到 parse_args() 所返回对象上的属性名。deprecated - 指明参数已被弃用并将在未来被移除,其默认值为 False:return:"""parser.addoption(#添加命令行参数--env,用来表示允许环境"--env",action="store",default="env",# 默认值choices = ["test","dev","auto"],help = "选择运行环境")@pytest.fixture(scope="session")
def get_env(request):"""获取命令行参数--env的值:param request::return: name"""name = request.config.getoption("--env")return name
pytest_addoption
钩子函数向 Pytest 的命令行解析器添加了一个名为 --env
的选项。
get_env
fixture在从 request.config.getoption
中获取我们通过命令行传递的 --env
选项的值,并打印出来。
- 然后再用例文件,编写测试类
class TestRunAddoption:def test_add_option(self, get_env):print(f"当前代码运行环境为:{get_env}")
- 执行以下命令
pytest -sv -k "test_add_option"
;输出内容如下;因为没有传入–env参数,所以读取到的是默认是env
============================================== test session starts ==============================================
platform win32 -- Python 3.13.2, pytest-8.3.5, pluggy-1.5.0 -- D:\Code_Study\Python_Pytest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\Code_Study\Python_Pytest
configfile: pytest.ini
collected 4 items / 3 deselected / 1 selected 06_hook_test.py::TestRunAddoption::test_add_option 当前代码运行环境为:env
PASSED======================================== 1 passed, 3 deselected in 0.01s ========================================
- 下面让我们添加–env参数,执行以下命令
pytest -sv -k "test_add_option" --env test
输入内容如下;这个时候我们传入了test,所以读取到的就是test
============================================== test session starts ==============================================
platform win32 -- Python 3.13.2, pytest-8.3.5, pluggy-1.5.0 -- D:\Code_Study\Python_Pytest\.venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\Code_Study\Python_Pytest
configfile: pytest.ini
collected 4 items / 3 deselected / 1 selected 06_hook_test.py::TestRunAddoption::test_add_option 当前代码运行环境为:test
PASSED======================================== 1 passed, 3 deselected in 0.01s ========================================
- 如果我们传入的参数,不在choices列表里,则会报错
执行以下命令pytest -sv -k "test_add_option" --env uat
(.venv) PS D:\Code_Study\Python_Pytest> pytest -sv -k "test_add_option" --env uat
ERROR: usage: pytest [options] [file_or_dir] [file_or_dir] [...]
pytest: error: argument --env: invalid choice: 'uat' (choose from test, dev, auto)
示例 2:自定义终端报告的头部信息
修改 conftest.py
文件,添加以下钩子函数:
# conftest.py
import pytestdef pytest_report_header(config):return ["Environment: Testing", "Python Version: 3.13"]
当你运行 Pytest 测试时,你会看到终端报告的头部包含了我们自定义的环境信息和 Python 版本。
执行以下命令pytest -sv -k "test_add_option" --env test
输入内容如下
============================================== test session starts ==============================================
platform win32 -- Python 3.13.2, pytest-8.3.5, pluggy-1.5.0 -- D:\Code_Study\Python_Pytest\.venv\Scripts\python.exe
cachedir: .pytest_cache
# 可以看到添加的钩子函数,会在这里输出
Environment: Testing
Python Version: 3.13rootdir: D:\Code_Study\Python_Pytest
configfile: pytest.ini
collected 4 items / 3 deselected / 1 selected 06_hook_test.py::TestRunAddoption::test_add_option 当前代码运行环境为:test
PASSED======================================== 1 passed, 3 deselected in 0.01s ========================================
示例 3:在测试会话结束后添加自定义总结信息
修改 conftest.py
文件,添加以下钩子函数:
# conftest.py
import pytestdef pytest_sessionfinish(session):print("\n完美撒花! 所有的用例都通过passed.")
这个 pytest_sessionfinish
钩子函数在整个测试会话结束后被调用,并根据测试的退出状态打印自定义的总结信息。
执行以下命令pytest -sv -k "test_add_option" --env test
输出内容如下
============================================== test session starts ==============================================
platform win32 -- Python 3.13.2, pytest-8.3.5, pluggy-1.5.0 -- D:\Code_Study\Python_Pytest\.venv\Scripts\python.exe
cachedir: .pytest_cache
Environment: Testing
Python Version: 3.13
rootdir: D:\Code_Study\Python_Pytest
configfile: pytest.ini
collected 4 items / 3 deselected / 1 selected 06_hook_test.py::TestRunAddoption::test_add_option 当前代码运行环境为:test
PASSED
完美撒花! 所有的用例都通过passed.======================================== 1 passed, 3 deselected in 0.01s ========================================
钩子函数的命名约定和参数
- 钩子函数的命名总是以
pytest_
开头。 - 钩子函数的名称和参数是 Pytest 预定义的,你需要遵循官方文档的说明来定义你的钩子函数。
- Pytest 会根据钩子函数的名称和参数类型来自动调用它们,并将相关的信息作为参数传递给你的钩子函数。
总结
Pytest 的钩子函数为我们提供了强大的能力来定制和扩展 Pytest 的默认行为。通过在 conftest.py
文件中实现不同的钩子函数,我们可以精细地控制测试流程的各个环节,满足各种复杂的测试需求,例如自定义命令行选项、修改测试报告的输出等等。掌握钩子函数的使用,将使你能够更深入地理解和利用 Pytest 框架,构建更加灵活和强大的自动化测试解决方案。
在下一篇文章中,我们将学习如何使用 Pytest 插件来进一步扩展 Pytest 的功能,利用社区已经开发好的工具来提升我们的测试效率和报告质量。请继续关注!