pytest的Hook函数(钩子函数)详解 || pytest统计测试结果(钩子函数:pytest_terminal_summary)
pytest的Hook函数详解
Hook函数的定义
①Hook函数又称为钩子函数,它的作用可以理解成钩住自己喜欢的东西,然后对自己喜欢的东西单独做一些其他的处理。
②钩子函数/hook函数:
1、是个函数,在系统消息触发时被系统调用
2、不是用户自己触发的
3、使用时直接编写函数体
4、钩子函数的名称是确定,当系统消息触发,自动会调用。
比如: pytest_runtest_makereport
③插件与hook函数关系:
插件就是用1个或者多个hook函数,也就是钩子函数构成的。如果想要编写新的插件,或者是仅仅改进现有的插件,都必须通过这个hook函数进行。所以想掌握pytest插件二次开发,必须搞定hook函数。
pytest统计测试结果(钩子方法:pytest_terminal_summary)
前言
①当用例执行完成后,希望获取到执行的结果,方便了解用例的执行情况,这时候就可以使用 pytest_terminal_summary 来进行测试结果的统计(可以拿到所有的执行结果)。
②也可以将获取到的结果当成测试总结报告;发邮件的时候先统计测试结果,然后再加上pytest-html插件生成的测试报告以邮件的方式发出去。
pytest_terminal_summary源码
pytest_terminal_summary 源码:
参数:
-
terminalreporter (内部使用的终端测试报告对象)
-
exitstatus (返回给操作系统的返回码)
-
config (pytest 的 config 对象)
案例参考
实例一:正常情况
创建conftest.py文件, pytest_terminal_summary 函数用于收集测试结果。代码如下:
import time def pytest_terminal_summary(terminalreporter, exitstatus, config): """ 收集测试结果 """ print(terminalreporter.stats) print("total:", terminalreporter._numcollected) print('passed:', len(terminalreporter.stats.get('passed', []))) print('failed:', len(terminalreporter.stats.get('failed', []))) print('error:', len(terminalreporter.stats.get('error', []))) print('skipped:', len(terminalreporter.stats.get('skipped', []))) print('成功率:%.2f' % (len(terminalreporter.stats.get('passed', [])) / terminalreporter._numcollected * 100) + '%') # terminalreporter._sessionstarttime 会话开始时间 duration = time.time() - terminalreporter._sessionstarttime print('total times:', duration, 'seconds')
test_a.py脚本代码如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 微信公众号:AllTests软件测试 """ import pytest def test_a1(): print("测试用例test_a1") assert 1 == 1 def test_a2(): print("测试用例test_a2") @pytest.mark.skip("跳过test_a3") def test_a3(): print("测试用例test_a3") assert 1 == 1 def test_a4(): print("测试用例test_a4") assert 1 == 2
test_b.py脚本代码如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 微信公众号:AllTests软件测试 """ def test_b1(): print("测试用例test_b1") def test_b2(): print("测试用例test_b2") assert 1 == 2
命令行参数运行:
pytest -s
运行结果:
获取到的测试用例执行状态打印到控制台。
实例二:setup前置函数异常情况
test_a脚本文件不变,修改test_b.py脚本文件如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 微信公众号:AllTests软件测试 """ import pytest @pytest.fixture(scope="function") def my_setup(): assert 1 == 2 def test_b1(my_setup): print("测试用例test_b1") def test_b2(): print("测试用例test_b2") assert 1 == 2
开始运行,运行结果如下截图:
示例三:teardown后置操作异常情况
test_a脚本文件不变,修改test_b.py脚本文件如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 微信公众号:AllTests软件测试 """ import pytest @pytest.fixture(scope="function") def my_teardown(): yield assert 1 == 2 def test_b1(my_teardown): print("测试用例test_b1") def test_b2(): print("测试用例test_b2") assert 1 == 2
开始运行,运行结果如下截图:
运行结果解析:
用例总数是6,但是测试报告状态结果为2 failed, 3 passed, 1 skipped, 1 error,合计为7。
从获取的打印在控制台上的 terminalreporter.stats 信息可以看出:
passed里when = 'call'时,统计一次test_b1用例:<TestReport 'test_b.py::test_b1' when='call' outcome='passed'>
error里when = 'teardown'时,又统计一次test_b1用例:<TestReport 'test_b.py::test_b1' when='teardown' outcome='failed'>
所以报告结果合计为7。
【但因为when='teardown'是测试用例的后置操作,一般用于数据的清理等操作,如teardown后置函数报错不影响测试用例的执行结果,所以在conftest.py文件里 pytest_terminal_summary 函数获取测试结果进行了忽略统计。】
查看打印的内部使用的终端测试报告对象的统计结果:
我的理解:
①因为当执行测试用例时,如果用例执行之前的setup前置操作发生异常,那么用例就会直接执行异常(即error,如示例二)。
②因为当执行测试用例时,如果用例的setup前置操作以及teardown后置操作以及该用例执行期间都没有发生异常,那么用例就会正常执行且用例的执行结果只能为pass或者failed(如示例一)
③因为当执行测试用例时,如果用例执行之后的teardown后置操作发生异常而用例本身依然会执行成功(不考虑用例执行的结果是passed还是failed),只是该用例的teardown后置操作发生异常。(如示例三:该用例既会出现在pass结果内,也会出现在error结果内;即出现两次)
如果想与上图正常的测试报告结果保持一致(即当执行测试用例时,如果用例执行之后的teardown后置操作发生异常而用例本身依然会执行成功(不考虑用例执行的结果是passed还是failure),只是该用例的teardown后置操作发生异常。(如示例三:该用例既会出现在passed/failed结果内,也会出现在error结果内;即出现两次)):
修改 pytest_terminal_summary 钩子函数如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 微信公众号:AllTests软件测试 """ import time def pytest_terminal_summary(terminalreporter, exitstatus, config): """ 收集测试结果 """ print(terminalreporter.stats) print("total:", terminalreporter._numcollected) print('passed:', len(terminalreporter.stats.get('passed', []))) print('failed:', len(terminalreporter.stats.get('failed', []))) print('error:', len(terminalreporter.stats.get('error', []))) print('skipped:', len(terminalreporter.stats.get('skipped', []))) # terminalreporter._sessionstarttime 会话开始时间 duration = time.time() - terminalreporter._sessionstarttime print('total times:', duration, 'seconds')
与上图相反,如果某测试用例中只有teardown后置函数发生异常则只将该用例统计为passed/failed;而不统计为error:(而不是既将其统计为error又同时统计为passed/failed):
#!/usr/bin/env python # -*- coding: utf-8 -*- """ 微信公众号:AllTests软件测试 """ import time def pytest_terminal_summary(terminalreporter, exitstatus, config): """ 收集测试结果 """ print(terminalreporter.stats) print("total:", terminalreporter._numcollected) print('passed:', len([i for i in terminalreporter.stats.get('passed', []) if i.when != 'teardown'])) print('failed:', len([i for i in terminalreporter.stats.get('failed', []) if i.when != 'teardown'])) print('error:', len([i for i in terminalreporter.stats.get('error', []) if i.when != 'teardown'])) print('skipped:', len([i for i in terminalreporter.stats.get('skipped', []) if i.when != 'teardown'])) print('成功率:%.2f' % (len(terminalreporter.stats.get('passed', []))/terminalreporter._numcollected*100)+'%') # terminalreporter._sessionstarttime 会话开始时间 duration = time.time() - terminalreporter._sessionstarttime print('total times:', duration, 'seconds')
【原因】when='teardown'是测试用例的后置操作,一般用于数据的清理等操作,如报错不影响测试用例的执行结果,可以忽略,只关注测试用例本身的执行结果。
拿到测试结果
如何拿到测试结果,这里将测试结果保存为txt文件,你们也可以保存json文件。
import time from _pytest import terminal # blog地址 https://www.cnblogs.com/yoyoketang/ def pytest_terminal_summary(terminalreporter, exitstatus, config): '''收集测试结果''' # print(terminalreporter.stats) total = terminalreporter._numcollected passed= len([i for i in terminalreporter.stats.get('passed', []) if i.when != 'teardown']) failed=len([i for i in terminalreporter.stats.get('failed', []) if i.when != 'teardown']) error=len([i for i in terminalreporter.stats.get('error', []) if i.when != 'teardown']) skipped=len([i for i in terminalreporter.stats.get('skipped', []) if i.when != 'teardown']) successful = len(terminalreporter.stats.get('passed', []))/terminalreporter._numcollected*100 # terminalreporter._sessionstarttime 会话开始时间 duration = time.time() - terminalreporter._sessionstarttime print('total times: %.2f' % duration, 'seconds') with open("result.txt", "w") as fp: fp.write("TOTAL=%s" % total+"\n") fp.write("PASSED=%s" % passed+"\n") fp.write("FAILED=%s" % failed+"\n") fp.write("ERROR=%s" % error+"\n") fp.write("SKIPPED=%s" % skipped+"\n") fp.write("SUCCESSFUL=%.2f%%" % successful+"\n") fp.write("TOTAL_TIMES=%.2fs" % duration)
邮件发送
邮件发送是配合jenkins发送邮件,可以参考这篇https://www.cnblogs.com/yoyoketang/p/14956235.html
参考博客:https://blog.csdn.net/wangmcn/article/details/120891614