python unittest 遇错重跑 + 测试报告不统计重跑的次数

增加健壮性

感谢大佬的倾情奉献@zbo1301

他的代码 我就改了几行 属于转载·····

上面博主实现的 貌似 不太符合我的需求

我自己稍微改了点东西

目前的问题就是

一图见真相

python unittest 遇错重跑 + 测试报告不统计重跑的次数_第1张图片

# -*- coding: utf-8 -*-
# auth: cy
# create 
# update:2019年1月11日
from unittest import TestCase
from unittest.case import _Outcome
import time


class TestCase_(TestCase):
    # 是否开启失败重跑
    FAILURE_REPEAT_RUN_FLAG = True
    # 失败重跑尝试次数
    FAILURE_REPEAT_RUN_NUM = 3
    # 设置重跑时间间隔
    REPEAT_TIME_INTERVAL = 5

    def run(self, result=None):
        run_count = 1
        orig_result = result
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            if startTestRun is not None:
                startTestRun()

        result.startTest(self)

        testMethod = getattr(self, self._testMethodName)
        if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
            # If the class or method was skipped.
            try:
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                self._addSkip(result, self, skip_why)
            finally:
                result.stopTest(self)
            return
        expecting_failure_method = getattr(testMethod,
                                           "__unittest_expecting_failure__", False)
        expecting_failure_class = getattr(self,
                                          "__unittest_expecting_failure__", False)
        expecting_failure = expecting_failure_class or expecting_failure_method
        outcome = _Outcome(result)
        try:
            self._outcome = outcome
            while True:
                with outcome.testPartExecutor(self):
                    self.setUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self, isTest=True):
                        testMethod()
                    outcome.expecting_failure = False
                    with outcome.testPartExecutor(self):
                        self.tearDown()
                self.doCleanups()

                for test, reason in outcome.skipped:
                    self._addSkip(result, test, reason)
                self._feedErrorsToResult(result, outcome.errors)

                if outcome.success:  # 成功为True case 失败 为 false
                    if expecting_failure:
                        if outcome.expectedFailure:
                            self._addExpectedFailure(result, outcome.expectedFailure)
                        else:
                            self._addUnexpectedSuccess(result)
                    else:
                        result.addSuccess(self)
                # =======================重跑===================
                if not self.FAILURE_REPEAT_RUN_FLAG:
                    return result
                if run_count < self.FAILURE_REPEAT_RUN_NUM and outcome.success is False:
                    try:
                        result.current_failed = False  # 这次的测试结果有错误,把他设置为没有错误 
                        outcome = _Outcome(result)  # 然后重新赋值 
                    except Exception as e:
                        print(e)
                        pass
                    run_count += 1
                    time.sleep(self.REPEAT_TIME_INTERVAL)
                    continue
                # ==============================================
                return result

        finally:
            result.stopTest(self)
            if orig_result is None:
                stopTestRun = getattr(result, 'stopTestRun', None)
                if stopTestRun is not None:
                    stopTestRun()

            # explicitly break reference cycles:
            # outcome.errors -> frame -> outcome -> outcome.errors
            # outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure
            outcome.errors.clear()
            outcome.expectedFailure = None

            # clear the outcome, no more needed
            self._outcome = None

如何使用呢

下面

# -*- coding: utf-8 -*-
# Author:CY
# U: 2018年9月5日
from configparser import ConfigParser
from CY.my_test_case import TestCase_
from run_path import setting_path


class RepeatRun(TestCase_):
    def setUp(self):
        conf = ConfigParser()
        conf.read(setting_path())
        self.password = conf.get('operation', 'password')
        self.username1 = conf.get('projectManager', 'taohui')

    def tearDown(self):
        print('*************')

    def test_01_Dse(self):

        print(self.password)
        raise Exception('erro')

    def test_02_Dse(self):
        print(self.username1)

上面是更改后的unittest

下面感谢@灰蓝 大婶的源码

https://github.com/huilansame/HTMLTestRunner_PY3.git

我稍微改了一下 统计error 的 字段

如图python unittest 遇错重跑 + 测试报告不统计重跑的次数_第2张图片

# -*- coding: utf-8 -*-


import os
import sys
from io import StringIO as StringIO
import time
import json
import unittest
import platform
import base64
from distutils.sysconfig import get_python_lib
import traceback
from functools import wraps

__all__ = ['BeautifulReport']

HTML_IMG_TEMPLATE = """
    
    
    
    

""" class OutputRedirector(object): """ Wrapper to redirect stdout or stderr """ def __init__(self, fp): self.fp = fp def write(self, s): self.fp.write(s) def writelines(self, lines): self.fp.writelines(lines) def flush(self): self.fp.flush() stdout_redirector = OutputRedirector(sys.stdout) stderr_redirector = OutputRedirector(sys.stderr) SYSSTR = platform.system() SITE_PAKAGE_PATH = get_python_lib() FIELDS = { "testPass": 0, "testResult": [ ], "testName": "", "testAll": 0, "testFail": 0, "beginTime": "", "totalTime": "", "testSkip": 0 } class PATH: """ all file PATH meta """ config_tmp_path = SITE_PAKAGE_PATH + '/BeautifulReport/template/template' class MakeResultJson: """ make html table tags """ def __init__(self, datas: tuple): """ init self object :param datas: 拿到所有返回数据结构 """ self.datas = datas self.result_schema = {} def __setitem__(self, key, value): """ :param key: self[key] :param value: value :return: """ self[key] = value def __repr__(self) -> str: """ 返回对象的html结构体 :rtype: dict :return: self的repr对象, 返回一个构造完成的tr表单 """ keys = ( 'className', 'methodName', 'description', 'spendTime', 'status', 'log', ) for key, data in zip(keys, self.datas): self.result_schema.setdefault(key, data) return json.dumps(self.result_schema) class ReportTestResult(unittest.TestResult): """ override""" def __init__(self, suite, stream=sys.stdout): """ pass """ super(ReportTestResult, self).__init__() self.begin_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) self.start_time = 0 self.stream = stream self.end_time = 0 self.failure_count = 0 self.error_count = 0 self.success_count = 0 self.skipped = 0 self.verbosity = 1 self.success_case_info = [] self.skipped_case_info = [] self.failures_case_info = [] self.errors_case_info = [] self.all_case_counter = 0 self.suite = suite self.status = '' self.result_list = [] self.case_log = '' self.default_report_name = '自动化测试报告' self.FIELDS = None self.sys_stdout = None self.sys_stderr = None self.outputBuffer = None @property def success_counter(self) -> int: """ set success counter """ return self.success_count @success_counter.setter def success_counter(self, value) -> None: """ success_counter函数的setter方法, 用于改变成功的case数量 :param value: 当前传递进来的成功次数的int数值 :return: """ self.success_count = value def startTest(self, test) -> None: """ 当测试用例测试即将运行时调用 :return: """ unittest.TestResult.startTest(self, test) self.outputBuffer = StringIO() stdout_redirector.fp = self.outputBuffer stderr_redirector.fp = self.outputBuffer self.sys_stdout = sys.stdout self.sys_stdout = sys.stderr sys.stdout = stdout_redirector sys.stderr = stderr_redirector self.start_time = time.time() def stopTest(self, test) -> None: """ 当测试用力执行完成后进行调用 :return: """ self.end_time = '{0:.3} s'.format((time.time() - self.start_time)) self.result_list.append(self.get_all_result_info_tuple(test)) self.complete_output() def complete_output(self): """ Disconnect output redirection and return buffer. Safe to call multiple times. """ if self.sys_stdout: sys.stdout = self.sys_stdout sys.stderr = self.sys_stdout self.sys_stdout = None self.sys_stdout = None return self.outputBuffer.getvalue() def stopTestRun(self, title=None) -> dict: """ 所有测试执行完成后, 执行该方法 :param title: :return: """ FIELDS['testPass'] = self.success_counter for item in self.result_list: item = json.loads(str(MakeResultJson(item))) FIELDS.get('testResult').append(item) FIELDS['testAll'] = len(self.result_list) err_unm = 0 for case_info in self.result_list: if case_info[4] == '失败': err_unm += 1 FIELDS['testName'] = title if title else self.default_report_name FIELDS['testFail'] = err_unm FIELDS['beginTime'] = self.begin_time end_time = int(time.time()) start_time = int(time.mktime(time.strptime(self.begin_time, '%Y-%m-%d %H:%M:%S'))) FIELDS['totalTime'] = str(end_time - start_time) + 's' FIELDS['testError'] = self.error_count FIELDS['testSkip'] = self.skipped self.FIELDS = FIELDS return FIELDS def get_all_result_info_tuple(self, test) -> tuple: """ 接受test 相关信息, 并拼接成一个完成的tuple结构返回 :param test: :return: """ return tuple([*self.get_testcase_property(test), self.end_time, self.status, self.case_log]) @staticmethod def error_or_failure_text(err) -> str: """ 获取sys.exc_info()的参数并返回字符串类型的数据, 去掉t6 error :param err: :return: """ return traceback.format_exception(*err) def addSuccess(self, test) -> None: """ pass :param test: :return: """ logs = [] output = self.complete_output() logs.append(output) if self.verbosity > 1: sys.stderr.write('ok ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('.') self.success_counter += 1 self.status = '成功' self.case_log = output.split('\n') self._mirrorOutput = True # print(class_name, method_name, method_doc) def addError(self, test, err): """ add Some Error Result and infos :param test: :param err: :return: """ logs = [] output = self.complete_output() logs.append(output) logs.extend(self.error_or_failure_text(err)) self.failure_count += 1 self.add_test_type('失败', logs) if self.verbosity > 1: sys.stderr.write('F ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('F') self._mirrorOutput = True def addFailure(self, test, err): """ add Some Failures Result and infos :param test: :param err: :return: """ logs = [] output = self.complete_output() logs.append(output) logs.extend(self.error_or_failure_text(err)) self.failure_count += 1 self.add_test_type('失败', logs) if self.verbosity > 1: sys.stderr.write('F ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('F') self._mirrorOutput = True def addSkip(self, test, reason) -> None: """ 获取全部的跳过的case信息 :param test: :param reason: :return: None """ logs = [reason] self.complete_output() self.skipped += 1 self.add_test_type('跳过', logs) if self.verbosity > 1: sys.stderr.write('S ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('S') self._mirrorOutput = True def add_test_type(self, status: str, case_log: list) -> None: """ abstruct add test type and return tuple :param status: :param case_log: :return: """ self.status = status self.case_log = case_log @staticmethod def get_testcase_property(test) -> tuple: """ 接受一个test, 并返回一个test的class_name, method_name, method_doc属性 :param test: :return: (class_name, method_name, method_doc) -> tuple """ class_name = test.__class__.__qualname__ method_name = test.__dict__['_testMethodName'] method_doc = test.__dict__['_testMethodDoc'] return class_name, method_name, method_doc class BeautifulReport(ReportTestResult, PATH): img_path = 'img/' if platform.system() != 'Windows' else 'img\\' def __init__(self, suites): super(BeautifulReport, self).__init__(suites) self.suites = suites self.log_path = None self.title = '自动化测试报告' self.filename = 'report.html' def report(self, description, filename: str = None, log_path='.'): """ 生成测试报告,并放在当前运行路径下 :param log_path: 生成report的文件存储路径 :param filename: 生成文件的filename :param description: 生成文件的注释 :return: """ if filename: self.filename = filename if filename.endswith('.html') else filename + '.html' if description: self.title = description self.log_path = os.path.abspath(log_path) self.suites.run(result=self) self.stopTestRun(self.title) self.output_report() text = '\n测试已全部完成, 可前往{}查询测试报告'.format(self.log_path) print(text) def output_report(self): """ 生成测试报告到指定路径下 :return: """ template_path = self.config_tmp_path override_path = os.path.abspath(self.log_path) if \ os.path.abspath(self.log_path).endswith('/') else \ os.path.abspath(self.log_path) + '/' with open(template_path, 'rb') as file: body = file.readlines() with open(override_path + self.filename, 'wb') as write_file: for item in body: if item.strip().startswith(b'var resultData'): head = ' var resultData = ' item = item.decode().split(head) item[1] = head + json.dumps(self.FIELDS, ensure_ascii=False, indent=4) item = ''.join(item).encode() item = bytes(item) + b';\n' write_file.write(item) @staticmethod def img2base(img_path: str, file_name: str) -> str: """ 接受传递进函数的filename 并找到文件转换为base64格式 :param img_path: 通过文件名及默认路径找到的img绝对路径 :param file_name: 用户在装饰器中传递进来的问价匿名 :return: """ pattern = '/' if platform != 'Windows' else '\\' with open(img_path + pattern + file_name, 'rb') as file: data = file.read() return base64.b64encode(data).decode() def add_test_img(*pargs): """ 接受若干个图片元素, 并展示在测试报告中 :param pargs: :return: """ def _wrap(func): @wraps(func) def __wrap(*args, **kwargs): img_path = os.path.abspath('{}'.format(BeautifulReport.img_path)) try: result = func(*args, **kwargs) except Exception: if 'save_img' in dir(args[0]): save_img = getattr(args[0], 'save_img') save_img(func.__name__) data = BeautifulReport.img2base(img_path, pargs[0] + '.png') print(HTML_IMG_TEMPLATE.format(data, data)) sys.exit(0) print('

') if len(pargs) > 1: for parg in pargs: print(parg + ':') data = BeautifulReport.img2base(img_path, parg + '.png') print(HTML_IMG_TEMPLATE.format(data, data)) return result if not os.path.exists(img_path + pargs[0] + '.png'): return result data = BeautifulReport.img2base(img_path, pargs[0] + '.png') print(HTML_IMG_TEMPLATE.format(data, data)) return result return __wrap return _wrap

然后就ok了 

 

时间戳 2019年11月12日

那个上面饼图统计那个改好了

    def stopTestRun(self, title=None) -> dict:
        """
            所有测试执行完成后, 执行该方法
        :param title:
        :return:
        """
        FIELDS['testPass'] = self.success_counter
        for item in self.result_list:
            item = json.loads(str(MakeResultJson(item)))
            FIELDS.get('testResult').append(item)
        FIELDS['testAll'] = len(self.result_list)
        err_unm = 0
        for case_info in self.result_list:
            if case_info[4] == '失败':
                err_unm += 1
        FIELDS['testName'] = title if title else self.default_report_name
        FIELDS['testFail'] = err_unm
        FIELDS['beginTime'] = self.begin_time
        end_time = int(time.time())
        start_time = int(time.mktime(time.strptime(self.begin_time, '%Y-%m-%d %H:%M:%S')))
        FIELDS['totalTime'] = str(end_time - start_time) + 's'
        FIELDS['testError'] = self.error_count
        FIELDS['testSkip'] = self.skipped
        self.FIELDS = FIELDS
        return FIELDS

 

你可能感兴趣的:(test)