At July 22 A.

This commit is contained in:
zhaoyafan 2022-07-22 16:31:27 +08:00
parent 85e265e5c1
commit 62e91c8647
5 changed files with 472 additions and 449 deletions

59
Base/Class/Logger.py Normal file
View File

@ -0,0 +1,59 @@
import logging
class Logger:
logger = None
levels = {
"CRITICAL": logging.CRITICAL, "FATAL": logging.FATAL, "ERROR": logging.ERROR, "WARNING": logging.WARNING,
"WARN": logging.WARN, "INFO": logging.INFO, "DEBUG": logging.DEBUG, "NOTSET": logging.NOTSET,
"D": logging.DEBUG, "I": logging.INFO, "W": logging.WARNING, "E": logging.ERROR,
"F": logging.FATAL
}
def __init__(self, name: str = '', level='DEBUG', fh: dict = None, ch: dict = None):
if fh and (not "format_style" in fh.keys()) and (not '%' in fh["format"]):
fh["format_style"] = '{'
if ch and (not "format_style" in ch.keys()) and (not '%' in ch["format"]):
ch["format_style"] = '{'
self.logger = logging.getLogger(name)
self.logger.setLevel(self.levels[level])
if fh:
fhandler = logging.FileHandler(filename=fh["filename"], mode=fh["mode"], encoding='utf-8')
fhandler.setLevel(self.levels[fh["level"]])
fhandler.setFormatter(
logging.Formatter(fmt=fh["format"], style=['%', fh["format_style"]]["format_style" in fh.keys()]))
self.logger.addHandler(fhandler)
if ch:
chandler = logging.StreamHandler()
chandler.setLevel(self.levels[ch["level"]])
chandler.setFormatter(
logging.Formatter(fmt=ch["format"], style=['%', ch["format_style"]]["format_style" in ch.keys()]))
self.logger.addHandler(chandler)
self.d = self.logger.debug
self.i = self.logger.info
self.w = self.logger.warning
self.e = self.logger.error
self.f = self.logger.fatal
self.c = self.logger.critical
if __name__ == '__main__':
log = Logger(name='test', level='DEBUG',
fh=None and {
"level": 'DEBUG',
"format": '{asctime} - {name} - {levelname[0]}: {message}',
"filename": './test.log',
"mode": 'a'
},
ch={
"level": 'DEBUG',
"format": '{asctime} - {name} - {levelname[0]}: {message}'
})
log.d("调试")
log.i("信息")
log.w("警告")
log.e("错误")
log.f("致命")
log.c("致命")

View File

@ -1,15 +1,17 @@
import time
def time_format(fmt='%Y-%m-%d %H:%M:%S', ts=None):
return time.strftime(fmt, time.localtime(ts))
def time_strftime(fmt='%Y-%m-%d %H:%M:%S', ts=None, utc=False):
return time.strftime(fmt, [time.localtime, time.gmtime][utc](ts))
def time_make(datetime_format='', dt=''):
def time_strptime(datetime_format='', dt=''):
return time.mktime(time.strptime(dt, datetime_format))
if __name__ == '__main__':
# Example.
print(time_format(fmt='%Y年%m月%d%H:%M:%S'))
print(time_format(fmt='%Y年%m月%d%H:%M:%S', ts=1640012345))
print(time_make('%Y-%m-%d %H:%M:%S', '2021-12-20 22:59:05'))
print(time_strftime(fmt='%Y年%m月%d%H:%M:%S'))
print(time_strftime(fmt='%Y年%m月%d%H:%M:%S', ts=1640012345))
print(time_strftime(fmt='%H:%M:%S', ts=120, utc=True))
print(time_strptime('%Y-%m-%d %H:%M:%S', '2021-12-20 22:59:05'))

View File

@ -1,4 +1,3 @@
# coding=utf-8
"""
A TestRunner for use with the Python unit testing framework. It
generates a HTML report to show the result at a glance.
@ -63,123 +62,12 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
# URL: http://tungwaiyip.info/software/HTMLTestRunner.html
# URL: https://github.com/Gelomen/HTMLTestReportCN-ScreenShot
__author__ = "Wai Yip Tung, Findyou, boafantasy, Gelomen"
__version__ = "1.2.0"
"""
Change History
Version 1.3.0 -- Gelomen
* 增加初始化报告目录自定义
* 升级版本
* 优化命名
Version 1.2.0 -- Gelomen
* 优化用例说明显示
* 错误和失败报告里可以放入多张截图
Version 1.1.0 -- Gelomen
* 优化报告截图写入方式
Version 1.0.2 -- Gelomen
* 新增测试结果统计饼图
* 优化筛选时只显示预览
Version 1.0.1 -- Gelomen
* 修复报告存入文件夹的bug
* 优化报告的命名方式
Version 1.0.0 -- Gelomen
* 修改测试报告文件夹路径的获取方式
* 修改截图获取文件夹路径的获取方式
Version 0.9.9 -- Gelomen
* 优化报告文件夹命名
* 优化截图存放的目录
* 增加图片阴影边框以突出图片
* 优化 失败用例合集 错误用例合集 显示的颜色
Version 0.9.8 -- Gelomen
* 优化回到顶部按钮的显示方式
Version 0.9.7 -- Gelomen
* 优化截图显示滚动页面会固定居中
Version 0.9.6 -- Gelomen
* 新增打开图片的特效可以直接在当前页面看截图
Version 0.9.5 -- Gelomen
* heading新增 失败 错误 测试用例合集
Version 0.9.4 -- Gelomen
* 修复失败和错误用例里对应按钮的颜色
Version 0.9.3 -- Gelomen
* 修复点击失败或错误按钮后浏览器版本和截图的列不会隐藏的bug
Version 0.9.2 -- Gelomen
* 美化 浏览器版本 截图 的显示
Version 0.9.1 -- Gelomen
* 使用UI自动化测试时增加 错误失败 详细信息的 浏览器类型和版本
Version 0.9.0 -- Gelomen
* 可通过 `need_screenshot=1` 作为开关将报告开启截图功能
* 增加 失败 错误 详细信息的 截图链接
Version 0.8.4 -- Gelomen
* 删除 失败模块 的显示
Version 0.8.3 -- Gelomen
* 修复 测试结果 的筛选
* 优化 失败错误 小图标的颜色
* 增加表格 最后一列 的显示以美化表格
Version 0.8.2.1 -Findyou
* 改为支持python3
Version 0.8.2.1 -Findyou
* 支持中文汉化
* 调整样式美化需要连入网络使用的百度的Bootstrap.js
* 增加 通过分类显示测试人员通过率的展示
* 优化详细收起状态的变换
* 增加返回顶部的锚点
Version 0.8.2
* Show output inline instead of popup window (Viorel Lupu).
Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.
Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
Version in 0.7.1
* Back port to Python 2.3 (Frank Horowitz).
* Fix missing scroll bars in detail log (Podi).
"""
# TODO: color stderr
# TODO: simplify javascript using ,ore than 1 class in the class attribute?
import datetime
import io
import time
import unittest
import os, re, sys, io, time, datetime, unittest, logging
from xml.sax import saxutils
import sys
import os
import re
# 全局变量 -- Gelomen
_global_dict = {}
# 让新建的报告文件夹路径存入全局变量 -- Gelomen
class GlobalMsg(object):
def __init__(self):
global _global_dict
@ -281,18 +169,18 @@ class Template_mixin(object):
DEFAULT_TITLE = '测试报告'
DEFAULT_DESCRIPTION = ''
DEFAULT_TESTER = 'QA'
DEFAULT_TESTER = 'Tester'
# ------------------------------------------------------------------------
# HTML Template
HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
# 网页模板开始
# 网页模板,变量列表 title, generator, stylesheet, heading, report, ending
HTML_TMPL = r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%(title)s</title>
<meta name="generator" content="%(generator)s"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
@ -311,6 +199,8 @@ class Template_mixin(object):
$(this).addClass("btn-danger")
}else if(text == "错误") {
$(this).addClass("btn-warning")
}else if(text == "通过") {
$(this).addClass("btn-success")
}
});
@ -356,10 +246,16 @@ class Template_mixin(object):
// 改变窗口大小时自动改变饼图的边距 -- Gelomen
var browserWidth = $(window).width();
var margin_left = browserWidth/2 - 450;
if(margin_left <= 240){
if(margin_left <= 0){
$("#container").css("margin", "auto");
$("#container").css("float", "left");
$("#container").css("width", "100%%");
$("#testinfo").css("width", "100%%");
}else {
$("#container").css("margin-left", margin_left + "px");
$("#container").css("float", "right");
$("#container").css("width", "450px");
$("#testinfo").css("width", "30%%");
}
});
@ -378,7 +274,6 @@ class Template_mixin(object):
$("#toTop").click(function() {
$("html,body").animate({"scrollTop":0}, 700)
})
// 增加饼状图 -- Gelomen
$('#container').highcharts({
chart: {
@ -396,11 +291,11 @@ class Template_mixin(object):
}
},
title: {
floating:true,
floating: true,
text: '测试结果占比'
},
tooltip: {
pointFormat: '{series.name}: <b>{point.percentage:.1f}%%</b>'
pointFormat: '<text style="font-size:10px">{series.name}: {point.percentage:.1f}%%</text>'
},
plotOptions: {
pie: {
@ -430,21 +325,15 @@ class Template_mixin(object):
innerSize: '80%%',
name: '比例',
data: [
['通过', %(Pass)s],
{
name: '失败',
y: %(fail)s,
sliced: true,
selected: true
},
['错误', %(error)s]
['通过', %(Pass)s], ['失败', %(fail)s], ['错误', %(error)s]
]
}]
}, function(c) {
// 环形圆心
// 环形圆心
var centerY = c.series[0].center[1],
titleHeight = parseInt(c.title.styles.fontSize);
c.setTitle({
x:0,
y:centerY + titleHeight/2
});
chart = c;
@ -482,8 +371,10 @@ function showCase(level) {
else {
tr.className = '';
// 切换筛选时只显示预览 -- Gelomen
// 失败
$("div[id^='div_ft']").attr("class", "collapse");
$("div[id^='div_et']").attr("class", "collapse");
$("div[id^='div_pt']").attr("class", "collapse");
}
}
if (id.substr(0,2) == 'pt') {
@ -493,8 +384,10 @@ function showCase(level) {
else {
tr.className = '';
// 切换筛选时只显示预览 -- Gelomen
// 通过
$("div[id^='div_ft']").attr("class", "collapse");
$("div[id^='div_et']").attr("class", "collapse");
$("div[id^='div_pt']").attr("class", "collapse");
}
}
if (id.substr(0,2) == 'et') {
@ -504,8 +397,10 @@ function showCase(level) {
else {
tr.className = '';
// 切换筛选时只显示预览 -- Gelomen
// 错误
$("div[id^='div_ft']").attr("class", "collapse");
$("div[id^='div_et']").attr("class", "collapse");
$("div[id^='div_pt']").attr("class", "collapse");
}
}
}
@ -513,7 +408,7 @@ function showCase(level) {
//加入详细切换文字变化 --Findyou
detail_class=document.getElementsByClassName('detail');
//console.log(detail_class.length)
if (level == 3) {
if (level == 4) {
for (var i = 0; i < detail_class.length; i++){
detail_class[i].innerHTML="收起"
}
@ -570,11 +465,12 @@ function html_escape(s) {
%(heading)s
%(report)s
%(ending)s
<div style='width:auto;height:24px;line-height:24px;border:1px solid #e3e3e3;text-align:center'><a href='#' style='color:#505050'></a></div>
</body>
</html>
"""
# variables: (title, generator, stylesheet, heading, report, ending)
# 网页模板结束
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Stylesheet
@ -583,36 +479,36 @@ function html_escape(s) {
# <link rel="stylesheet" href="$url" type="text/css">
STYLESHEET_TMPL = """
<style type="text/css" media="screen">
body { font-family: Microsoft YaHei;padding: 20px; font-size: 100%; }
table { font-size: 100%; }
.table tbody tr td{
<style type="text/css" media="screen">
body { font-family: Microsoft YaHei;padding: 20px; font-size: 100%; }
table { font-size: 100%; }
.table tbody tr td{
vertical-align: middle;
}
/* -- heading ---------------------------------------------------------------------- */
.heading .description, .attribute {
/* -- heading ---------------------------------------------------------------------- */
.heading .description, .attribute {
clear: both;
}
}
/* --- 失败和错误合集样式 -- Gelomen --- */
.failCollection, .errorCollection {
width: 100px;
/* --- 失败和错误合集样式 -- Gelomen --- */
.failCollection, .errorCollection {
width: auto;
float: left;
}
#failCaseOl li {
}
#failedCaseOl li {
color: red
}
#errorCaseOl li {
}
#errorsCaseOl li {
color: orange
}
}
/* --- 打开截图特效样式 -- Gelomen --- */
.data-img{
/* --- 打开截图特效样式 -- Gelomen --- */
.data-img{
cursor:pointer
}
}
.pic_looper{
.pic_looper{
width:100%;
height:100%;
position: fixed;
@ -622,9 +518,9 @@ table { font-size: 100%; }
background: #000;
display: none;
z-index: 100;
}
}
.pic_show{
.pic_show{
width:100%;
position:fixed;
left:0;
@ -635,93 +531,100 @@ table { font-size: 100%; }
text-align: center;
display: none;
z-index: 100;
}
}
.pic_box{
.pic_box{
padding:10px;
width:90%;
height:90%;
margin:40px auto;
text-align: center;
overflow: hidden;
}
}
.pic_box img{
.pic_box img{
width: auto;
height: 100%;
-moz-box-shadow: 0px 0px 20px 0px #000;
-webkit-box-shadow: 0px 0px 20px 0px #000;
box-shadow: 0px 0px 20px 0px #000;
}
}
/* --- 饼状图div样式 -- Gelomen --- */
#container {
/* --- 饼状图div样式 -- Gelomen --- */
#container {
max-width: 100%;
width: 450px;
height: 300px;
float: left;
}
float: right;
}
/* -- report ------------------------------------------------------------------------ */
#total_row { font-weight: bold; }
.passCase { color: #5cb85c; }
.failCase { color: #d9534f; font-weight: bold; }
.errorCase { color: #f0ad4e; font-weight: bold; }
.hiddenRow { display: none; }
.testcase { margin-left: 2em; }
.screenshot:link { text-decoration: none;color: deeppink; }
.screenshot:visited { text-decoration: none;color: deeppink; }
.screenshot:hover { text-decoration: none;color: darkcyan; }
.screenshot:active { text-decoration: none;color: deeppink; }
</style>
"""
/* -- report ------------------------------------------------------------------------ */
#total_row { font-weight: bold; }
.passedCase { color: #3FB83F; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; font-size: 14px; font-weight: bold; }
.failedCase { color: #D9433F; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; font-size: 14px; font-weight: bold; }
.errorsCase { color: #F0A02F; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; font-size: 14px; font-weight: bold; }
.hiddenRow { display: none; }
.testcase { margin-left: 1em; word-break: break-all; white-space: pre-wrap; }
.screenshot:link { text-decoration: none;color: deeppink; }
.screenshot:visited { text-decoration: none;color: deeppink; }
.screenshot:hover { text-decoration: none;color: darkcyan; }
.screenshot:active { text-decoration: none;color: deeppink; }
</style>
"""
# ------------------------------------------------------------------------
# Heading
#
# 添加显示截图 和 饼状图 的div -- Gelomen
HEADING_TMPL = """<div class='pic_looper'></div> <div class='pic_show'><div class='pic_box'><img src=''/></div> </div>
<div class='heading'>
<div style="width: 650px; float: left;">
# 头部信息开始
# 添加显示截图和统计图div变量列表 title, parameters, description
HEADING_TMPL = """
<div class='pic_looper'></div>
<div class='pic_show'>
<div class='pic_box'>
<img src=''/>
</div>
</div>
<div class='heading'>
<div id="testinfo" style="max-width:515px ;width: auto; float: left;">
<h1 style="font-family: Microsoft YaHei">%(title)s</h1>
%(parameters)s
<p class='description'>%(description)s</p>
</div>
<div id="container"></div>
</div>
</div>
<div id="container"></div>
</div>
"""
""" # variables: (title, parameters, description)
HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s : </strong> %(value)s</p>
""" # variables: (name, value)
# 测试信息模板,变量列表 name, value
HEADING_ATTRIBUTE_TMPL = """
<p class='attribute'><strong>%(name)s : </strong> %(value)s</p>
"""
# 头部信息结束
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Report
#
# 汉化,加美化效果 --Findyou
# 报告模板开始
# 变量列表 test_list, count, Pass, fail, error ,passrate
REPORT_TMPL = """
<div style="width: 500px; clear: both;">
<p id='show_detail_line'>
<a class="btn btn-primary" href='javascript:showCase(0)'>概要{ %(passrate)s }</a>
<a class="btn btn-success" href='javascript:showCase(2)'>通过{ %(Pass)s }</a>
<a class="btn btn-danger" href='javascript:showCase(1)'>失败{ %(fail)s }</a>
<a class="btn btn-warning" href='javascript:showCase(3)'>错误{ %(error)s }</a>
<a class="btn btn-info" href='javascript:showCase(4)'>所有{ %(count)s }</a>
</p>
</div>
<table id='result_table' class="table table-condensed table-bordered table-hover">
<colgroup>
<col align='left' style="width: 300px;"/>
<col align='right' style="width: 300px;"/>
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' style="width: 200px;"/>
</colgroup>
<tr id='header_row' class="text-center success" style="font-weight: bold;font-size: 14px;">
<td>用例集/测试用例</td>
<div style="width: auto; clear: both;">
<p id='show_detail_line'>
<a class="btn btn-primary" href='javascript:showCase(0)'>概要 %(passrate)s</a>
<a class="btn btn-success" href='javascript:showCase(2)'>通过 %(Pass)s</a>
<a class="btn btn-danger" href='javascript:showCase(1)'>失败 %(fail)s</a>
<a class="btn btn-warning" href='javascript:showCase(3)'>错误 %(error)s</a>
<a class="btn btn-info" href='javascript:showCase(4)'>全部 %(count)s</a>
</p>
</div>
<table id='result_table' class="table table-condensed table-bordered table-hover">
<colgroup>
<col align='left' style="width: 300px;"/>
<col align='right' style="width: 285px;"/>
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' style="width: 200px;"/>
</colgroup>
<tr id='header_row' class="text-center success" style="font-weight: bold;font-size: 14px;">
<td>测试用例</td>
<td>说明</td>
<td>总计</td>
<td>通过</td>
@ -729,22 +632,23 @@ table { font-size: 100%; }
<td>错误</td>
<td>耗时</td>
<td>详细</td>
</tr>
%(test_list)s
<tr id='total_row' class="text-center active">
</tr>
%(test_list)s
<tr id='total_row' class="text-center active">
<td colspan='2'>总计</td>
<td>%(count)s</td>
<td>%(Pass)s</td>
<td>%(fail)s</td>
<td>%(error)s</td>
<td>%(time_usage)s</td>
<td>通过%(passrate)s</td>
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error ,passrate)
<td>通过%(passrate)s</td>
</tr>
</table>
"""
REPORT_CLASS_TMPL = r"""
<tr class='%(style)s warning'>
# 变量列表 style, desc, count, Pass, fail, error, cid
REPORT_CLASS_TMPL = """
<tr class='%(style)s warning'>
<td>%(name)s</td>
<td>%(doc)s</td>
<td class="text-center">%(count)s</td>
@ -752,80 +656,67 @@ table { font-size: 100%; }
<td class="text-center">%(fail)s</td>
<td class="text-center">%(error)s</td>
<td class="text-center">%(time_usage)s</td>
<td class="text-center"><a href="javascript:showClassDetail('%(cid)s',%(count)s)" class="detail" id='%(cid)s'>详细</a></td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)
<td class="text-center"><a href="javascript:showClassDetail('%(cid)s',%(count)s)" class="detail" id='%(cid)s'>查看全部</a></td>
</tr>
"""
# 失败 的样式去掉原来JS效果美化展示效果 -Findyou / 美化类名上下居中,有截图列 -- Gelomen
REPORT_TEST_WITH_OUTPUT_TMPL_1 = r"""
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s' style="vertical-align: middle"><div class='testcase'>%(name)s</div></td>
<td style="vertical-align: middle">%(doc)s</td>
<td colspan='5' align='center'>
<!--默认收起错误信息 -Findyou
<button id='btn_%(tid)s' type="button" class="btn btn-xs collapsed" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
<div id='div_%(tid)s' class="collapse"> -->
<!-- 默认展开错误信息 -Findyou / 修复失败按钮的颜色 -- Gelomen -->
<button id='btn_%(tid)s' type="button" class="btn btn-xs" data-toggle="collapse" data-target='#div_%(tid)s,#div_%(tid)s_screenshot'>%(status)s</button>
<div id='div_%(tid)s' class="collapse in">
<pre style="text-align:left">
%(script)s
</pre>
</div>
</td>
<td class="text-center" style="vertical-align: middle"><div id='div_%(tid)s_screenshot' class="collapse in">浏览器版本<div style="color: brown;">%(browser)s</div></br>截图%(screenshot)s</div></td>
</tr>
""" # variables: (tid, Class, style, desc, status)
# 失败 的样式去掉原来JS效果美化展示效果 -Findyou / 美化类名上下居中,无截图列 -- Gelomen
REPORT_TEST_WITH_OUTPUT_TMPL_0 = r"""
# 失败样式(有截图列),变量列表 tid, Class, style, desc, status
REPORT_TEST_WITH_OUTPUT_TMPL_1 = """
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s' style="vertical-align: middle"><div class='testcase'>%(name)s</div></td>
<td style="vertical-align: middle">%(doc)s</td>
<td colspan='5' align='center'>
<!--默认收起错误信息 -Findyou
<button id='btn_%(tid)s' type="button" class="btn btn-xs collapsed" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
<div id='div_%(tid)s' class="collapse"> -->
<!-- 默认展开错误信息 -Findyou / 修复失败按钮的颜色 -- Gelomen -->
<button id='btn_%(tid)s' type="button" class="btn btn-xs" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
<button id='btn_%(tid)s' type="button" class="btn btn-xs" data-toggle="collapse" data-target='#div_%(tid)s,#div_%(tid)s_screenshot'>%(status)s</button>
<div id='div_%(tid)s' class="collapse in">
<pre style="text-align:left">
<pre style="text-align:left;font-size:12px;color:#e52000">
%(script)s
</pre>
</div>
</td>
<td class='%(style)s' style="vertical-align: middle"></td>
<td class="text-center" style="vertical-align: middle"><div id='div_%(tid)s_screenshot' class="collapse in">浏览器<div style="color: brown;">%(browser)s</div></br>截图%(screenshot)s</div></td>
</tr>
""" # variables: (tid, Class, style, desc, status)
# 通过 的样式,加标签效果 -Findyou / 美化类名上下居中 -- Gelomen
REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s' style="vertical-align: middle"><div class='testcase'>%(name)s</div></td>
<td style="vertical-align: left">%(doc)s</td>
<td colspan='5' align='center'><span class="label label-success success">%(status)s</span></td>
<td class='%(style)s' style="vertical-align: middle"></td>
</tr>
""" # variables: (tid, Class, style, desc, status)
REPORT_TEST_OUTPUT_TMPL = r"""
%(id)s: %(output)s
""" # variables: (id, output)
# ------------------------------------------------------------------------
# ENDING
#
# 增加返回顶部按钮 --Findyou
ENDING_TMPL = """<div id='ending'>&nbsp;</div>
<div id="toTop" style=" position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer; display: none">
<a><span class="glyphicon glyphicon-eject" style = "font-size:30px;" aria-hidden="true">
</span></a></div>
"""
# 失败样式(无截图列),变量列表 tid, Class, style, desc, status
REPORT_TEST_WITH_OUTPUT_TMPL_0 = """
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s' style="vertical-align: middle"><div class='testcase'>%(name)s</div></td>
<td style="vertical-align: middle">%(doc)s</td>
<td colspan='5' align='center'>
<button id='btn_%(tid)s' type="button" class="btn btn-xs" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
<div id='div_%(tid)s' class="collapse in">
<pre style="text-align:left;font-size:12px;color:#e52000">%(script)s</pre>
</div>
</td>
<td class='%(style)s' style="vertical-align: middle"></td>
</tr>
"""
# -------------------- The end of the Template class -------------------
# 通过样式,变量列表 tid, Class, style, desc, status
REPORT_TEST_NO_OUTPUT_TMPL = """
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s' style="vertical-align: middle"><div class='testcase'>%(name)s</div></td>
<td style="vertical-align: left">%(doc)s</td>
<td colspan='5' align='center'><button type="button" class="btn btn-xs">%(status)s</button></td>
<td class='%(style)s' style="vertical-align: middle"></td>
</tr>
"""
# 测试输出内容
REPORT_TEST_OUTPUT_TMPL = '%(id)s:' + "\n" + '%(output)s'
# 返回顶部按钮
ENDING_TMPL = """
<div id='ending'>&nbsp;</div>
<div id="toTop" style="position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer; display: none">
<a>
<span class="glyphicon glyphicon-eject" style = "font-size:28px;color:#b0b0b0" aria-hidden="true">
</span>
</a>
</div>
"""
# 报告模板结束
# ------------------------------------------------------------------------
TestResult = unittest.TestResult
@ -836,13 +727,14 @@ class _TestResult(TestResult):
# It lacks the output and reporting ability compares to unittest._TextTestResult.
def __init__(self, verbosity=1):
TestResult.__init__(self)
super().__init__(verbosity=verbosity)
self.verbosity = verbosity
self.outputBuffer = None
self.stdout0 = None
self.stderr0 = None
self.success_count = 0
self.failure_count = 0
self.error_count = 0
self.verbosity = verbosity
self.passed_count = 0
self.failed_count = 0
self.errors_count = 0
# result is a list of result in 4 tuple
# (
@ -852,13 +744,14 @@ class _TestResult(TestResult):
# stack trace,
# )
self.result = []
# 增加一个测试通过率 --Findyou
self.passrate = float(0)
# 增加失败用例合集
self.failCase = ""
self.failedCase = ""
# 增加错误用例合集
self.errorCase = ""
self.errorsCase = ""
self.logger = logging.getLogger('test')
def startTest(self, test):
stream = sys.stderr
@ -866,7 +759,7 @@ class _TestResult(TestResult):
# stream.write(stdout_content)
# stream.flush()
# stream.write("\n")
TestResult.startTest(self, test)
super().startTest(test)
# just one buffer for both stdout and stderr
self.outputBuffer = io.StringIO()
stdout_redirector.fp = self.outputBuffer
@ -875,48 +768,57 @@ class _TestResult(TestResult):
self.stderr0 = sys.stderr
sys.stdout = stdout_redirector
sys.stderr = stderr_redirector
self.test_start_time = round(time.time(), 2)
self.stime = round(time.time(), 2)
self.loggerStream = io.StringIO()
self.ch = logging.StreamHandler(self.loggerStream)
self.ch.setLevel(logging.DEBUG)
self.ch.setFormatter(
logging.Formatter('%(asctime)s - %(name)s -%(levelname)s -%(process)d -%(processName)s - %(message)s'))
self.logger.addHandler(self.ch)
def complete_output(self):
"""
Disconnect output redirection and return buffer.
Safe to call multiple times.
"""
self.test_end_time = round(time.time(), 2)
self.etime = round(time.time(), 2)
if self.stdout0:
sys.stdout = self.stdout0
sys.stderr = self.stderr0
self.stdout0 = None
self.stderr0 = None
return self.outputBuffer.getvalue()
return self.loggerStream.getvalue() + self.outputBuffer.getvalue()
def stopTest(self, test):
# Usually one of addSuccess, addError or addFailure would have been called.
# But there are some path in unittest that would bypass this.
# We must disconnect stdout in stopTest(), which is guaranteed to be called.
self.complete_output()
# 移除日志Handler
self.logger.removeHandler(self.ch)
def addSuccess(self, test):
self.success_count += 1
TestResult.addSuccess(self, test)
self.passed_count += 1
super().addSuccess(test)
output = self.complete_output()
use_time = round(self.test_end_time - self.test_start_time, 2)
self.result.append((0, test, output, '', use_time))
utime = round(self.etime - self.stime, 2)
self.result.append((0, test, output, '', utime))
if self.verbosity > 1:
sys.stderr.write(' S ')
sys.stderr.write('Passed: ')
sys.stderr.write(str(test))
sys.stderr.write('\n')
else:
sys.stderr.write(' S ')
sys.stderr.write('\n')
sys.stderr.write(str(test.__class__.__doc__))
def addError(self, test, err):
self.error_count += 1
TestResult.addError(self, test, err)
self.errors_count += 1
super().addError(test, err)
_, _exc_str = self.errors[-1]
output = self.complete_output()
use_time = round(self.test_end_time - self.test_start_time, 2)
self.result.append((2, test, output, _exc_str, use_time))
utime = round(self.etime - self.stime, 2)
self.result.append((2, test, output, _exc_str, utime))
if self.verbosity > 1:
sys.stderr.write(' E ')
sys.stderr.write(str(test))
@ -925,16 +827,16 @@ class _TestResult(TestResult):
sys.stderr.write(' E ')
sys.stderr.write('\n')
# 添加收集错误用例名字 -- Gelomen
self.errorCase += "<li>" + str(test) + "</li>"
# 收集错误测试用例名称以在测试报告中显示
self.errorsCase += "<li>" + str(test) + "</li>"
def addFailure(self, test, err):
self.failure_count += 1
TestResult.addFailure(self, test, err)
self.failed_count += 1
super().addFailure(test, err)
_, _exc_str = self.failures[-1]
output = self.complete_output()
use_time = round(self.test_end_time - self.test_start_time, 2)
self.result.append((1, test, output, _exc_str, use_time))
utime = round(self.etime - self.stime, 2)
self.result.append((1, test, output, _exc_str, utime))
if self.verbosity > 1:
sys.stderr.write(' F ')
sys.stderr.write(str(test))
@ -943,8 +845,8 @@ class _TestResult(TestResult):
sys.stderr.write(' F ')
sys.stderr.write('\n')
# 添加收集失败用例名字 -- Gelomen
self.failCase += "<li>" + str(test) + "</li>"
# 收集失败测试用例名称以在测试报告中显示
self.failedCase += "<li>" + str(test) + "</li>"
# 新增 need_screenshot 参数,-1为无需截图否则需要截图 -- Gelomen
@ -1003,47 +905,47 @@ class HTMLTestRunner(Template_mixin):
Override this to add custom attributes.
"""
startTime = str(self.startTime)[:19]
duration = str(self.stopTime - self.startTime)
duration = time.strftime('%H:%M:%S', time.gmtime((self.stopTime - self.startTime).seconds))
status = []
status.append('%s' % (result.success_count + result.failure_count + result.error_count))
if result.success_count:
status.append('通过 %s' % result.success_count)
if result.failure_count:
status.append('失败 %s' % result.failure_count)
if result.error_count:
status.append('错误 %s' % result.error_count)
status.append('%s' % (result.passed_count + result.failed_count + result.errors_count))
if result.passed_count:
status.append('通过 %s' % result.passed_count)
if result.failed_count:
status.append('失败 %s' % result.failed_count)
if result.errors_count:
status.append('错误 %s' % result.errors_count)
if status:
status = ''.join(status)
if (result.success_count + result.failure_count + result.error_count) > 0:
self.passrate = str("%.2f%%" % (float(result.success_count) / float(
result.success_count + result.failure_count + result.error_count) * 100))
if (result.passed_count + result.failed_count + result.errors_count) > 0:
self.passrate = str("%.2f%%" % (float(result.passed_count) / float(
result.passed_count + result.failed_count + result.errors_count) * 100))
else:
self.passrate = "0.00 %"
else:
status = 'none'
if len(result.failCase) > 0:
failCase = result.failCase
if len(result.failedCase) > 0:
failedCase = result.failedCase
else:
failCase = ""
failedCase = ""
if len(result.errorCase) > 0:
errorCase = result.errorCase
if len(result.errorsCase) > 0:
errorsCase = result.errorsCase
else:
errorCase = ""
errorsCase = ""
return [
('测试人员', self.tester),
('开始时间', startTime),
('合计耗时', duration),
('测试结果', status + ",通过率 = " + self.passrate),
('失败用例合集', failCase),
('错误用例合集', errorCase),
('测试结果', status + ",通过率 " + self.passrate),
('失败用例', failedCase),
('错误用例', errorsCase),
]
def generateReport(self, test, result):
report_attrs = self.getReportAttributes(result)
generator = 'HTMLTestRunner %s' % __version__
generator = 'HTMLTestRunner'
stylesheet = self._generate_stylesheet()
# 添加 通过、失败 和 错误 的统计,以用于饼图 -- Gelomen
Pass = self._generate_report(result)["Pass"]
@ -1075,29 +977,29 @@ class HTMLTestRunner(Template_mixin):
a_lines = []
for name, value in report_attrs:
# 如果是 失败用例 或 错误用例合集,则不进行转义 -- Gelomen
if name == "失败用例合集":
if name == "失败用例":
if value == "":
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=name,
value="<ol style='float: left;'>" + value + "</ol>",
value=value,
)
else:
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=name,
value="<div class='panel-default' style='float: left;'><a class='showDetail' data-toggle='collapse' href='#failCaseOl' style='text-decoration: none;'>点击查看</a></div>"
"<ol id='failCaseOl' class='collapse' style='float: left;'>" + value + "</ol>",
value="<a class='showDetail' data-toggle='collapse' href='#failedCaseOl' style='text-decoration: none;'>点击查看</a>"
"<ol id='failedCaseOl' class='collapse' style='float: left; font-family: Menlo,Monaco,Consolas,monospace;'>" + value + "</ol>",
)
elif name == "错误用例合集":
elif name == "错误用例":
if value == "":
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=name,
value="<ol style='float: left;'>" + value + "</ol>",
value=value,
)
else:
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=name,
value="<div class='panel-default' style='float: left;'><a class='showDetail' data-toggle='collapse' href='#errorCaseOl' style='text-decoration: none;'>点击查看</a></div>"
"<ol id='errorCaseOl' class='collapse' style='float: left;'>" + value + "</ol>",
value="<a class='showDetail' data-toggle='collapse' href='#errorsCaseOl' style='text-decoration: none;'>点击查看</a>"
"<ol id='errorsCaseOl' class='collapse' style='float: left; font-family: Menlo,Monaco,Consolas,monospace;'>" + value + "</ol>",
)
else:
line = self.HEADING_ATTRIBUTE_TMPL % dict(
@ -1159,18 +1061,18 @@ class HTMLTestRunner(Template_mixin):
sum_ns = round(sum_ns, 2)
report = self.REPORT_TMPL % dict(
test_list=''.join(rows),
count=str(result.success_count + result.failure_count + result.error_count),
Pass=str(result.success_count),
fail=str(result.failure_count),
error=str(result.error_count),
count=str(result.passed_count + result.failed_count + result.errors_count),
Pass=str(result.passed_count),
fail=str(result.failed_count),
error=str(result.errors_count),
time_usage=str(sum_ns) + "", # 所有用例耗时
passrate=self.passrate,
)
# 获取 通过、失败 和 错误 的统计并return以用于饼图 -- Gelomen
Pass = str(result.success_count)
fail = str(result.failure_count)
error = str(result.error_count)
Pass = str(result.passed_count)
fail = str(result.failed_count)
error = str(result.errors_count)
return {"report": report, "Pass": Pass, "fail": fail, "error": error}
def _generate_report_test(self, rows, cid, tid, n, t, o, e):
@ -1221,7 +1123,7 @@ class HTMLTestRunner(Template_mixin):
row = tmpl % dict(
tid=tid,
Class=(n == 0 and 'hiddenRow' or 'none'),
style=n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase'),
style=n == 2 and 'errorsCase' or (n == 1 and 'failedCase' or 'passedCase'),
name=name,
doc=doc,
script=script,
@ -1241,7 +1143,7 @@ class HTMLTestRunner(Template_mixin):
row = tmpl % dict(
tid=tid,
Class=(n == 0 and 'hiddenRow' or 'none'),
style=n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase'),
style=n == 2 and 'errorsCase' or (n == 1 and 'failedCase' or 'passedCase'),
name=name,
doc=doc,
script=script,

52
run.py
View File

@ -4,6 +4,39 @@ from test_case import TestDemo
import unittest
from types import FunctionType
# TestDemo3 = unittest.TestCase
# TestDemo3.test_007 = test_007
# TestDemo3.test_007.__module__= TestDemo3
obj = type("TestCases",(unittest.TestCase,),dict())
obj.__doc__ = 'Doc'
obj.test_demo0123456789ABCDEF_000000 = FunctionType(compile('def foo(self): import time; time.sleep(0)', "", "exec").co_consts[0], globals(), "foo")
obj.test_demo1 = FunctionType(compile('def foo(self): return "bar"', "", "exec").co_consts[0], globals(), "foo")
obj.test_demo2 = FunctionType(compile('def foo(self): assert 1 == 2', "", "exec").co_consts[0], globals(), "foo")
obj.test_demo3 = FunctionType(compile('def foo(): return "bar"', "", "exec").co_consts[0], globals(), "foo")
obj.test_demo1.__qualname__ = "test_demo1"
obj.test_demo1.__doc__ = "这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;"
obj.test_demo2.__qualname__ = "test_demo2"
obj.test_demo2.__doc__ = "这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;这是一条测试用例;"
obj.test_demo3.__qualname__ = "test_demo3"
obj.test_demo3.__doc__ = "这是一条测试用例;这是一条测试用例;这是一条测试用例;"
# print(obj)
# foo_code = compile('def foo(): return "bar"', "", "exec")
# print(foo_code)
# foo_func = FunctionType(foo_code.co_consts[0], globals(), "foo")
# foo_func.__qualname__ = "test_demo"
# print(foo_func)
base_path = os.path.dirname(__file__)
report_path = base_path
report_filename = os.path.join(report_path, 'report.html')
@ -12,11 +45,28 @@ case_suite = unittest.TestSuite()
case_suite.addTest(TestDemo('test_one'))
case_suite.addTest(TestDemo('test_two'))
case_suite.addTest(TestDemo('test_tre'))
case_suite.addTest(TestDemo('test_0123456789ABCDEF_123456_000000'))
# case_suite.addTest(obj('test_demo1'))
# case_suite.addTest(obj('test_demo1'))
# case_suite.addTest(obj('test_demo1'))
# case_suite.addTest(obj('test_demo1'))
# case_suite.addTest(obj('test_demo1'))
# case_suite.addTest(obj('test_demo1'))
# case_suite.addTest(obj('test_demo1'))
# case_suite.addTest(obj('test_demo1'))
# case_suite.addTest(obj('test_demo1'))
# case_suite.addTest(obj('test_demo2'))
# case_suite.addTest(obj('test_demo3'))
def start():
with open(report_filename, 'wb') as f:
runner = HTMLTestRunner(stream=f, title='自动化测试报告', verbosity=2, description='描述', tester='Tester')
runner = HTMLTestRunner(stream=f,
title='自动化测试报告',
verbosity=2,
description='描述',
tester='DESKTOP')
runner.run(case_suite)

View File

@ -1,23 +1,33 @@
import unittest
from Base.Class.Logger import *
from Base.Class.Http import *
log = Logger(name='test', level='DEBUG', ch={
"level": 'DEBUG',
"format": '{asctime} - {name} - {levelname[0]}: {message}'
})
class TestDemo(unittest.TestCase):
def test_one(self):
'''
哈哈
ONE用例
:return:
'''
assert 1 == 1
log.i("Info...")
log.w("Warnning...")
def test_two(self):
'''
呵呵
:return:
'''
assert 'H' in 'Hello!'
log.d("正在断言...")
assert 'A' in 'Hello!', '断言失败'
def test_tre(self):
assert 5 == 10, '断言失败'
assert a == 10
def test_0123456789ABCDEF_123456_000000(self):
Request().http('GET',"http://more-md.fanscloud.net/iplookup", proxy="http://127.0.0.1:8888")
pass
TestDemo.test_tre.__doc__ = "测试吖"
if __name__ == '__main__':
unittest.main()