pytest提供了pytest_runtest_makereport这个方法,可以捕获用例的执行情况。根据官方提供的示例,在conftest.py文件中添加如下代码就可以捕获每个用例的执行结果。(官方示例链接:
https://docs.pytest.org/en/7.1.x/example/simple.html?highlight=pytest_runtest_makereport)
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
# execute all other hooks to obtain the report object
outcome = yield
rep = outcome.get_result() # rep可以拿到用例的执行结果详情
个人有两种理解:
1.这个钩子方法会被每个测试用例调用 3 次,分别是:
- 用例步骤 setup 执行完毕后,调用 1 次,返回 setup 的执行结果并执行钩子函数中setup相关代码-->接着执行用例测试步骤;
- 用例步骤 call 执行完毕之后,调用 1 次,返回测试用例的执行结果并执行钩子函数中call相关的代码-->接着执行teardown相关步骤(若有);
- 用例步骤 teardown 执行完毕后,调用1 次,返回 teardown 的执行结果;
2.钩子函数和用例交叉执行:
1.测试用例setup步骤-->钩子函数(pytest_runtest_makereport)获取setup结果并执行setup相符的相关代码-->测试用例执行call 步骤-->钩子函数获取call 结果并执行call相符的相关代码-->测试用例执行teardown 步骤-->钩子函数获取teardown 结果并执行teardown相符的相关代码
调用钩子函数
# -*- coding: utf-8 -*-
# conftest.py
import threading
import time
import pytest
# from temp import th_temp
def th_temp():
for i in range(10):
print(i, time.strftime("%Y%m%d%H%M%S"))
time.sleep(1)
@pytest.hookimpl(trylast=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == "setup":
print("\n 开始执行钩子函数setup相关脚本 {}".format(time.strftime("%Y%m%d%H%M%S")))
t1 = threading.Thread(target=th_temp, )
# t1.daemon = True
t1.start()
time.sleep(10)
if report.when == "call" and report.failed:
print("\n 获取call结果并执行符合结果的脚本{}".format(time.strftime("%Y%m%d%H%M%S")))
if report.when == "teardown" and report.passed:
print("\n 获取teardown结果并执行相关脚本{}".format(time.strftime("%Y%m%d%H%M%S")))
print('{} 从结果中获取测试报告:'.format(time.strftime("%Y%m%d%H%M%S")), report)
print('{} 从报告中获取 nodeid:'.format(time.strftime("%Y%m%d%H%M%S")), report.nodeid)
print('{} 从报告中获取调用步骤:'.format(time.strftime("%Y%m%d%H%M%S")), report.when)
print('{} 从报告中获取执行结果:'.format(time.strftime("%Y%m%d%H%M%S")),report.outcome)
测试用例py文件
#test_prac
# -*- coding: utf-8 -*-
import time
def setup_function():
print("{} setup开始执行".format(time.strftime("%Y%m%d%H%M%S")))
for i in range(5):
print(i, time.strftime("%Y%m%d%H%M%S"))
time.sleep(1)
print("{} setup执行完毕".format(time.strftime("%Y%m%d%H%M%S")))
def teardown_function():
print("{} teardown开始执行".format(time.strftime("%Y%m%d%H%M%S")))
for i in range(5):
print(i, time.strftime("%Y%m%d%H%M%S"))
time.sleep(1)
print("{} teardown执行完毕".format(time.strftime("%Y%m%d%H%M%S")))
def test_01():
print(1111)
# assert 1==1
def test_02():
print("\n 执行测试用例步骤脚本")
for i in range(5):
print(i, time.strftime("%Y%m%d%H%M%S"))
time.sleep(1)
assert 1 == "2" + time.strftime("%Y%m%d%H%M%S")
def test_03():
print(33333)
assert 3 == 3
上述执行结果:
# test1
开始执行钩子函数setup相关脚本 20240804230252
0 20240804230252
1 20240804230253
2 20240804230254
3 20240804230255
4 20240804230256
5 20240804230257
6 20240804230258
7 20240804230259
8 20240804230300
9 20240804230301
20240804230302 从结果中获取测试报告: <TestReport 'test_prac.py::test_01' when='setup' outcome='passed'>
20240804230302 从报告中获取 nodeid: test_prac.py::test_01
20240804230302 从报告中获取调用步骤: setup
20240804230302 从报告中获取执行结果: passed
20240804230247 setup开始执行
0 20240804230247
1 20240804230248
2 20240804230249
3 20240804230250
4 20240804230251
20240804230252 setup执行完毕
20240804230302 从结果中获取测试报告: <TestReport 'test_prac.py::test_01' when='call' outcome='passed'>
20240804230302 从报告中获取 nodeid: test_prac.py::test_01
20240804230302 从报告中获取调用步骤: call
20240804230302 从报告中获取执行结果: passed
PASSED [ 33%]1111
#test2
开始执行钩子函数setup相关脚本 20240804230312
0 20240804230312
1 20240804230313
2 20240804230314
3 20240804230315
4 20240804230316
5 20240804230317
6 20240804230319
7 20240804230320
8 20240804230321
9 20240804230322
20240804230322 从结果中获取测试报告: <TestReport 'test_prac.py::test_02' when='setup' outcome='passed'>
20240804230322 从报告中获取 nodeid: test_prac.py::test_02
20240804230322 从报告中获取调用步骤: setup
20240804230322 从报告中获取执行结果: passed
20240804230307 setup开始执行
0 20240804230307
1 20240804230308
2 20240804230309
3 20240804230310
4 20240804230311
20240804230312 setup执行完毕
获取call结果并执行符合结果的脚本20240804230328
20240804230328 从结果中获取测试报告: <TestReport 'test_prac.py::test_02' when='call' outcome='failed'>
20240804230328 从报告中获取 nodeid: test_prac.py::test_02
20240804230328 从报告中获取调用步骤: call
20240804230328 从报告中获取执行结果: failed
FAILED [ 66%]
执行测试用例步骤脚本
0 20240804230322
1 20240804230323
2 20240804230324
3 20240804230325
4 20240804230326
test_prac.py:27 (test_02)
1 != '220240804230327'
预期:'220240804230327'
实际:1
<点击以查看差异>
def test_02():
print("\n 执行测试用例步骤脚本")
for i in range(5):
print(i, time.strftime("%Y%m%d%H%M%S"))
time.sleep(1)
> assert 1 == "2" + time.strftime("%Y%m%d%H%M%S")
E AssertionError: assert 1 == ('2' + '20240804230327')
E + where '20240804230327' = <built-in function strftime>('%Y%m%d%H%M%S')
E + where <built-in function strftime> = time.strftime
test_prac.py:33: AssertionError
具体示例如下:
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""
1.该插件作用于pytest的钩子函数上,可以获取到测试用例不同执行阶段的结果(setup,call,teardown)
2.可以获取钩子方法的调用结果(返回一个result对象)和调用结果的测试报告(返回一个report对象)
:param item:
:param call:
:return:
"""
outcome = yield # 生成器会给outcome send 一个pluggy.callers._Result对象
rep = outcome.get_result() # 通过get_result拿到所有钩子函数的结果
Ntime = time.strftime("%Y%m%d%H%M%S", time.localtime())
if rep.when == "setup":
path_info.recording_path = "video/" + Ntime + "_" + \
rep.nodeid.split("::")[1] + ".mp4"
path_info.recd_thread = threading.Thread(target=Recording, args=(path_info.recording_path,))
path_info.recd_thread.daemon = True # 子线程无论是否正在运行都会随着父线程运行完毕后一起退出
path_info.recd_thread.start() # 执行录屏操作
if rep.when == "teardown" and rep.passed:
path_info.stop_recording = 1
time.sleep(1)
path_info.stop_recording = 0
file_oprate.delete_file(path_info.recording_path)
if rep.when == "call" and rep.failed:
# 收集失败的用例
case_info.last_failCase.append(rep.nodeid)
imag_path = os.path.abspath(".") + r"/" + screen_shot.screen_shot("imags")
with open(imag_path, "rb") as f:
img_bit = f.read()
allure.attach(img_bit, "失败截图", allure.attachment_type.PNG)
# 当前用例执行的话有很多原因比如点击位置错误展现的弹窗不对,为了不阻塞之后的用例执行,直接进行了杀进程的操作
pid = processop.check_process_exsit(web_info.web_name)
if pid != None:
cmd = 'taskkill /f /im ' + web_info.web_name
os.system(cmd)
path_info.stop_recording = 1 #传入参数1则表示停止录屏,不执行对应代码(录屏和截图函数需自己写,本文中的录屏和截图函数已写在其他文件中)
time.sleep(1)
with open(path_info.recording_path, "rb") as f:
video_bit = f.read()
allure.attach(video_bit, "失败录屏",
allure.attachment_type.MP4) # 可以设置需要显示在allure报告的附件,包含了多种类型,可以通过allure.attachment_type查看支持的类型
time.sleep(1)
path_info.stop_recording = 0 #为0则每次本钩子函数执行完后启用录屏操作,录屏相关函数需要已在其他文件中写入,此处不作赘述
参考文章
pytest实现异常用例截图并在allure报告中查看_pytest 实现 allure 报告插入截图-CSDN博客
【pytest】Hook 方法之 pytest_runtest_makereport:获取测试用例执行结果_roport.nodeid-CSDN博客