本篇文章简单记录自动化测试的三种生成HTML测试报告方式。


第一种:HTMLTestRunner

HTMLTestRunner是Python党员测试框架Unittest基础上扩展的报告模板,所以需要搭配Unittest使用。
HTMLTestRunner原版本为Python2开发的,现在已经停止维护了。可以手动更改为适配Python3版本。

文件下载(有分的大佬资助一下,hhh)或者直接复制下面的代码(新建文件HTMLTestRunner.py)就行了

# -*- 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.
The simplest way to use this is to invoke its main method. E.g.import unittestimport HTMLTestRunner... define your tests ...if __name__ == '__main__':HTMLTestRunner.main()
For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.# output to a filefp = file('my_report.html', 'wb')runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title='My unit test',description='This demonstrates the report output by HTMLTestRunner.')# Use an external stylesheet.# See the Template_mixin class for more customizable optionsrunner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'# run the testrunner.run(my_test_suite)
------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyrightnotice, this list of conditions and the following disclaimer in thedocumentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may beused to endorse or promote products derived from this software withoutspecific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
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__author__ = "xxx"
__version__ = "0.9.1""""
Change History
Version 0.9.1
* 用Echarts添加执行情况统计图 (灰蓝)
Version 0.9.0
* 改成Python 3.x (灰蓝)
Version 0.8.3
* 使用 Bootstrap稍加美化 (灰蓝)
* 改为中文 (灰蓝)
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 sys
import io
import time
import unittest
from xml.sax import saxutils# ------------------------------------------------------------------------
# The redirectors below are used to capture output during testing. Output
# sent to sys.stdout and sys.stderr are automatically captured. However
# in some cases sys.stdout is already cached before HTMLTestRunner is
# invoked (e.g. calling logging.basicConfig). In order to capture those
# output, use the redirectors for the cached stream.
#
# e.g.
#   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
#   >>>class OutputRedirector(object):""" Wrapper to redirect stdout or stderr """def __init__(self, fp):self.fp = fpdef 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)# ----------------------------------------------------------------------
# Templateclass Template_mixin(object):"""Define a HTML template for report customerization and generation.Overall structure of an HTML reportHTML+------------------------+|<html>                  ||  <head>                ||                        ||   STYLESHEET           ||   +----------------+   ||   |                |   ||   +----------------+   ||                        ||  </head>               ||                        ||  <body>                ||                        ||   HEADING              ||   +----------------+   ||   |                |   ||   +----------------+   ||                        ||   REPORT               ||   +----------------+   ||   |                |   ||   +----------------+   ||                        ||   ENDING               ||   +----------------+   ||   |                |   ||   +----------------+   ||                        ||  </body>               ||</html>                 |+------------------------+"""STATUS = {0: u'通过',1: u'失败',2: u'错误',}DEFAULT_TITLE = 'Unit Test Report'DEFAULT_DESCRIPTION = ''# ------------------------------------------------------------------------# HTML TemplateHTML_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">
<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"/><link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script><!-- <script type="text/javascript" src="js/echarts.common.min.js"></script> -->%(stylesheet)s</head>
<body><script language="javascript" type="text/javascript"><!--output_list = Array();/* level - 0:Summary; 1:Failed; 2:All */function showCase(level) {trs = document.getElementsByTagName("tr");for (var i = 0; i < trs.length; i++) {tr = trs[i];id = tr.id;if (id.substr(0,2) == 'ft') {if (level == 2 || level == 0) {tr.className = 'hiddenRow';}else {tr.className = '';}}if (id.substr(0,2) == 'pt') {if (level < 2) {tr.className = 'hiddenRow';}else {tr.className = '';}}}      }function showClassDetail(cid, count) {var id_list = Array(count);var toHide = 1;for (var i = 0; i < count; i++) {tid0 = 't' + cid.substr(1) + '.' + (i+1);tid = 'f' + tid0;tr = document.getElementById(tid);if (!tr) {tid = 'p' + tid0;tr = document.getElementById(tid);}id_list[i] = tid;if (tr.className) {toHide = 0;}}for (var i = 0; i < count; i++) {tid = id_list[i];if (toHide) {//document.getElementById('div_'+tid).style.display = 'none'document.getElementById(tid).className = 'hiddenRow';}else {document.getElementById(tid).className = '';}}}function showTestDetail(div_id){var details_div = document.getElementById(div_id)var displayState = details_div.style.display// alert(displayState)if (displayState == 'block' || displayState == '' ) {details_div.style.display = 'none'}else {displayState = 'block'details_div.style.display = 'block'}}function html_escape(s) {s = s.replace(/&/g,'&');s = s.replace(/</g,'<');s = s.replace(/>/g,'>');return s;}/* obsoleted by detail in <div>function showOutput(id, name) {var w = window.open("", //urlname,"resizable,scrollbars,status,width=800,height=450");d = w.document;d.write("<pre>");d.write(html_escape(output_list[id]));d.write("\n");d.write("<a href='javascript:window.close()'>close</a>\n");d.write("</pre>\n");d.close();}*/--></script><div id="div_base">%(heading)s%(report)s%(ending)s%(chart_script)s</div>
</body>
</html>
"""  # variables: (title, generator, stylesheet, heading, report, ending, chart_script)ECHARTS_SCRIPT = """<script type="text/javascript">// 基于准备好的dom,初始化echarts实例var myChart = echarts.init(document.getElementById('chart'));// 指定图表的配置项和数据var option = {title : {text: '测试执行情况',x:'center'},tooltip : {trigger: 'item',formatter: "{a} <br/>{b} : {c} ({d}%%)"},color: ['#95b75d', 'grey', '#b64645'],legend: {orient: 'vertical',left: 'left',data: ['通过','失败','错误']},series : [{name: '测试执行情况',type: 'pie',radius : '60%%',center: ['50%%', '60%%'],data:[{value:%(Pass)s, name:'通过'},{value:%(fail)s, name:'失败'},{value:%(error)s, name:'错误'}],itemStyle: {emphasis: {shadowBlur: 10,shadowOffsetX: 0,shadowColor: 'rgba(0, 0, 0, 0.5)'}}}]};// 使用刚指定的配置项和数据显示图表。myChart.setOption(option);</script>"""  # variables: (Pass, fail, error)# ------------------------------------------------------------------------# Stylesheet## alternatively use a <link> for external style sheet, e.g.#   <link rel="stylesheet" href="$url" type="text/css">STYLESHEET_TMPL = """
<style type="text/css" media="screen">body        { font-family: Microsoft YaHei,Consolas,arial,sans-serif; font-size: 80%; }table       { font-size: 100%; }pre         { white-space: pre-wrap;word-wrap: break-word; }/* -- heading ---------------------------------------------------------------------- */h1 {/**font-size: 16pt;color: gray;*/}.heading {margin-top: 0ex;margin-bottom: 1ex;}.heading .attribute {margin-top: 1ex;margin-bottom: 0;}.heading .description {margin-top: 2ex;margin-bottom: 3ex;}/* -- css div popup ------------------------------------------------------------------------ */a.popup_link {}a.popup_link:hover {color: red;}.popup_window {display: none;position: relative;left: 0px;top: 0px;/*border: solid #627173 1px; */padding: 10px;/*background-color: #E6E6D6; */font-family: "Lucida Console", "Courier New", Courier, monospace;text-align: left;font-size: 8pt;/* width: 500px;*/}}/* -- report ------------------------------------------------------------------------ */#show_detail_line {margin-top: 3ex;margin-bottom: 1ex;}#result_table {width: 99%;}#header_row {font-weight: bold;color: #303641;background-color: #dff0d8;}#total_row  { font-weight: bold; }.passClass  { background-color: #bdedbc; }.failClass  { background-color: #ffefa4; }.errorClass { background-color: #ffc9c9; }.passCase   { color: #5cb85c; }.failCase   { color: #FF6600; font-weight: bold; }.errorCase  { color: #c00; font-weight: bold; }.hiddenRow  { display: none; }.testcase   { margin-left: 2em; }/* -- ending ---------------------------------------------------------------------- */#ending {}#div_base {padding: 20px;}
</style>
"""# ------------------------------------------------------------------------# Heading#HEADING_TMPL = """<div class='page-header'><h1>%(title)s</h1>%(parameters)s</div><div style="float: left;width:50%%;"><p class='description'>%(description)s</p></div><div id="chart" style="width:50%%;height:400px;float:left;"></div>
"""  # variables: (title, parameters, description)HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
"""  # variables: (name, value)# ------------------------------------------------------------------------# Report#REPORT_TMPL = u"""<p id='show_detail_line'><a class="btn btn-primary" href='javascript:showCase(0)'>概要{ %(passrate)s }</a><a class="btn btn-danger" href='javascript:showCase(1)'>未通过{ %(notPass)s }</a><a class="btn btn-success" href='javascript:showCase(2)'>通过{ %(Pass)s }</a><a class="btn btn-info" href='javascript:showCase(3)'>所有{ %(count)s }</a>        </p><table id='result_table' class="table table-bordered"><colgroup><col align='left' /><col align='right' /><col align='right' /><col align='right' /><col align='right' /><col align='right' /></colgroup><tr id='header_row' class="text-center success"><td>用例集/测试用例</td><td>总数</td><td>通过</td><td>失败</td><td>错误</td><td>查看</td></tr>%(test_list)s<tr id='total_row' class="text-center active"><td>总计</td><td>%(count)s</td><td>%(Pass)s</td><td>%(fail)s</td><td>%(error)s</td><td>通过率:%(passrate)s</td></tr></table>
"""  # variables: (test_list, count, Pass, fail, error, passrate)REPORT_CLASS_TMPL = u"""<tr class='%(style)s'><td class="text-center">%(desc)s</td><td class="text-center">%(count)s</td><td class="text-center">%(Pass)s</td><td class="text-center">%(fail)s</td><td class="text-center">%(error)s</td><td class="text-center"><a href="javascript:showClassDetail('%(cid)s',%(count)s)">详细</a></td></tr>
"""  # variables: (style, desc, count, Pass, fail, error, cid)REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'><td class='%(style)s'><div class='testcase'>%(desc)s</div></td><td colspan='5' align='center'><!--css div popup start--><a οnfοcus='this.blur();' data-toggle="collapse" href="javascript:showTestDetail('div_%(tid)s')" >%(status)s</a><div id='div_%(tid)s' class="collapse in"><pre>%(script)s</pre></div><!--css div popup end--></td>
</tr>
"""  # variables: (tid, Class, style, desc, status)REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'><td class='%(style)s'><div class='testcase'>%(desc)s</div></td><td colspan='5' align='center' style="color:#428bca">%(status)s</td>
</tr>
"""  # variables: (tid, Class, style, desc, status)REPORT_TEST_OUTPUT_TMPL = r"""%(id)s: %(output)s"""  # variables: (id, output)# ------------------------------------------------------------------------# ENDING#ENDING_TMPL = """<div id='ending'> </div><div style=" position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer"><a href="#"><span class="glyphicon glyphicon-eject" style = "font-size:30px;" aria-hidden="true"></span></a></div>"""# -------------------- The end of the Template class -------------------TestResult = unittest.TestResultclass _TestResult(TestResult):# note: _TestResult is a pure representation of results.# It lacks the output and reporting ability compares to unittest._TextTestResult.def __init__(self, verbosity=1):TestResult.__init__(self)self.stdout0 = Noneself.stderr0 = Noneself.success_count = 0self.failure_count = 0self.error_count = 0self.verbosity = verbosity# result is a list of result in 4 tuple# (#   result code (0: success; 1: fail; 2: error),#   TestCase object,#   Test output (byte string),#   stack trace,# )self.result = []self.subtestlist = []self.passrate = float(0)def startTest(self, test):TestResult.startTest(self, test)# just one buffer for both stdout and stderrself.outputBuffer = io.StringIO()stdout_redirector.fp = self.outputBufferstderr_redirector.fp = self.outputBufferself.stdout0 = sys.stdoutself.stderr0 = sys.stderrsys.stdout = stdout_redirectorsys.stderr = stderr_redirectordef complete_output(self):"""Disconnect output redirection and return buffer.Safe to call multiple times."""if self.stdout0:sys.stdout = self.stdout0sys.stderr = self.stderr0self.stdout0 = Noneself.stderr0 = Nonereturn 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()def addSuccess(self, test):if test not in self.subtestlist:self.success_count += 1TestResult.addSuccess(self, test)output = self.complete_output()self.result.append((0, test, output, ''))if self.verbosity > 1:sys.stderr.write('ok ')sys.stderr.write(str(test))sys.stderr.write('\n')else:sys.stderr.write('..')def addError(self, test, err):self.error_count += 1TestResult.addError(self, test, err)_, _exc_str = self.errors[-1]output = self.complete_output()self.result.append((2, test, output, _exc_str))if self.verbosity > 1:sys.stderr.write('E  ')sys.stderr.write(str(test))sys.stderr.write('\n')else:sys.stderr.write('E')def addFailure(self, test, err):self.failure_count += 1TestResult.addFailure(self, test, err)_, _exc_str = self.failures[-1]output = self.complete_output()self.result.append((1, test, output, _exc_str))if self.verbosity > 1:sys.stderr.write('F  ')sys.stderr.write(str(test))sys.stderr.write('\n')else:sys.stderr.write('F')def addSubTest(self, test, subtest, err):if err is not None:if getattr(self, 'failfast', False):self.stop()if issubclass(err[0], test.failureException):self.failure_count += 1errors = self.failureserrors.append((subtest, self._exc_info_to_string(err, subtest)))output = self.complete_output()self.result.append((1, test, output + '\nSubTestCase Failed:\n' + str(subtest),self._exc_info_to_string(err, subtest)))if self.verbosity > 1:sys.stderr.write('F  ')sys.stderr.write(str(subtest))sys.stderr.write('\n')else:sys.stderr.write('F')else:self.error_count += 1errors = self.errorserrors.append((subtest, self._exc_info_to_string(err, subtest)))output = self.complete_output()self.result.append((2, test, output + '\nSubTestCase Error:\n' + str(subtest), self._exc_info_to_string(err, subtest)))if self.verbosity > 1:sys.stderr.write('E  ')sys.stderr.write(str(subtest))sys.stderr.write('\n')else:sys.stderr.write('E')self._mirrorOutput = Trueelse:self.subtestlist.append(subtest)self.subtestlist.append(test)self.success_count += 1output = self.complete_output()self.result.append((0, test, output + '\nSubTestCase Pass:\n' + str(subtest), ''))if self.verbosity > 1:sys.stderr.write('ok ')sys.stderr.write(str(subtest))sys.stderr.write('\n')else:sys.stderr.write('..')class HTMLTestRunner(Template_mixin):def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):self.stream = streamself.verbosity = verbosityif title is None:self.title = self.DEFAULT_TITLEelse:self.title = titleif description is None:self.description = self.DEFAULT_DESCRIPTIONelse:self.description = descriptionself.startTime = datetime.datetime.now()def run(self, test):"Run the given test case or test suite."result = _TestResult(self.verbosity)test(result)self.stopTime = datetime.datetime.now()self.generateReport(test, result)print('\nTime Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)return resultdef 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 cls not in rmap:rmap[cls] = []classes.append(cls)rmap[cls].append((n,t,o,e))r = [(cls, rmap[cls]) for cls in classes]return rdef getReportAttributes(self, result):"""Return report attributes as a list of (name, value).Override this to add custom attributes."""startTime = str(self.startTime)[:19]duration = str(self.stopTime - self.startTime)status = []if result.success_count: status.append(u'通过 %s' % result.success_count)if result.failure_count: status.append(u'失败 %s' % result.failure_count)if result.error_count:   status.append(u'错误 %s' % result.error_count  )if status:status = ' '.join(status)self.passrate = str("%.2f%%" % (float(result.success_count) / float(result.success_count + result.failure_count + result.error_count) * 100))else:status = 'none'return [(u'开始时间', startTime),(u'运行时长', duration),(u'测试结果', status + " 通过率= "+self.passrate),]def generateReport(self, test, result):report_attrs = self.getReportAttributes(result)generator = 'HTMLTestRunner %s' % __version__stylesheet = self._generate_stylesheet()heading = self._generate_heading(report_attrs)report = self._generate_report(result)ending = self._generate_ending()chart = self._generate_chart(result)output = self.HTML_TMPL % dict(title = saxutils.escape(self.title),generator = generator,stylesheet = stylesheet,heading = heading,report = report,ending = ending,chart_script = chart)self.stream.write(output.encode('utf8'))def _generate_stylesheet(self):return self.STYLESHEET_TMPLdef _generate_heading(self, report_attrs):a_lines = []for name, value in report_attrs:line = self.HEADING_ATTRIBUTE_TMPL % dict(name = saxutils.escape(name),value = saxutils.escape(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 headingdef _generate_report(self, result):rows = []sortedResult = self.sortResult(result.result)for cid, (cls, cls_results) in enumerate(sortedResult):# subtotal for a classnp = nf = ne = 0for n,t,o,e in cls_results:if n == 0: np += 1elif n == 1: nf += 1else: ne += 1# format class descriptionif cls.__module__ == "__main__":name = cls.__name__else:name = "%s.%s" % (cls.__module__, cls.__name__)doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""desc = doc and '%s: %s' % (name, doc) or namerow = self.REPORT_CLASS_TMPL % dict(style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',desc = desc,count = np+nf+ne,Pass = np,fail = nf,error = ne,cid = 'c%s' % (cid+1),)rows.append(row)for tid, (n,t,o,e) in enumerate(cls_results):self._generate_report_test(rows, cid, tid, n, t, o, e)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),notPass = str(result.failure_count + result.error_count),fail = str(result.failure_count),error = str(result.error_count),passrate=self.passrate,)return reportdef _generate_chart(self, result):chart = self.ECHARTS_SCRIPT % dict(Pass=str(result.success_count),fail=str(result.failure_count),error=str(result.error_count),)return chartdef _generate_report_test(self, rows, cid, tid, n, t, o, e):# e.g. 'pt1.1', 'ft1.1', etchas_output = bool(o or e)tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)name = t.id().split('.')[-1]doc = t.shortDescription() or ""desc = doc and ('%s: %s' % (name, doc)) or nametmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPLscript = self.REPORT_TEST_OUTPUT_TMPL % dict(id=tid,output=saxutils.escape(o+e),)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],)rows.append(row)if not has_output:returndef _generate_ending(self):return self.ENDING_TMPL##############################################################################
# Facilities for running tests from the command line
############################################################################### Note: Reuse unittest.TestProgram to launch test. In the future we may
# build our own launcher to support more specific command line
# parameters like test title, CSS, etc.
class TestProgram(unittest.TestProgram):"""A variation of the unittest.TestProgram. Please refer to the baseclass for command line parameters."""def runTests(self):# Pick HTMLTestRunner as the default test runner.# base class's testRunner parameter is not useful because it means# we have to instantiate HTMLTestRunner before we know self.verbosity.if self.testRunner is None:self.testRunner = HTMLTestRunner(verbosity=self.verbosity)unittest.TestProgram.runTests(self)main = TestProgram##############################################################################
# Executing this module from the command line
##############################################################################if __name__ == "__main__":main(module=None)

例子参考:HTMLTestRunner需要结合Unittest的suite使用,将测试用例都装在到suite中后运行

import HTMLTestRunner
# 手机号码+密码登录成功用例
def test_login_success(self):self.result = self.lb.login_by_password_success('15622297351', 'aa111111')self.assertTrue(self.result, '测试用例登录失败')if __name__ == '__main__':# unittest.main()# 文件名时间戳local_time = time.strftime('%Y%m%d%H%M%S', time.localtime())# 保存report文件路径report_path = os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__)))+ '/report/login/' + local_time + '.html')f = open(report_path, 'wb')# 实例化测试套件suite = unittest.TestSuite()# 添加测试用例suite.addTest(LoginTest('test_login_success'))suite.addTest(LoginTest('test_account_error'))runner = HTMLTestRunner.HTMLTestRunner(stream=f, title='This is Ebemate login_test_case report',description='这是登录测试报告', verbosity=2)runner.run(suite)f.close()

第二种:Allure

Allure是个费钱强大的报告框架,他结合Pytest单元测试框架使用能够非常详细的展示测试用例的执行情况,以及展示各种独特的分析结果,同时还支持多种语种的切换,但目前为止它功能最强大,操作也相对较为复杂。

使用步骤

  1. Allure报告框架是有Java开发的,所以需要安装Java8以上版本
  2. 安装Pytest:pip install pytest
  3. 安装allure:pip install allure-pytest
  4. 执行pytest命令运行测试用例,生成测试结果数据:pytest -s -q --alluredir 'D:\\PO\\TestResult\\allureReport'(路径为生成测试数据结果存放位置路径)
  5. 安装Allure Command Line(下载):工具解压后(放置位置看自己,可以放项目里也可以放其他地方),配置系统环境变量PATH(\安装路径\allure-commandline\bin)方便其他项目调用。配置好后,命令行输入allure出现Usage使用说明就成功。
  6. 将第4生成的测试结果数据生成测试报告:allure generate directory-with-results/ -o directory-with-report
    directory-with-results:为测试结果数据文件存放路径(D:\PO\TestResult\allureReport)
    directory-with-report:用于自定义HTML报告生成到哪个路径下(D:\PO\TestResult\Report)
  7. 运行生成文件的index.html

第三种:BeautifulReport

BeautifulReport是基于Unittest党员测试框架的一个可以生成HTML格式的自动化测试报告的报告框架,它为用户提供了既简洁又美观的HTML模板,将生成的结果添加到模板中,然后在指定的目录下生成HTML格式报告。
使用步骤

  1. 获取报告模板地址,直接下载。
  2. 项目中放入template文件和BeautifulReport.py
  3. 新建Run_Automation_BR.py,编写执行测试用例代码,并调用BeautifulReport
import unittest
import os
from Util.BeautifulReport import BeautifulReport
if __name__ == '__main__':# 获取当前文件所在目录的父目录的绝对路径parent_directory_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))# 定义测试集合test_suite = unittest.defaultTestLoader.discover('TestScript', pattern='test*.py')result = BeautifulReport(test_suite)result.report(filename='测试报告', description='测试报告', log_path=parent_directory_path + "\\PO\\TestResult\\Report\\")


如果出现上述运行Error,根据提示,可以看到是项目没有找到默认路径下的template文件。存在两种解决方法:
第一种:根据异常信息,将template文件放到系统提示的路径中去。
第二种:修改BeautifulReport.py源码中template文件的实际路径。代码如下:

# BeautifulReport.py第67行
# 获取当前文件所在目录的父目录的绝对路径
parent_directory_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
class PATH:""" all file PATH meta """# config_tmp_path = SITE_PAKAGE_PATH + '/BeautifulReport/template/template'# 重新定义template路径config_tmp_path = parent_directory_path + '/template/template'
  1. 再次运行Run_Automation_BR.py文件,生成报告


以上内容仅作为记录

自动化测试的三种测试报告模板相关推荐

  1. WordPress自适应白天暗夜无刷新加载三种布局模板源码

    一款基于Wordpress开发的高颜值的自适应主题,支持白天与黑夜模式.首页支持三种布局:博客风格,cms风格,企业风格 主题特性: 支持白天与暗黑模式 全局无刷新加载 支持博客与CMS布局 内置WP ...

  2. C++类模板的三种特化类型

    From: http://blog.sina.com.cn/s/blog_65d069c601010fb3.html 说起C++的模板及模板特化, 相信很多人都很熟悉 ,但是说到模板特化的几种类型,相 ...

  3. poj 3486 A Simple Problem with Integers(树状数组第三种模板改段求段)

    1 /* 2 树状数组第三种模板(改段求段)不解释! 不明白的点这里:here! 3 */ 4 #include<iostream> 5 #include<cstring> 6 ...

  4. halcon三种模板匹配方法

    转自 : http://blog.csdn.net/hust1900/article/details/8843270 halcon有三种模板匹配方法:即Component-Based.Gray-Val ...

  5. web自动化测试-第四讲: 三种时间等待

    我们在做web自动化测试,执行脚本的时候,想要对一些页面对象(输入框.按钮等)进行操作,需要对获取该元素的对象,才能对其操作(点击.输入文本内容等),但是,可能由于页面加载过慢导致代码报错:Messa ...

  6. vue2.0模板的三种写法

    vue2.0中的模板有三种写法,根据不同的需求运用不同的方法来实现 1. <!DOCTYPE html> <html lang="en"> <head ...

  7. EDM模板设计:教您设计三种独特的邮件营销模板

    教您设计三种独特的邮件营销模板 邮件营销,模板 众所周知,好的邮件营销必须要有好的模板设计,这也是EDM设计研究中非常重要的一个环节.下面博主教大家设计三种独特的邮件营销模板,供大家参考和学习. 一. ...

  8. 一个模板三种风格版本人物头像照片墙动态开场视频PR片头模板(含相同效果的AE模板)

    一个模板三种风格版本人物头像照片墙动态开场视频PR片头模板(含相同效果的AE模板) 项目特色: Adobe Premiere Pro CC 2020或更高版本 必须安装Adobe After Effe ...

  9. vue数据模板文件的下载三种方法

    用vue2针对后台返回数据的不同提供的下载模板文件三种办法(针对Excel) 一.当后台返回的数据是文件流时,以下代码可实现直接下载文件到本地 let ele = document.createEle ...

最新文章

  1. ubuntu 禁用透明大页_Linux关于透明大页的使用与禁用介绍
  2. NoSQL介绍(三)
  3. sql 判断分钟是偶数数据_使用SQL交换座位(奇偶数的用法)
  4. 启动Eclipse 弹出“Failed to load the JNI shared library”错误的解决方法
  5. Android dp、dip、dpi、px、sp简介及相关换算,及其应用实例
  6. 你还在用虚拟机,win10自带的linux还有图形界面(小白教程)
  7. 高德地图api如何不显示logo_Python爬取高德地图POI数据获取「洗浴推拿指南」
  8. 快速了解Spring Cloud
  9. linux0.11 init函数,linux0.11启动与初始化
  10. 如何将ppt中的绘图高清保存
  11. shell插入多行文本
  12. 七年级计算机基本结构,七年级信息技术计算机基本组成和工作原理
  13. 阿里大数据之路:数据管理篇大总结
  14. matlab机械臂运动仿真
  15. Home Assistant 基于EZSP Zigbee Dongle创建Zigbee智能家居系统
  16. 装的系统没有截图和计算机工具栏,不想安装专用的截图工具?这里有几个Windows(snipping tool)截图小技巧_都叫兽软件...
  17. iOS coding多人协作开发工具
  18. Git常用操作速查,没有人比我更简单!!
  19. SQL语句操作数据2
  20. 2022全球数商大会顺利举行,合合信息旗下启信宝斩获年度数据产品奖

热门文章

  1. python爬取歌词生成词云图
  2. OSChina 周五乱弹 ——人类发明眼镜之前眼镜蛇叫什么?
  3. Encryption raised an exception
  4. python openpyxl怎么将数组写入excel_Python-使用openpyxl模块写入Excel文件
  5. 大型软件开发中的流程与规范
  6. js判断是否是微信扫描进入
  7. 华为BLM是什么?有什么用?怎么用?三张图就说清楚了
  8. 根据代表性序列预测OTU/ASV生活史策略——寡营养型or富营养型
  9. 招商银行信用卡中心2018秋招部分编程题汇总
  10. Biotin-PEG2k-NHS,Biotin-PEG2000-NHS,PEG衍生物