1. 程式人生 > >Python&Selenium&Unittest&BeautifuReport 自動化測試並生成HTML自動化測試報告

Python&Selenium&Unittest&BeautifuReport 自動化測試並生成HTML自動化測試報告

一、摘要

本篇博文將介紹如何藉助BeautifulReport和HTML模版,生成HTML測試報告的BeautifulReport 原始碼Clone地址為 https://github.com/TesterlifeRaymond/BeautifulReport,其中

BeautifulReport.py和其template是我們需要的關鍵。

二、BeautifulReport

如下程式碼是BeautifulReport.py的原始碼,其中幾個註釋的地方需要注意,將其整合進自己的自動化框架時需要做相應的修改

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 = """ <a href="data:image/png;base64, {}"> <img src="data:image/png;base64, {}" width="800px" height="500px"/> </a> <br></br>
""" 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 = 'D:\\Programs\\Python\\PythonUnittest\\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) FIELDS['testName'] = title if title else self.default_report_name FIELDS['testFail'] = self.failure_count 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 # template_path = "D:\\PythonUnittest\\Template\\template" 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('<br></br>') 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

三、template

template檔案是和BeautifulReport.py一起使用的,他將unittest的測試結果按照template的樣式轉換成HTML格式的報告

四、呼叫BeautifulReport

import unittest
from Run.BeautifulReport import BeautifulReport

if __name__ == '__main__':
    test_suite = unittest.defaultTestLoader.discover('TestScripts', pattern='test*.py')
    result = BeautifulReport(test_suite)
    result.report(filename='測試報告', description='測試報告', log_path='D:\\Programs\\Python\\PythonUnittest\\Reports')

 五、報告樣式