小门板儿

Menu

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插件),可以使用的钩子

2、第二部分: 初始化挂钩

初始化钩子需要插件和conftest.py文件

3、第三部分: collection 收集钩子

4、第四部分:测试运行(runtest)钩子

5、第五部分:Reporting 报告钩子

6、第六部分:调试/相互作用钩

很少有可以用于特殊报告或与异常交互的挂钩:

参考文献:https://blog.csdn.net/u011035397/article/details/109546814

— 于 共写了8393个字
— 标签:

评论已关闭。