【pytest】Hooks函数之统计测试结果(pytest_terminal_summary)

前言
用例执行完成后,我们希望能获取到执行的结果,这样方便我们快速统计用例的执行情况。
也可以把获取到的结果当成总结报告,发邮件的时候可以先统计测试结果,再加上html的报告。

pytest_terminal_summary
关于TerminalReporter类可以在_pytest.terminal中查看到

 
 
pytest_terminal_summary(terminalreporter, exitstatus, config)
最后的结果汇总,可以拿到所有的执行结果
参数:
- terminalreporter (_pytest.terminal.TerminalReporter) – 内部使用的终端测试报告对象
- exitstatus (int) – 返回给操作系统的返回码
- config(_pytest.config.Config) - pytest config对象

TerminalReporter部分代码

class TerminalReporter(object):
    def __init__(self, config, file=None):
        import _pytest.config
 
        self.config = config
        self._numcollected = 0
        self._session = None
        self._showfspath = None
 
        self.stats = {}
        self.startdir = config.invocation_dir
 
 
    def report_collect(self, final=False):
        if self.config.option.verbose < 0:
            return
 
        if not final:
            # Only write "collecting" report every 0.5s.
            t = time.time()
            if (
                self._collect_report_last_write is not None
                and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION
            ):
                return
            self._collect_report_last_write = t
 
        errors = len(self.stats.get("error", []))
        skipped = len(self.stats.get("skipped", []))
        deselected = len(self.stats.get("deselected", []))
        selected = self._numcollected - errors - skipped - deselected
        if final:
            line = "collected "
        else:
            line = "collecting "
        line += (
            str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s")
        )
        if errors:
            line += " / %d errors" % errors
        if deselected:
            line += " / %d deselected" % deselected
        if skipped:
            line += " / %d skipped" % skipped
        if self._numcollected > selected > 0:
            line += " / %d selected" % selected
        if self.isatty:
            self.rewrite(line, bold=True, erase=True)
            if final:
                self.write("\n")
        else:
            self.write_line(line)

案例参考

先在test_a.py写几个用例

# test_a.py
import pytest
# 上海-悠悠
 
def test_1():
    print("测试用例1111")
    assert 1 == 1
 
 
@pytest.mark.skip("跳过")
def test_2():
    print("测试用例22222")
    assert 1 == 1
 
 
def test_3():
    print("测试用例3333")
 
 
def test_4():
    print("测试用例44444444")
    assert 1 == 2

test_b.py用例参考

# test_b.py
import time
# 上海-悠悠
 
def test_5():
    print("测试用例55555555")
    time.sleep(3)
 
 
def test_6():
    print("测试用例66666666")
    time.sleep(3)
    assert 1 == 2

于是在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', [])))
    # terminalreporter._sessionstarttime 会话开始时间
    duration = time.time() - terminalreporter._sessionstarttime
    print('total times:', duration, 'seconds')
 

运行结果

D:\soft\pytest_xuexi_demo>pytest
============================= test session starts =============================
platform win32 -- Python 3.6.0, pytest-4.5.0, py-1.5.4, pluggy-0.13.1
rootdir: D:\soft\pytest_xuexi_demo
plugins: allure-pytest-2.8.6, PyTestReport-0.1.9.3, forked-0.2, html-1.19.0, metadata-1.7.0, repeat-0.7.0, rerunfailures-8.0, xdist-1.23.2
collected 6 items
 
test_a.py .s.F                                                           [ 66%]
test_b.py .F                                                             [100%]
 
================================== FAILURES ===================================
___________________________________ test_4 ____________________________________
 
    def test_4():
        print("测试用例44444444")
>       assert 1==2
E       assert 1 == 2
 
test_a.py:21: AssertionError
---------------------------- Captured stdout call -----------------------------
测试用例44444444
___________________________________ test_6 ____________________________________
 
    def test_6():
        print("测试用例66666666")
        time.sleep(3)
>       assert 1 == 2
E       assert 1 == 2
 
test_b.py:18: AssertionError
---------------------------- Captured stdout call -----------------------------
测试用例66666666
{'': [<TestReport 'test_a.py::test_1' when='setup' outcome='passed'>, <TestReport 'test_a.py::test_1' when='teardown' outcome='passed'>, <TestReport 'test_a.py::test_2' when='teardown' outcome='passed'>, <TestReport 'test_a.py::test_3' when='setup' outcome='passed'>, <TestReport 'test_a.py::test_3' when='teardown' outcome='passed'>, <TestReport 'test_a.py::test_4' when='setup' outcome='passed'>, <TestReport 'test_a.py::test_4' when='teardown' outcome='passed'>, <TestReport 'test_b.py::test_5' when='setup' outcome='passed'>, <TestReport 'test_b.py::test_5' when='teardown' outcome='passed'>, <TestReport 'test_b.py::test_6' when='setup' outcome='passed'>, <TestReport 'test_b.py::test_6' when='teardown' outcome='passed'>], 'passed': [<TestReport 'test_a.py::test_1' when='call' outcome='passed'>, <TestReport 'test_a.py::test_3' when='call' outcome='passed'>, <TestReport 'test_b.py::test_5' when='call' outcome='passed'>], 'skipped': [<TestReport 'test_a.py::test_2' when='setup' outcome='skipped'>], 'failed': [<TestReport 'test_a.py::test_4' when='call' outcome='failed'>, <TestReport 'test_b.py::test_6' when='call' outcome='failed'>]}
total: 6
passed: 3
failed: 2
error: 0
skipped: 1
total times: 6.150860786437988 seconds
================ 2 failed, 3 passed, 1 skipped in 6.15 seconds ================

setup和teardown异常情况

如果setup出现异常,test_b.py代码修改下

# test_b.py
import time
import pytest
# 上海-悠悠
 
 
@pytest.fixture(scope="function")
def setup_demo():
    raise TypeError("ERROR!")
 
 
def test_5(setup_demo):
    print("测试用例55555555")
    time.sleep(3)
 
 
def test_6():
    print("测试用例66666666")
    time.sleep(3)
    assert 1 == 2

重新运行用例,结果如下

total: 6
passed: 2
failed: 2
error: 1
skipped: 1
成功率:33.33%
total times: 3.1817877292633057 seconds
=========== 2 failed, 2 passed, 1 skipped, 1 error in 3.18 seconds ============

此时统计结果没什么问题,接下来看teardown异常情况

# test_b.py
import time
import pytest
# 上海-悠悠
 
 
@pytest.fixture(scope="function")
def setup_demo():
    yield 
    raise TypeError("ERROR!")
 
 
def test_5(setup_demo):
    print("测试用例55555555")
    time.sleep(3)
 
 
def test_6():
    print("测试用例66666666")
    time.sleep(3)
    assert 1 == 2

运行结果

{'': [<TestReport 'test_a.py::test_1' when='setup' outcome='passed'>, <TestReport 'test_a.py::test_1' when='teardown' outcome='passed'>, <TestReport 'test_a.py::test_2' when='teardown' outcome='passed'>, <TestReport 'test_a.py::test_3' when='setup' outcome='passed'>, <TestReport 'test_a.py::test_3' when='teardown' outcome='passed'>, <TestReport 'test_a.py::test_4' when='setup' outcome='passed'>, <TestReport 'test_a.py::test_4' when='teardown' outcome='passed'>, <TestReport 'test_b.py::test_5' when='setup' outcome='passed'>, <TestReport 'test_b.py::test_6' when='setup' outcome='passed'>, <TestReport 'test_b.py::test_6' when='teardown' outcome='passed'>], 'passed': [<TestReport 'test_a.py::test_1' when='call' outcome='passed'>, <TestReport 'test_a.py::test_3' when='call' outcome='passed'>, <TestReport 'test_b.py::test_5' when='call' outcome='passed'>], 'skipped': [<TestReport 'test_a.py::test_2' when='setup' outcome='skipped'>], 'failed': [<TestReport 'test_a.py::test_4' when='call' outcome='failed'>, <TestReport 'test_b.py::test_6' when='call' outcome='failed'>], 'error': [<TestReport 'test_b.py::test_5' when='teardown' outcome='failed'>]}
total: 6
passed: 3
failed: 2
error: 1
skipped: 1
成功率:50.00%
total times: 6.18759298324585 seconds
=========== 2 failed, 3 passed, 1 skipped, 1 error in 6.19 seconds ============

这个时候总用例是6,但是2 failed, 3 passed, 1 skipped, 1 error加起来的个数是7,这个为什么?

从 terminalreporter.stats 可以看出 passed 里面 when='call' 时候统计了一次 test_5 用例

<TestReport 'test_b.py::test_5' when='call' outcome='passed'>

error 里面 when='teardown' 又统计了一次 test_5 用例

'error': [<TestReport 'test_b.py::test_5' when='teardown' outcome='failed'>]

when='teardown' 是测试用例的后置操作,一般用于数据的清理,报错了的话不影响测试用例的执行结果,所以可以忽略掉

修改后的最终代码如下

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')

运行后的结果

total: 6
passed: 3
failed: 2
error: 0
skipped: 1
成功率:50.00%
total times: 6.20133113861084 seconds
=========== 2 failed, 3 passed, 1 skipped, 1 error in 6.20 seconds ============

拿到测试结果

很多小伙伴问到如何拿到测试结果,这里我把测试结果保存为txt文件,你们也可以保存json文件

import time
# 作者-上海悠悠 QQ交流群:717225969
# 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)

邮件发送

总结:

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

 

文档获取方式: 

加入我的软件测试交流群:822269834免费获取~(同行大佬一起学术交流,每晚都有大佬直播分享技术知识点)

这份文档,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

以上均可以分享,只需要你搜索vx公众号:程序员雨果,即可免费领取
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值