BeautifulReport(自己用得很水)

"""
@Project: BeautifulReport
@Author: Raymond Mocobk
@Data: 2019/03/20
@File: __init__.py.py
@License: MIT
"""

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 = """
    
    
    
    

""" origin_stdout = sys.stdout def output2console(s): """将stdout内容输出到console""" tmp_stdout = sys.stdout sys.stdout = origin_stdout print(s, end='') sys.stdout = tmp_stdout class OutputRedirector(object): """ Wrapper to redirect stdout or stderr """ def __init__(self, fp): self.fp = fp def write(self, s): self.fp.write(s) output2console(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() class PATH: """ all file PATH meta """ template_path = os.path.join(os.path.dirname(__file__), 'template') config_tmp_path = os.path.join(template_path, 'template.html') 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.sys_stdout = None self.sys_stderr = None self.outputBuffer = None self.fields = { "testPass": 0, "testResult": [], "testName": "", "testAll": 0, "testFail": 0, "beginTime": "", "totalTime": "", "testSkip": 0 } @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: """ self.fields['testPass'] = self.success_counter for item in self.result_list: item = json.loads(str(MakeResultJson(item))) self.fields.get('testResult').append(item) self.fields['testAll'] = len(self.result_list) self.fields['testName'] = title if title else self.default_report_name self.fields['testFail'] = self.failure_count self.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'))) self.fields['totalTime'] = str(end_time - start_time) + 's' self.fields['testError'] = self.error_count self.fields['testSkip'] = self.skipped return self.fields def stopTestRun_1(self, title=None) -> dict: """ 所有测试执行完成后, 执行该方法 :param title: :return: """ #修改值 self.fields["testResult"]=[] self.fields['testPass'] = self.success_counter for item in self.result_list: item = json.loads(str(MakeResultJson(item))) self.fields.get('testResult').append(item) self.fields['testAll'] = len(self.result_list) self.fields['testName'] = title if title else self.default_report_name self.fields['testFail'] = self.failure_count self.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'))) self.fields['totalTime'] = str(end_time - start_time) + 's' self.fields['testError'] = self.error_count self.fields['testSkip'] = self.skipped return self.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)) # output traceback info sys.stderr.write(''.join(traceback.format_exception(*err)) + '\n') self.failure_count += 1 self.add_test_type('错误', logs) if self.verbosity > 1: sys.stderr.write('E ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('E') 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.report_dir = None self.title = '自动化测试报告' self.filename = 'report.html' def run_test(self, description, filename: str = None, report_dir='.', log_path=None, theme='theme_default'): """ 生成测试报告,并放在当前运行路径下 :param report_dir: 生成report的文件存储路径 :param filename: 生成文件的filename :param description: 生成文件的注释 :param theme: 报告主题名 theme_default theme_cyan theme_candy theme_memories :return: """ if log_path: import warnings message = ('"log_path" is deprecated, please replace with "report_dir"\n' "e.g. result.report(filename='测试报告_demo', description='测试报告', report_dir='report')") warnings.warn(message) if filename: self.filename = filename if filename.endswith('.html') else filename + '.html' if description: self.title = description self.report_dir = os.path.abspath(report_dir) os.makedirs(self.report_dir, exist_ok=True) res=self.suites.run(result=self) self.stopTestRun(self.title) return res def test_result(self): #这个是处理依赖的时候记录的所以只处理不生成数据 self.suites.run(result=self) self.stopTestRun_1(self.title) def report_1(self,theme='theme_default'): #已经存在了数据 所以不需要生成直接生成就行了 self.output_report(theme) text = '\n测试已全部完成, 可打开 {} 查看报告'.format(os.path.join(self.report_dir, self.filename)) print(text) def report_result(self,theme='theme_default'): self.suites.run(result=self) self.stopTestRun_1(self.title) self.output_report(theme) text = '\n测试已全部完成, 可打开 {} 查看报告'.format(os.path.join(self.report_dir, self.filename)) print(text) def report(self, description, filename: str = None, report_dir='.', log_path=None, theme='theme_default'): """ 生成测试报告,并放在当前运行路径下 :param report_dir: 生成report的文件存储路径 :param filename: 生成文件的filename :param description: 生成文件的注释 :param theme: 报告主题名 theme_default theme_cyan theme_candy theme_memories :return: """ if log_path: import warnings message = ('"log_path" is deprecated, please replace with "report_dir"\n' "e.g. result.report(filename='测试报告_demo', description='测试报告', report_dir='report')") warnings.warn(message) if filename: self.filename = filename if filename.endswith('.html') else filename + '.html' if description: self.title = description self.report_dir = os.path.abspath(report_dir) os.makedirs(self.report_dir, exist_ok=True) self.suites.run(result=self) self.stopTestRun(self.title) self.output_report(theme) text = '\n测试已全部完成, 可打开 {} 查看报告'.format(os.path.join(self.report_dir, self.filename)) print(text) def output_report(self, theme): """ 生成测试报告到指定路径下 :return: """ def render_template(params: dict, template: str): for name, value in params.items(): name = '${' + name + '}' template = template.replace(name, value) return template template_path = self.config_tmp_path with open(os.path.join(self.template_path, theme + '.json'), 'r',encoding="utf-8") as theme: render_params = { **json.load(theme), 'resultData': json.dumps(self.fields, ensure_ascii=False, indent=4) } override_path = os.path.abspath(self.report_dir) if \ os.path.abspath(self.report_dir).endswith('/') else \ os.path.abspath(self.report_dir) + '/' with open(template_path, 'rb') as file: body = file.read().decode('utf-8') with open(override_path + self.filename, 'w', encoding='utf-8', newline='\n') as write_file: html = render_template(render_params, body) write_file.write(html) @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

 

你可能感兴趣的:(BeautifulReport(自己用得很水))