pytest–编写钩子函数
目录
一、Hook函数的定义
Hook函数又称为钩子函数,它的作用可以理解成钩住自己喜欢的东西(在window中,喜欢的东西可理解为消息),然后对自己喜欢的东西单独做处理
二、一个例子:Hook函数修改pytest-html报告
confest.py
通过为标题行实现自定义钩子函数来修改列,下面的示例在conftest.py脚本中使用测试函数docstring添加描述(Description)列,添加可排序时间(Time)列,并删除链接(Link)列:
@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
cells.insert(2,html.th('Description'))
cells.insert(1,html.th('Time', class_='sortable time', col='time'))
cells.pop()
@pytest.mark.optionalhook
def pytest_html_results_table_row(report, cells):
cells.insert(2, html.td(report.description))
cells.insert(1, html.td(datetime.utcnow(), class_='col-time'))
cells.pop()
@pytest.mark.hookwrapper #也可以用@pytest.hookimpl(hookwrapper=True) 两者作用相同
def pytest_runtest_makereport(item, call): #此钩子函数在setup(初始化的操作),call(测试用例执行时),teardown(测试用例执行完毕后的处理)都会执行一次
outcome = yield
report = outcome.get_result()
report.description = str(item.function.__doc__)
运行后查看测试报告:
三、装饰器pytest.hookimpl(hookwrapper=True)
装饰器@pytest.hookimpl(hookwrapper=True),它的作用和装饰器@pytest.mark.hookwrapper是一样的,当pytest调用钩子函数时,它首先执行钩子函数装饰器并传递与常规钩子函数相同的参数(个人理解是当用该装饰器@pytest.hookimpl(hookwrapper=True)装饰时,他会把其他常规钩子函数的参数都传递给当前被装饰的钩子函数)
在钩子函数装饰器的yield处,Pytest将执行下一个钩子函数实现,并以Result对象的形式,封装结果或异常信息的实例的形式将其结果返回到yield处。因此,yield处通常本身不会抛出异常(除非存在错误)
总结如下: @pytest.hookimpl(hookwrapper=True)装饰的钩子函数,有以下两个作用: (1)可以获取到测试用例不同执行阶段的结果(setup,call,teardown) (2)可以获取钩子方法的调用结果(yield返回一个result对象)和调用结果的测试报告(返回一个report对象)
@pytest.hookimpl(hookwrapper=True,tryfirst=True)
def pytest_runtest_makereport(item,call):
'''
:param item:测试用例对象
:param call:
测试用例的测试步骤
执行完常规钩子函数返回的report报告有个属性叫report.when
先执行when=’setup’ 返回setup 的执行结果
然后执行when=’call’ 返回call 的执行结果
最后执行when=’teardown’返回teardown 的执行结果
:return:
'''
out=yield
report=out.get_result()
print(111111111111111111)
print(out)
print(report)
print(report.when)
print(report.nodeid)
print(report.outcome)
print(111111111111111111)
运行结果:执行三次的原因是此钩子函数会在测试用例执行的不同阶段(setup, call, teardown)都会调用一次
D:\pytest\learn01>pytest -s -v -q --html=report1.html g/test_unittest.py
============================== test session starts ================================
platform win32 -- Python 3.7.3, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\pytest\learn01, configfile: pytest.ini
plugins: html-3.1.1, metadata-1.11.0
collected 1 item
g\test_unittest.py pytest fixture
111111111111111111
<pluggy.callers._Result object at 0x00000000038D95F8>
<TestReport 'g/test_unittest.py::MyTest::test_method1' when='setup' outcome='passed'>
setup
g/test_unittest.py::MyTest::test_method1
passed
111111111111111111
111111111111111111
<pluggy.callers._Result object at 0x00000000038D95F8>
<TestReport 'g/test_unittest.py::MyTest::test_method1' when='call' outcome='passed'>
call
g/test_unittest.py::MyTest::test_method1
passed
111111111111111111
.111111111111111111
<pluggy.callers._Result object at 0x0000000002D12470>
<TestReport 'g/test_unittest.py::MyTest::test_method1' when='teardown' outcome='passed'>
teardown
g/test_unittest.py::MyTest::test_method1
passed
111111111111111111
---------------- generated html file: file://D:\pytest\learn01\report1.html -------------------
================= 1 passed in 0.04s ===============
四、Hook函数排序/调用示例
对于任何给定的钩子函数规格,可能存在多个实现,因此我们通常将钩子函数执行视为1:N的函数调用,其中N是已注册函数的数量。有一些方法可以影响钩子函数实现是在其他之前还是之后,即在N-sized函数列表中的位置:
@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(items):
# will execute as early as possible
print('execute Plugin 1')
# Plugin 2
@pytest.hookimpl(trylast=True)
def pytest_collection_modifyitems(items):
# will execute as late as possible
print('execute Plugin 2')
# Plugin 3
@pytest.hookimpl(hookwrapper=True)
def pytest_collection_modifyitems(items):
# will execute even before the tryfirst one above!
print('execute Plugin 3 before yield ')
outcome = yield
print('execute Plugin 3 after yield ')
这是执行的顺序:
1、Plugin3的pytest_collection_modifyitems被调用直到注入点,因为它是一个钩子函数装饰器。 2、调用Plugin1的pytest_collection_modifyitems是因为它标有tryfirst=True。 3、调用Plugin2的pytest_collection_modifyitems因为它被标记trylast=True(但即使没有这个标记,它也会在Plugin1之后出现)。 4、插件3的pytest_collection_modifyitems,在注入点之后执行代码。yield接收一个Result实例,该实例封装了调用非装饰器的结果。包装不得修改结果。 以上使用tryfirst,trylast,以及结合hookwrapper=True的示例,它会影响彼此之间hookwrappers的排序 上述内容为官方文档,但是在运行时,实际上只调用了Plugin3,按官方文档应该是都调用,只是调用顺序不一样???
五、钩子函数总结
1、第一部分:setuptools
引导挂钩要求足够早注册的插件(内部和setuptools插件),可以使用的钩子
- pytest_load_initial_conftests(early_config,parser,args): 在命令行选项解析之前实现初始conftest文件的加载。
- pytest_cmdline_preparse(config,args): (不建议使用)在选项解析之前修改命令行参数。
- pytest_cmdline_parse(pluginmanager,args): 返回一个初始化的配置对象,解析指定的args。
- pytest_cmdline_main(config): 要求执行主命令行动作。默认实现将调用configure hooks和runtest_mainloop。
2、第二部分: 初始化挂钩
初始化钩子需要插件和conftest.py文件
- pytest_addoption(parser): 注册argparse样式的选项和ini样式的配置值,这些值在测试运行开始时被调用一次。
- pytest_addhooks(pluginmanager): 在插件注册时调用,以允许通过调用来添加新的挂钩
- pytest_configure(config): 许插件和conftest文件执行初始配置。
- pytest_unconfigure(config): 在退出测试过程之前调用。
- pytest_sessionstart(session): 在Session创建对象之后,执行收集并进入运行测试循环之前调用。
- pytest_sessionfinish(session,exitstatus): 在整个测试运行完成后调用,就在将退出状态返回系统之前。
- pytest_plugin_registered(plugin,manager):一个新的pytest插件已注册。
3、第三部分: collection 收集钩子
- pytest_collection(session): 执行给定会话的收集协议。
- pytest_collect_directory(path, parent): 在遍历目录以获取集合文件之前调用。
- pytest_collect_file(path, parent) 为给定的路径创建一个收集器,如果不相关,则创建“无”。
- pytest_pycollect_makemodule(path: py._path.local.LocalPath, parent) 返回给定路径的模块收集器或无。
- pytest_pycollect_makeitem(collector: PyCollector, name: str, obj: object) 返回模块中Python对象的自定义项目/收集器,或者返回None。在第一个非无结果处停止
- pytest_generate_tests(metafunc: Metafunc) 生成(多个)对测试函数的参数化调用。
- pytest_make_parametrize_id(config: Config, val: object, argname: str) 返回val 将由@ pytest.mark.parametrize调用使用的给定用户友好的字符串表示形式,如果挂钩不知道,则返回None val。
- pytest_collection_modifyitems(session: Session, config: Config, items: List[Item]) 在执行收集后调用。可能会就地过滤或重新排序项目。
- pytest_collection_finish(session: Session) 在执行并修改收集后调用。
4、第四部分:测试运行(runtest)钩子
- pytest_runtestloop(session: Session) 执行主运行测试循环(收集完成后)。
- pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) 对单个测试项目执行运行测试协议。
- pytest_runtest_logstart(nodeid: str, location: Tuple[str, Optional[int], str]) 在运行单个项目的运行测试协议开始时调用。
- pytest_runtest_logfinish(nodeid: str, location: Tuple[str, Optional[int], str])在为单个项目运行测试协议结束时调用。
- pytest_runtest_setup(item: Item) 调用以执行测试项目的设置阶段。
- pytest_runtest_call(item: Item) 调用以运行测试项目的测试(调用阶段)。
- pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) 调用以执行测试项目的拆卸阶段。
- pytest_runtest_makereport(item: Item, call: CallInfo[None]) 被称为为_pytest.reports.TestReport测试项目的每个设置,调用和拆卸运行测试阶段创建一个。
- pytest_pyfunc_call(pyfuncitem: Function) 调用基础测试功能。
5、第五部分:Reporting 报告钩子
- pytest_collectstart(collector: Collector) 收集器开始收集。
- pytest_make_collect_report(collector: Collector) 执行collector.collect()并返回一个CollectReport。
- pytest_itemcollected(item: Item) 我们刚刚收集了一个测试项目。
- pytest_collectreport(report: CollectReport) 收集器完成收集。
- pytest_deselected(items: Sequence[Item]) 要求取消选择的测试项目,例如按关键字。
- pytest_report_header(config: Config, startdir: py._path.local.LocalPath) 返回要显示为标题信息的字符串或字符串列表,以进行终端报告。
- pytest_report_collectionfinish(config: Config, startdir: py._path.local.LocalPath, items: Sequence[Item]) 返回成功完成收集后将显示的字符串或字符串列表。
- pytest_report_teststatus(report: Union[CollectReport, TestReport], config: Config) 返回结果类别,简写形式和详细词以进行状态报告。
- pytest_terminal_summary(terminalreporter: TerminalReporter, exitstatus: ExitCode, config: Config) 在终端摘要报告中添加一个部分。
- pytest_fixture_setup(fixturedef: FixtureDef[Any], request: SubRequest) 执行夹具设置执行。
- pytest_fixture_post_finalizer(fixturedef: FixtureDef[Any], request: SubRequest) 在夹具拆除之后但在清除缓存之前调用,因此夹具结果fixturedef.cached_result仍然可用(不是 None)
- pytest_warning_captured(warning_message: warnings.WarningMessage, when: Literal[‘config’, ‘collect’, ‘runtest’], item: Optional[Item], location: Optional[Tuple[str, int, str]]) (已弃用)处理内部pytest警告插件捕获的警告。
- pytest_warning_recorded(warning_message: warnings.WarningMessage, when: Literal[‘config’, ‘collect’, ‘runtest’], nodeid: str, location: Optional[Tuple[str, int, str]]) 处理内部pytest警告插件捕获的警告。
- pytest_runtest_logreport(report: TestReport) 处理项目的_pytest.reports.TestReport每个设置,调用和拆卸运行测试阶段产生的结果。
- pytest_assertrepr_compare(config: Config, op: str, left: object, right: object) 返回失败断言表达式中的比较的说明。
- pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) (实验性的)在断言通过时调用。
6、第六部分:调试/相互作用钩
很少有可以用于特殊报告或与异常交互的挂钩:
- pytest_internalerror(excrepr: ExceptionRepr, excinfo: ExceptionInfo[BaseException]) 要求内部错误。返回True以禁止对将INTERNALERROR消息直接打印到sys.stderr的回退处理。
- pytest_keyboard_interrupt(excinfo: ExceptionInfo[Union[KeyboardInterrupt, Exit]]) 要求键盘中断。
- pytest_exception_interact(node: Union[Item, Collector], call: CallInfo[Any], report: Union[CollectReport, TestReport]) 在引发可能可以交互处理的异常时调用。
- pytest_enter_pdb(config: Config, pdb: pdb.Pdb) 调用了pdb.set_trace()。
参考文献:https://blog.csdn.net/u011035397/article/details/109546814