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
test_b.py脚本代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
微信公众号:AllTests软件测试
"""
def test_b1():
print("测试用例test_b1")
def test_b2():
print("测试用例test_b2")
assert
命令行参数运行:
运行结果:
获取到的测试用例执行状态打印到控制台。
实例二: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
开始运行,运行结果如下截图:
示例三: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
开始运行,运行结果如下截图:
运行结果解析:
用例总数是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
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"
邮件发送
邮件发送是配合jenkins发送邮件
去期待陌生,去拥抱惊喜。