在pytest中钩子函数(hook functions)是用来自定义和扩展 pytest 的功能的关键机制。这些钩子函数可以在 pytest 的配置文件中或者插件中实现,用于干预测试执行的不同阶段和行为,这篇文章主要用来记录他不同钩子函数的使用
我们在开发自动化测试平台的时候其实可以借助于钩子函数来调用pytest测试脚本
首先简单定义的我们的测试函数如下test_demo.py:
def test_one():
a = 100
assert a < 100
def test_two():
a = 100
assert a == 100
def test_three():
a = 200
assert a > 100
上面就是我们的测试函数了,那我们一切以测试函数为准
我们钩子函数,我们都在pytest这个项目目录的根目录下创建我们的conftest.py文件,这样执行pytest测试的时候会自动触发我们这个文件中钩子函数
钩子函数之:pytest_collection_modifyitems(session, config, items)
1.用于修改或者重新排序收集到的测试项(test items),在items这个参数中
2.可以通过这个钩子来动态地过滤测试用例或者改变它们的执行顺序
下面是我们定义的函数:
def pytest_collection_modifyitems(session, config, items):
"""
:param session:当前 pytest 会话对象
:param config:当前的配置对象
:param items:包含所有已收集测试项的列表
:return:
"""
print("Modifying collected items...")
print(items)
for i in items:
print(i.name)
当我们使用 pytest .\test_demo.py 命令行启动我们测试的时候,我们可以看到如下打印:
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.10.5, pytest-8.3.1, pluggy-1.5.0
rootdir: C:\Users\w\Plants\pytest_demo
collecting ... Modifying collected items...
[<Function test_one>, <Function test_two>, <Function test_three>]
test_one
test_two
test_three
collected 3 itemstest_demo.py F.. [100%]
=============================================================================== FAILURES ===============================================================================
_______________________________________________________________________________ test_one _______________________________________________________________________________def test_one():
a = 100
> assert a < 100
E assert 100 < 100test_demo.py:3: AssertionError
======================================================================= short test summary info ========================================================================
FAILED test_demo.py::test_one - assert 100 < 100
===================================================================== 1 failed, 2 passed in 0.04s ======================================================================
我们可以直接看到在test session starts 之后这里直接触发了我们的Modifying collected items...,证明在测试执行直接,会直接触发这个钩子函数,我们打印的参数
items:[<Function test_one>, <Function test_two>, <Function test_three>] 打印出来所有收集到的函数,然后直接将函数全部放到一个列表中
我们用for循环去一个个循环打印,会发现,这些函数其实是能点出来函数名的,
test_one,test_two,test_three按照收集的顺序打印出来,他执行也是这个列表的顺序
那么我们就可以在这个里面进行一些操作
改变收集到的测试case,去掉某些case执行:
def pytest_collection_modifyitems(session, config, items):
"""
:param session:当前 pytest 会话对象
:param config:当前的配置对象
:param items:包含所有已收集测试项的列表
:return:
"""
print("Modifying collected items...")
print(items)
new_items = []
for i in items:
if i.name != 'test_one':
new_items.append(i)
print('select case list is {}'.format(new_items))
items[:] = new_items
在这个代码中,我们将收集的case内容发生了改变,这样就会执行我们想要执行的特定case
C:\Users\w\Plants\pytest_demo> pytest .\test_demo.py
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.10.5, pytest-8.3.1, pluggy-1.5.0
rootdir: C:\Users\w\Plants\pytest_demo
collecting ... Modifying collected items...
[<Function test_one>, <Function test_two>, <Function test_three>]
select case list is [<Function test_two>, <Function test_three>]
collected 3 itemstest_demo.py .. [100%]
========================================================================== 2 passed in 0.01s ===========================================================================
在上面命令输出中,我们可以明显的看到,自动收集到的是3个case,但是因为我们中间做出了改变,最后实际执行的只有2个case,这两个才是我们真正想要执行的
在我们实际项目中,会存在case的自由组合执行,那么这个时候可能平台下发的case列表就可以做这样的操作,如下,我的case列表是最终写入在了一个json文件中,那么可以做这样的更改
test_case.json
{ "test_case_list": [ "test_one", "test_three" ] }
那么在我的钩子函数中,我进行这样的修改:
def pytest_collection_modifyitems(session, config, items):
"""
:param session:当前 pytest 会话对象
:param config:当前的配置对象
:param items:包含所有已收集测试项的列表
:return:
"""
print("Modifying collected items...")
with open(r'test_case.json', 'r', encoding='utf8') as f:
test_case_dict = json.load(f)
test_case_list = test_case_dict['test_case_list']
new_items = [item for item in items if item.name in test_case_list]
print('select case list is {}'.format(new_items))
items[:] = new_items
现在执行的打印就变成了
pytest .\test_demo.py -v
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.10.5, pytest-8.3.1, pluggy-1.5.0 -- D:\Virtualenvs\Plants\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\w\Plants\pytest_demo
collecting ... Modifying collected items...
select case list is [<Function test_one>, <Function test_three>]
collected 3 itemstest_demo.py::test_one FAILED [ 50%]
test_demo.py::test_three PASSED [100%]=============================================================================== FAILURES ===============================================================================
_______________________________________________________________________________ test_one _______________________________________________________________________________def test_one():
a = 100
> assert a < 100
E assert 100 < 100test_demo.py:3: AssertionError
======================================================================= short test summary info ========================================================================
FAILED test_demo.py::test_one - assert 100 < 100
===================================================================== 1 failed, 1 passed in 0.03s ======================================================================
我们可以看到收集的还是3个case,但是我们直接用json文件中的test_case_list的case,这样最终执行的其实是我们想要执行的test_one跟test_three,这样就可以动态去改变执行case,实现从平台下发的case自由组合
其他钩子函数
暂时还没有实际用到,记录一下:
pytest_configure(config):
- 当 pytest 开始执行时调用。
- 允许插件或者配置文件在整个测试运行之前执行一些初始化操作。
pytest_unconfigure(config):
- 当 pytest 执行完成后调用。
- 可以用于执行一些清理操作或者收尾工作。
pytest_addoption(parser, pluginmanager):
- 用于添加额外的命令行选项。
- 允许插件向 pytest 添加自定义的命令行选项,以控制测试运行的行为。
pytest_generate_tests(metafunc):
- 在测试收集阶段动态生成测试参数。
- 可以根据需要动态生成测试参数,例如从外部数据源加载测试数据并生成测试用例。
pytest_runtest_setup(item):
- 在每个测试用例执行 setup 阶段之前调用。
- 允许执行一些预备操作或者设置步骤,例如创建临时资源或者初始化测试环境。
pytest_runtest_call(item):
- 在每个测试用例执行测试函数时调用。
- 可以在这里实现对测试用例执行过程的监控或者记录。
pytest_runtest_teardown(item):
- 在每个测试用例执行 teardown 阶段之后调用。
- 可以用于清理测试用例执行过程中创建的资源或者状态恢复。
pytest_sessionfinish(session, exitstatus):
- 在整个测试会话结束时调用。
- 允许进行最终的报告生成、日志记录或者资源释放等操作。
有个有意思的钩子函数:
def pytest_sessionfinish(session, exitstatus):
"""
:param session: 当前测试会话对象
:param exitstatus: 测试退出码
:return:
"""
print(exitstatus)
# 如果我们全部都执行成功了这exitstatus就是0
# 如果有FAILED 这个exitstatus 就是ExitCode.TESTS_FAILED 代表有执行失败的case
可以按照这个退出码来判断我们case是不是执行成功了,如果这个exitstatus不为0,那么就代表这次测试肯定是有失败的,那么我们之间按照这个给平台发送一个失败的通知,这样不会从日志中去判断获取了