pytest学习-Hooks函数获取用例执行结果
前言:
pytest提供的很多钩子(Hooks)方法,方便我们对测试用例框架进行二次开发,可以根据自己的需求进行改造。Hook函数又称为钩子函数,它的作用可以理解为勾住自己喜欢的东西,然后对自己喜欢的东西作单独处理。
我写的这段代码包含一个回调函数,当我有喜欢的消息发出时,这个回调函数就会执行,所以说,钩子就是指的回调函数。
先学习一下pytest_runtest_makereport这个钩子方法,可以更清晰的了解用例的执行过程,并获取每个用例的执行结果。
钩子函数有两个作用:
1)可以获取到测试用例不同执行阶段的结果(setup、call、teardown)
2)可以获取钩子方法的调用结果(yield 返回一个result对象)和调用结果的测试报告(返回一个report对象)
pytest的Hook函数,修改pytet-html报告
可以自定义修改pytest-html报告,修改方法如下:
1)在项目根目录添加conftest.py
2)在conftest.py中通过标题行实现自定义钩子函数来修改
pytest_runtest_makereport
先看一下相关的源代码,在_pytest/runner.py文件中,导入之后点进去查看
函数执行完成后返回的是测试报告对象,TestReport
这里的item 是测试用例,call 是测试步骤,具体执行过程如下:
-先执行when='setup' 返回setup的执行结果
-然后执行when=‘call’,返回call的执行结果
-最后执行when='teardown' 返回teardown的执行结果
运行案例:
conftest.py写pytest_runtest_makereport内容,打印运行过程和运行结果
conftest.py文件如下:
import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item,call): """ :param item: 测试用例对象 :param call: 测试用例执行步骤 执行完常规钩子函数 返回的report报告有个属性叫report.when 先习性when = 'setup' 返回setup的执行结果 然后执行when = ‘call' 返回call的执行结果 然后执行when = 'teardown' 返回 teardown的执行结果 :return: """ print("------------------------") # 获取钩子方法的调用结果 out = yield # 从钩子方法的调用结果中 获取测试报告 report = out.get_result() print('测试报告:%s'%report) print('步骤:%s'%report.when) print('nodeid:%s'%report.nodeid) print('description : %s'%str(item.function.__doc__)) print('运行结果: %s'%report.outcome)
测试用例test_a.py如下:
import pytest def test_a(): print("用例描述 test_a")
运行结果:
============================= test session starts ============================= collecting ... collected 1 item test_a.py::test_a ------------------------ 测试报告:<TestReport 'test_a.py::test_a' when='setup' outcome='passed'> 步骤:setup nodeid:test_a.py::test_a description : None 运行结果: passed fixture login function ------------------------ 测试报告:<TestReport 'test_a.py::test_a' when='call' outcome='passed'> 步骤:call nodeid:test_a.py::test_a description : None 运行结果: passed PASSED [100%]用例描述 test_a ------------------------ 测试报告:<TestReport 'test_a.py::test_a' when='teardown' outcome='passed'> 步骤:teardown nodeid:test_a.py::test_a description : None 运行结果: passed ============================== 1 passed in 0.01s ============================== Process finished with exit code 0
从运行结果可以看出,运行用例的过程将会经历三个阶段:setup-call-teardown,每个阶段后悔返回Result对象和Testreport对象,以及对象属性
setup和teardown 上面的用例默认都没有,结果都是passed
setup和teardown:
给用例写个fixture增加用例的前置和后置操作,conftest.py内容如下:使用pytest -s test_a.py命令执行文件
import pytest @pytest.mark.hookwrapper def pytest_runtest_makereport(item,call): """ :param item: 测试用例对象 :param call: 测试用例执行步骤 执行完常规钩子函数 返回的report报告有个属性叫report.when 先习性when = 'setup' 返回setup的执行结果 然后执行when = ‘call' 返回call的执行结果 然后执行when = 'teardown' 返回 teardown的执行结果 :return: """ print("------------------------") # 获取钩子方法的调用结果 out = yield # 从钩子方法的调用结果中 获取测试报告 report = out.get_result() print('测试报告:%s'%report) print('步骤:%s'%report.when) print('nodeid:%s'%report.nodeid) print('description : %s'%str(item.function.__doc__)) print('运行结果: %s'%report.outcome) @pytest.fixture(scope='session',autouse=True) def fix_a(request): print("setup 前置函数") def fin_down(): print("teardown后置函数") request.addfinalizer(fin_down)
运行结果如下:
setup失败的情况
当setup运行失败了,setup的执行结果为failed,后面的call用例 和 teardown用例就不会执行了
此时用例状态是error,也就是用例call都还没有开始执行,就异常了
call失败的情况:
如果是setup正常执行但是call失败了,此时用例执行结果是failed,但是前置和后置 都是能够执行的
如果是teardown执行失败了:
只获取call的结果:
我们在写用例的时候,如果保证setup和teardown不报错,只关注测试用例本身的运行结果,前面的petest_runtest_makereport钩子方法执行了三个,可以增加一个判断
if report.when == 'call'
Hook函数获取失败截图的写法conftest.py:
# !/usr/bin/env python # -*-coding:utf-8 -*- """ # File : conftest.py # Time :2021/11/20 14:35 # Author :author Kong_hua_sheng_25304 # version :python 3.8 # Description: """ from selenium import webdriver import pytest driver: webdriver.Chrome = None @pytest.mark.hookwrapper def pytest_runtest_makereport(item): """ 当测试失败时,自动截图,展示到html报告中 :param item: 这里的item是测试用例 :return: """ pytest_html = item.config.pluginmanager.getplugin('html') outcome = yield report = outcome.get_result() # 获取report对象 extra = getattr(report,'extra',[]) #获取report对象中有没有extra属性 print("type item: ",type(item)) print("type report: ",type(report)) print("extra:",extra) if report.when == 'call' or report.when == 'setup': xfail = hasattr(report,'wasfail') # 判断rport对象中是否包含wasfail if (report.skipped and xfail) or (report.failed and not xfail): file_name = report.nodeid.replace("::","_")+".png" # 将report中nodeid中 :: 用下划线代替给截图进行命名 screen_img = _capter_screenshot() # 进行截图 if file_name: html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:600px;height:300px;" ' \ 'onclick="window.open(this.src)" align="right"/></div>' % screen_img extra.append(pytest_html.extras.html(html)) report.extra = extra def _capter_screenshot(): """ 截图保存为base64,展示到html中 :return: """ return driver.get_screenshot_as_base64() @pytest.fixture(scope='session',autouse=True) def browser(): global driver if driver is None: driver = webdriver.Chrome() return driver