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

posted @ 2021-07-28 08:47  习久性成  阅读(2549)  评论(0编辑  收藏  举报