基于Python+Selenium的UI自动化测试已经实现,测试用例也已经上百,现在的问题是,用例太多,顺序执行下来,跑一遍自动化测试,需要超过1小时,时间太长了,所以考虑多线程运行测试。
话不多说,上代码,这是主程序:
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
File Name: runTestcases.py
Description :
Author : 曾良均
QQ: 277099728
Date: 9/27/2021 11:28 AM
-------------------------------------------------
Change Activity:
9/27/2021:
-------------------------------------------------
"""
__author__ = 'ljzeng'
from HTMLTestRunners import *
from sendemail import *
from queryMSSQL import commQuery
import threadpool
global all_result
def all_testcase():
# 待执行测试用例的目录
case_dir = ".\\TestCase"
discover = unittest.defaultTestLoader.discover(case_dir, pattern="*.py", top_level_dir=None)
return discover
def checkver():
# 检查IronIntel版本
dt = 'ironintel'
sqls = "select appver from SYS_CUSTSITES where COMPANYID='iicon004'"
vers = commQuery(dt=dt, sqlstr=sqls)
ver = vers[0][0]
return ver
def run(case, report, nth=0):
global all_result
fp_temp = open(report, "wb")
runner = HTMLTestRunner(stream=fp_temp,description=u'测试用例结果' + report)
res = runner.run(case)
all_result.append(res)
fp_temp.close()
sys.stderr.write("run: %s \n" % str(len(all_result)))
os.system("del %s" % report)
if __name__ == "__main__":
path = ".\\report"
isExists = os.path.exists(path)
if not isExists:
os.mkdir(path)
nowtime = datetime.now().strftime("%Y.%m.%d.%H%M%S.%f")[:-3]
rtime = datetime.now().strftime("%Y.%m.%d %H:%M")
start_time = datetime.now()
time_stamp = int(time.time())
report_path = ".\\report\\result_%s" % nowtime
os.mkdir(report_path)
all_result = []
cases = all_testcase()
task_pool = threadpool.ThreadPool(5)
count = 0
lst = []
for i,j in zip(cases, range(len(list(cases)))):
file_path = report_path + '\\' + nowtime + '_' + str(count) + '.html'
file_path = file_path.replace("\r", r"\r").replace('\n', r'\n')
count += 1
# sys.stderr.write("\ncase: %s" % i)
lst.append(([i, file_path,j],None))
rqs = threadpool.makeRequests(run, lst)
[task_pool.putRequest(req) for req in rqs]
task_pool.wait()
end_time = datetime.now()
report_file = report_path + "\\result_%s.html" % nowtime
fp = open(report_file, "wb")
merge_html = MergeResult(fp=fp,result_list=all_result,start_time=start_time,end_time=end_time, title=u'测试报告', description=u'用例执行情况:')
merge_html.make_html()
fp.close()
# 发送邮件
try:
vers = checkver()
# sendEmail(ver=vers, report=report_file, runtime=rtime)
except:
sys.stderr.write("Email delivery report failed!!")
finally:
sys.stderr.write(os.popen('move .\\report\\*.* ' + report_path).read())
下面是重点:测试报告,因为是多线程运行的测试,每个测试结果需要合并到最终的报告中,根据网上大神的代码,改造:基于HTMLTestRunner,加了合并报告部分,下面是合并报告的代码:
class MergeResult(Template_mixin):
def __init__(self, fp, result_list, start_time, end_time, title, description):
""" 都是复制其他class 方法,用到的数值"""
# 文件指针吧
self.stream = fp
self.result_lst = result_list
self.verbosity = 2
self.startTime = start_time
self.run_times = 0
self.stopTime = end_time
self.totle_count = 0
self.pass_count = 0
self.fail_count = 0
self.error_count = 0
self.skip_count = 0
self.rows = []
self.title = title
self.description = description
def _generate_heading(self, report_attrs):
a_lines = []
for name, value in report_attrs:
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=name,
value=value,
)
a_lines.append(line)
heading = self.HEADING_TMPL % dict(
title=saxutils.escape(self.title),
parameters=''.join(a_lines),
description=saxutils.escape(self.description),
)
return heading
def _generate_report_test(self, rows, cid, tid, n, t, o, e):
"""原版copy"""
# e.g. 'pt1.1', 'ft1.1', etc
has_output = bool(o or e)
if n == 0:
tmp = "p"
elif n == 1:
tmp = "f"
elif n == 2:
tmp = "e"
else:
tmp = "s"
# tid = tmp + 't%d.%d.%d' % (self.run_times, cid + 1, tid + 1) # cid重新取
tid = tmp + 't%d.%d.%d' % (self.run_times, cid, tid + 1)
name = t.id().split('.')[-1]
if self.verbosity > 1:
doc = getattr(t, '_testMethodDoc', "") or ''
else:
doc = ""
desc = doc and ('%s: %s' % (name, doc)) or name
if not PY3K:
if isinstance(desc, str):
desc = desc.decode("utf-8")
tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
# o and e should be byte string because they are collected from stdout and stderr?
if isinstance(o, str):
# uo = unicode(o.encode('string_escape'))
if PY3K:
uo = o
else:
uo = o.decode('utf-8', 'ignore')
else:
uo = o
if isinstance(e, str):
# ue = unicode(e.encode('string_escape'))
if PY3K:
ue = e
elif e.find("Error") != -1 or e.find("Exception") != -1:
es = e.decode('utf-8', 'ignore').split('\n')
try:
if es[-2].find("\\u") != -1 or es[-2].find('"\\u') != -1:
es[-2] = es[-2].decode('unicode_escape')
except Exception:
pass
ue = u"\n".join(es)
else:
ue = e.decode('utf-8', 'ignore')
else:
ue = e
script = self.REPORT_TEST_OUTPUT_TMPL % dict(
id=tid,
output=saxutils.escape(uo + ue),
)
if getattr(t, 'imgs', []):
# 判断截图列表,如果有则追加
tmp = u""
for i, img in enumerate(t.imgs):
if i == 0:
tmp += """ <img src="https://img-blog.csdnimg.cn/2022010707314313099.jpg" style="display: block;" class="img"/>\n""" % img
else:
tmp += """ <img src="https://img-blog.csdnimg.cn/2022010707314313099.jpg" style="display: none;" class="img"/>\n""" % img
imgs = self.IMG_TMPL % dict(imgs=tmp)
else:
imgs = u""""""
row = tmpl % dict(
tid=tid,
Class=(n == 0 and 'hiddenRow' or 'none'),
style=n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase'),
desc=desc,
script=script,
status=self.STATUS[n],
img=imgs,
)
rows.append(row)
if not has_output:
return
def sortResult(self, result_list):
# unittest does not seems to run in any particular order.
# Here at least we want to group them together by class.
rmap = {}
classes = []
for n, t, o, e in result_list:
cls = t.__class__
if not cls in rmap:
rmap[cls] = []
classes.append(cls)
rmap[cls].append((n, t, o, e))
r = [(cls, rmap[cls]) for cls in classes]
return r
def init_rows(self, c_id, current_result):
"""返回一个用例行 - list"""
# 引用
sortedResult = self.sortResult(current_result.result)
for cid, (cls, cls_results) in enumerate(sortedResult):
# subtotal for a class
np = nf = ne = ns = 0
for n, t, o, e in cls_results:
if n == 0:
np += 1
elif n == 1:
nf += 1
elif n == 2:
ne += 1
else:
ns += 1
name = cls.__name__
doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
desc = doc and '%s: %s' % (name, doc) or name
if not PY3K:
if isinstance(desc, str):
desc = desc.decode("utf-8")
row = self.REPORT_CLASS_TMPL % dict(
style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
desc=desc,
count=np + nf + ne + ns,
Pass=np,
fail=nf,
skip=ns,
error=ne,
cid='c%s.%s' % (self.run_times, c_id),
)
self.rows.append(row)
for tid, (n, t, o, e) in enumerate(cls_results):
# self._generate_report_test(self.rows, cid, tid, n, t, o, e)
self._generate_report_test(self.rows, c_id, tid, n, t, o, e)
self.totle_count += current_result.success_count + current_result.failure_count + current_result.error_count
self.pass_count += current_result.success_count
self.fail_count += current_result.failure_count
self.error_count += current_result.error_count
self.skip_count += current_result.skip_count
def rows_to_report(self):
"""每个case行数列数构造 tr td构造"""
total = self.totle_count
report = self.REPORT_TMPL % dict(
test_list=u''.join(self.rows),
count=str(total),
Pass=str(self.pass_count),
Pass_p=self.pass_count * 1.00 / total * 100 if total else 0.0,
fail=str(self.fail_count),
error=str(self.error_count),
skip=str(self.skip_count),
total=str(total),
channel=str(self.run_times),
)
return report
def getReportAttributes(self):
"""
开始时间:xxx
耗时:xx
状态:xx
"""
startTime = str(self.startTime)[:19]
duration = str(self.stopTime - self.startTime)
status = []
# status.append(u'Pass:%s' % self.pass_count)
# status.append(u'Failure:%s' % self.fail_count)
# status.append(u'Error:%s' % self.error_count)
# status.append(u'Skip:%s' % self.skip_count)
status.append(u'<span class="tj passCase"> Pass</span>:%s' % self.pass_count)
status.append(u'<span class="tj failCase"> Fail</span>:%s' % self.fail_count)
status.append(u'<span class="tj errorCase"> Error</span>:%s' % self.error_count)
status.append(u'<span class="tj skipCase"> Skip</span>:%s' % self.skip_count)
total = self.totle_count
if total > 0:
passed = self.pass_count * 1.000 / total * 100
else:
passed = 0.0
# status.append(u'通过率:%.1f%%' % passed)
status.append(u'<span class="tj"> 通过率</span>:%.1f%%' % passed)
if status:
status = u' '.join(status)
else:
status = 'none'
return [
(u'开始时间', startTime),
(u'耗时', duration),
(u'状态', status),
]
def make_html(self):
"""合并全部result 结果,输出到html"""
for yy in range(len(self.result_lst)):
# 重置这个计数器---解决合成的html,不能展开下属行问题
# self.run_times = (yy + 1) * 1000 # 点击报告上的统计不能展开对应内容
self.run_times = 1 # 点击报告中的Detail,打开的内容不正确
self.init_rows(yy+1, self.result_lst[yy]) # 参数yy即为原cid
report = self.rows_to_report()
# 顶部左上角按个 开始时间 + 耗时 + 状态
report_attrs = self.getReportAttributes()
heading = self._generate_heading(report_attrs)
# 版本
generator = 'HTMLTestRunner %s' % __version__
stylesheet = self.STYLESHEET_TMPL
# 结尾
ending = self.ENDING_TMPL
output = self.HTML_TMPL % dict(
title=saxutils.escape(self.title),
generator=generator,
stylesheet=stylesheet,
heading=heading,
report=report,
ending=ending,
channel=self.run_times,
)
if PY3K:
self.stream.write(output.encode())
else:
self.stream.write(output.encode('utf8'))