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')
五、报告样式
Python&Selenium&Unittest&BeautifuReport 自动化测试并生成HTML自动化测试报告的更多相关文章
- Java&Selenium&TestNG&ZTestReport 自动化测试并生成HTML自动化测试报告
一.摘要 本篇博文将介绍如何借助ZTestReport和HTML模版,生成HTML测试报告的ZTestReport 源码Clone地址为 https://github.com/zhangfei1984 ...
- python+selenium+unittest 实现自动化测试
示例代码: baidu.py import csv #导入csv模块 from itertools import islice #从itertools导入islice,后边让其默认跳过第一行使用 fr ...
- Python+Selenium+Unittest+Ddt+HTMLReport分布式数据驱动自动化测试框架结构
1.Business:公共业务模块,如登录模块,可以把登录模块进行封装供调用 ------login_business.py from Page_Object.Common_Page.login_pa ...
- Python+selenium+unittest+HTMLTestReportCN单元测试框架分享
分享一个比较基础的,系统性的知识点.Python+selenium+unittest+HTMLTestReportCN单元测试框架分享 Unittest简介 unittest是Python语言的单元测 ...
- Python HTMLTestRunner生成网页自动化测试报告时中文编码报错UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6
1. 由于使用Python Selenium做网页自动化测试时,有截取网页上的中文信息保存到测试结果中,最终出现编码错误如下: File "D:/PycharmProjects/AutoTe ...
- python+selenium +unittest生成HTML测试报告
python+selenium+HTMLTestRunner+unittest生成HTML测试报告 首先要准备HTMLTestRunner文件,官网的HTMLTestRunner是python2语法写 ...
- Python Selenium unittest+HTMLTestRunner实现 自动化测试及发送测试报告邮件
1.UI测试框架搭建-目录结构 2. 文件介绍 2.1.baseinfo->__init__.py 配置文件定义基础参数 #-*-coding:utf-8-*- #测试用例配置参数 base_u ...
- Python+appium+unittest UI自动化测试
什么是UI自动化 自动化分层 单元自动化测试,指对软件中最小可测试单元进行检查和验证,一般需要借助单元测试框架,如java的JUnit,python的unittest等 接口自动化测试,主要检查验证模 ...
- Python单元测试unittest与HTMLTestRunner报告生成
本文为简单介绍,使用python自带模块unittest来进行单元测试 首先我们有一个需要测试的类,employee.py 定义了涨薪的方法.我们需要测试这个类的功能是否正确. class Empl ...
随机推荐
- java、python、golang等开发语言如何快速生成二维码?
免费二维码生成途径非常多!比如比较有名的草料二维码,如果只是简单的使用,用它就足够了.但是如果想大规模的生成,那就不太合适了.再者很多工具都没办法在二维码中加入logo(像微信二维码一样). 接下来, ...
- Python Elasticsearch
以下所用版本为Elasticsearch 7.2.0 1.安装 pip3 install elasticsearch -i https://pypi.tuna.tsinghua.edu.cn/simp ...
- EMR-LDAP配置
usersync是负责在配置policy的时候可选用户有ldap里的用户,admin是负责登录webui的 https://cwiki.apache.org/confluence/display/RA ...
- SQL SERVER 字符串函数 REPLACE()
定义: REPLACE()返回用另一个字符串值替换原字符串中出现的所有指定字符串值之后的字符串. 语法: REPLACE ( string_expression , string_pattern , ...
- tabs 导航 及内容切换
<!-- 导航头 --> <div class="col-md-6" style="padding: 0px"> <ul id=& ...
- 在django中进行后台管理时插入外键数据时不显示值的问题
在django的后台管理站点插入数据时,发现需要添加外键时,下拉框中不显示值 按照显示内容中的object,考虑这里应该是调用的模型类的objects对象方法,那么去models.py中对模型类添加一 ...
- APM之原理篇
APM,应用性能监控,有new relic等产品,对APM感兴趣的应该不会不知道它了.主要功能就是统计分析应用的CPU.内存.网络.数据库.UI等性能,并提供错误日志捕获.编码人员需要做的仅仅是使用它 ...
- Java web server 基本实现原理
public class WebServer { //服务端Socket只要一个,所以定义成static, 同一时间只能一个线程访问(主线程) private static ServerSocket ...
- Dijstra_优先队列_前向星
Dijstra算法求最短路径 具体实现方式 设置源点,将源点从原集u{}中取出并放入新建集s{} 找出至源点最近的点q从原集取出放入新集s{} 由q点出发,更新所有由q点能到达的仍处于原集的点到源点的 ...
- PHP获取今日、昨日、本周、上周、本月、上月、本季、上季、今年、去年
//今天开始$beginToday = date('Y-m-d 00:00:00', time());//今天结束$endToday = date('Y-m-d 23:59:59', time()); ...