UnitTest框架管理测试用例——python自动化测试

news/2025/3/18 14:01:26/

UnitTest框架

UnitTestPython自带一个单元测试框架,常用它来做单元测试。

注意:对于测试来说,UnitTest框架的作用是 自动化脚本(用例代码)执行框架————(使用UnitTest框架来管理 运行多个测试用例的)

为什么使用UnitTest框架

  1. 能够组织多个用例去执行
  2. 提供丰富的断言方法(让程序代码判断代替人工自动的判断预期结果与实际结果是否相符)
  3. 能够生成测试报告

UnitTest框架的组成

TestCase(最核心的模块)

TestCase(测试用例),每一个TestCase(测试用例)都是一个代码文件,在这个代码文件中来书写真正的用例代码

TestSuite

TestSuite(测试套件)TestSuite用来 管理,组装(打包)多个TestCase(测试用例)的。

TestRunner

TestRunner(测试执行,测试运行),TestRunner用来执行TestSuite(测试套件)的。

TestLoader

TestLoader(测试加载)TestLoader用来 管理,组装(打包)多个TestCase(测试用例)的,TestLoader是对TestSuit(测试套件)功能的补充。

Fixture

Fixture(测试夹层),Fixture是书写在TestCase(测试用例)的代码中,是一个代码结构,可以在每个方法执行前后都会执行的内容。

在这里插入图片描述

如上图,蓝色的方框就是一个方法(函数),该方法在执行前后都会执行Fixture的内容。

测试结果的查看

在这里插入图片描述

如上图紫色方框所示,四个点表示四个测试用例(测试方法)全部通过。

TestCase(测试用例)的书写

在这里插入图片描述

TestSuite(测试套件)与TestRunner(测试运行)的书写

在这里插入图片描述

TestLoader(测试加载)的书写

在这里插入图片描述

Fixture(测试夹具)的书写

Fixture是一种代码结构,在某些特定情况下会自动执行。

方法级别

所谓方法级别,即在每个测试方法(测试用例)执行前后都会自动调用的结构。

在这里插入图片描述

类级别

所谓类级别,即在每个开始前和结束后,各执行一次(不管该类中有多少方法,都只执行一次)。

在这里插入图片描述

模块级别

所谓模块级别,即在每个代码文件执行前后都会自动调用的结构。

在这里插入图片描述

UnitTest 断言

断言:让程序代替人工自动地判断预期结果和实际结果是否相符

断言的结果:

为什么学习断言

  1. 提高测试效率。
  2. 实现自动化测试(让脚本在无人看守的情况下自动运行)。

断言的常用方法

注意:在测试类中使用断言方法时,必须使用self.断言方法这种形式。

在这里插入图片描述

在这里插入图片描述

如上图所示,重点掌握以下方法:

  • assertEqual(预期结果,实际结果),函数作用:判断预期结果与实际结果是否相等,如果相等,则测试用例通过,如果不相等,则测试用例不通过,抛出异常。
  • assertIn(预期结果,实际结果),函数作用:判断预期结果是否包含在实际结果当中,如果包含,则测试用例通过,如果不包含,则测试用例不通过,抛出异常。其中实际结果不一定是个容器,例如:("admin","aabbadmindd")——————前面这种情况,也叫包含。

参数化

参数化:在测试方法中,使用变量来代替具体的测试数据,然后使用传参的方法将数据传递给参数。

工作中的场景:

  1. 测试数据一般放在json文件当中。
  2. 使用代码读取json文件,获取我们想要的测试数据。
  3. 最终测试数据必须以下形式—>[(),(),()……]或者[[],[],[]……]

安装插件

unittest框架本身是不支持参数化的,要想支持参数化,需要安装插件。

安装命令:pip install parameterized

参数化的使用

在这里插入图片描述

如上图红框,要想在unittest框架中使用参数化,除了测试用例要有形参外,还要引入相关插件import parameterized,并且在参数化的测试用例上添加@parameterized.expand(测试数据的变量名)装饰器

跳过

跳过:对于一些未完成或者不满足测试条件的测试方法和测试类,不想执行,可以选择跳过。

使用方法(通过装饰器来完成):

  • @unittest.skip('跳过的原因'),将该装饰器放在对应的方法或类上面,系统执行时可以直接跳过它们。
  • @unittest.skipIf(判断条件,'跳过的原因'),根据判断条件是否成立,来决定是否跳过,若判断条件成立,则跳过,反之则不跳过。

测试报告

Unit Test框架自带的TestRunner测试运行模块一次只能生成单个测试类的测试报告,要想生成多个测试类的综合(通过TestSuite打包)测试报告,则需要使用第三方的模块来生成第三方的测试报告。

生成第三方的测试报告

  1. 获取第三方的 测试运行模块 ,将其放在项目代码的目录当中。
  2. 导包 unittest以及第三方测试运行模块。
  3. 使用套件对象,加载对象去添加测试用例
  4. 实例化 第三方的运行对象 并运行套件对象。

第三方测试运行模块的使用

我这里使用的是HTMLTestRunner.py文件和HTMLTestRunnerCN.py文件,两者区别在前者能够生成英文版的测试报告,后者能够生成中文版的测试报告。

  1. 首先引入第三方测试运行模块
  2. 实例函数 HTMLTestRunner()共有四个参数,参数1:stream=? 必填,测试报告的文件对象(用open通过wb形式打开的),参数2:verbosity=? 可选,报告的详细程度,默认1 简略2 详细参数3:title=? 可选,测试报告的标题,参数4:description=? 可选,描述信息,如python版本,pycharm版本。
  3. 通过.run(TestSuite对象)来运行测试用例
  4. 中文测试报告模块的实例化函数为HTMLTestReporterCN(),其参数与 HTMLTestRunner()的参数类似,只是在末尾多了一个可选参数:tester=? 可选,记录测试人的信息

第三方 测试运行模块具体文件

  • 文件名:HTMLTestRunner.py(生成英文测试报告)
python">"""
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__ = "Wai Yip Tung"
__version__ = "0.8.2""""
Change HistoryVersion 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 sys
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: 'pass',1: 'fail',2: 'error',}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"/>%(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 < 1) {tr.className = 'hiddenRow';}else {tr.className = '';}}if (id.substr(0,2) == 'pt') {if (level > 1) {tr.className = '';}else {tr.className = 'hiddenRow';}}}
}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 = 'block'details_div.style.display = 'block'}else {details_div.style.display = 'none'}
}function html_escape(s) {s = s.replace(/&/g,'&amp;');s = s.replace(/</g,'&lt;');s = s.replace(/>/g,'&gt;');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>%(heading)s
%(report)s
%(ending)s</body>
</html>
"""# variables: (title, generator, stylesheet, heading, report, ending)# ------------------------------------------------------------------------# 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: verdana, arial, helvetica, sans-serif; font-size: 80%; }
table       { font-size: 100%; }
pre         { }/* -- 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: 4ex;margin-bottom: 6ex;
}/* -- 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: 80%;border-collapse: collapse;border: 1px solid #777;
}
#header_row {font-weight: bold;color: white;background-color: #777;
}
#result_table td {border: 1px solid #777;padding: 2px;
}
#total_row  { font-weight: bold; }
.passClass  { background-color: #6c6; }
.failClass  { background-color: #c60; }
.errorClass { background-color: #c00; }
.passCase   { color: #6c6; }
.failCase   { color: #c60; font-weight: bold; }
.errorCase  { color: #c00; font-weight: bold; }
.hiddenRow  { display: none; }
.testcase   { margin-left: 2em; }/* -- ending ---------------------------------------------------------------------- */
#ending {
}</style>
"""# ------------------------------------------------------------------------# Heading#HEADING_TMPL = """<div class='heading'>
<h1>%(title)s</h1>
%(parameters)s
<p class='description'>%(description)s</p>
</div>""" # variables: (title, parameters, description)HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
""" # variables: (name, value)# ------------------------------------------------------------------------# Report#REPORT_TMPL = """
<p id='show_detail_line'>Show
<a href='javascript:showCase(0)'>Summary</a>
<a href='javascript:showCase(1)'>Failed</a>
<a href='javascript:showCase(2)'>All</a>
</p>
<table id='result_table'>
<colgroup>
<col align='left' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
</colgroup>
<tr id='header_row'><td>Test Group/Test case</td><td>Count</td><td>Pass</td><td>Fail</td><td>Error</td><td>View</td>
</tr>
%(test_list)s
<tr id='total_row'><td>Total</td><td>%(count)s</td><td>%(Pass)s</td><td>%(fail)s</td><td>%(error)s</td><td>&nbsp;</td>
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)REPORT_CLASS_TMPL = r"""
<tr class='%(style)s'><td>%(desc)s</td><td>%(count)s</td><td>%(Pass)s</td><td>%(fail)s</td><td>%(error)s</td><td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</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 class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >%(status)s</a><div id='div_%(tid)s' class="popup_window"><div style='text-align: right; color:red;cursor:pointer'><a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >[x]</a></div><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'>%(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'>&nbsp;</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 = []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):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')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(sys.stderr, '\nTimeElapsed: %s' % (self.stopTime-self.startTime))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 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 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('Pass %s'    % result.success_count)if result.failure_count: status.append('Failure %s' % result.failure_count)if result.error_count:   status.append('Error %s'   % result.error_count  )if status:status = ' '.join(status)else:status = 'none'return [('Start Time', startTime),('Duration', duration),('Status', status),]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()output = self.HTML_TMPL % dict(title = saxutils.escape(self.title),generator = generator,stylesheet = stylesheet,heading = heading,report = report,ending = ending,)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),fail = str(result.failure_count),error = str(result.error_count),)return reportdef _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_TMPL# o and e should be byte string because they are collected from stdout and stderr?if isinstance(o,str):# TODO: some problem with 'string_escape': it escape \n and mess up formating# uo = unicode(o.encode('string_escape'))uo = eelse:uo = oif isinstance(e,str):# TODO: some problem with 'string_escape': it escape \n and mess up formating# ue = unicode(e.encode('string_escape'))ue = eelse:ue = escript = self.REPORT_TEST_OUTPUT_TMPL % dict(id = tid,output = saxutils.escape(uo+ue),)row = tmpl % dict(tid = tid,Class = (n == 0 and 'hiddenRow' or 'none'),style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),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)
  • 文件名:HTMLTestRunnerCN.py(生成中文测试报告)
python">#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 HTMLTestReportCN... define your tests ...if __name__ == '__main__':HTMLTestReportCN.main()For more customization options, instantiates a HTMLTestReportCN object.
HTMLTestReportCN is a counterpart to unittest's TextTestRunner. E.g.# output to a filefp = file('my_report.html', 'wb')runner = HTMLTestReportCN.HTMLTestReportCN(stream=fp,title='My unit test',description='This demonstrates the report output by HTMLTestReportCN.')# 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
Copyright (c) 2017, Findyou
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__ = "Wai Yip Tung,  Findyou"
__version__ = "0.8.3""""
Change History
Version 0.8.3 -Findyou 20171206
* BUG fixed :错误的测试用例没有统计与显示
* BUG fixed :当PASS的测试用例有print内容时,通过按钮显示为红色
* 表格背景颜色根据用例结果显示颜色,优先级: 错误(黄色)>失败(红色)>通过(绿色)
* 合并文为HTMLTestRunner*N.py 同时支持python2,python3Version 0.8.2.2 -Findyou
* HTMLTestRunnerEN.py 支持 python3.x
* HTMLTestRunnerEN.py 支持 python2.xVersion 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
try:from StringIO import StringIO
except ImportError:from io import StringIO
import sys
import time
import unittest
from xml.sax import saxutilstry:reload(sys)sys.setdefaultencoding('utf-8')
except NameError:pass# ------------------------------------------------------------------------
# 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: '通过',1: '失败',2: '错误',}DEFAULT_TITLE = '测试报告'DEFAULT_DESCRIPTION = ''DEFAULT_TESTER='QA'# ------------------------------------------------------------------------# 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://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>%(stylesheet)s
</head>
<body >
%(heading)s
%(report)s
%(ending)s
<script language="javascript" type="text/javascript">
output_list = Array();
// 修改按钮颜色显示错误问题 --Findyou v0.8.2.3$("button[id^='btn_pt']").addClass("btn btn-success");
$("button[id^='btn_ft']").addClass("btn btn-danger");
$("button[id^='btn_et']").addClass("btn btn-warning");/*level
增加分类并调整,增加error按钮事件 --Findyou v0.8.2.3
0:Pass    //pt none, ft hiddenRow, et hiddenRow
1:Failed  //pt hiddenRow, ft none, et hiddenRow
2:Error    //pt hiddenRow, ft hiddenRow, et none
3:All     //pt none, ft none, et none
4:Summary //all hiddenRow
*///add Error button event --Findyou v0.8.2.3
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 == 0 || level == 2 || level == 4 ) {tr.className = 'hiddenRow';}else {tr.className = '';}}if (id.substr(0,2) == 'pt') {if (level == 1 || level == 2 || level == 4) {tr.className = 'hiddenRow';}else {tr.className = '';}}if (id.substr(0,2) == 'et') {if (level == 0 || level == 1 || level == 4) {tr.className = 'hiddenRow';}else {tr.className = '';}}}//加入【详细】切换文字变化 --Findyoudetail_class=document.getElementsByClassName('detail');//console.log(detail_class.length)if (level == 3) {for (var i = 0; i < detail_class.length; i++){detail_class[i].innerHTML="收起"}}else{for (var i = 0; i < detail_class.length; i++){detail_class[i].innerHTML="详细"}}
}//add Error button event --Findyou v0.8.2.3
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);}if (!tr) {tid = 'e' + 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];//修改点击无法收起的BUG,加入【详细】切换文字变化 --Findyouif (toHide) {document.getElementById(tid).className = 'hiddenRow';document.getElementById(cid).innerText = "详细"}else {document.getElementById(tid).className = '';document.getElementById(cid).innerText = "收起"}}
}function html_escape(s) {s = s.replace(/&/g,'&amp;');s = s.replace(/</g,'&lt;');s = s.replace(/>/g,'&gt;');return s;
}
</script>
</body>
</html>
"""# variables: (title, generator, stylesheet, heading, report, ending)# ------------------------------------------------------------------------# 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,Tahoma,arial,helvetica,sans-serif;padding: 20px; font-size: 100%; }
table       { font-size: 100%; }/* -- heading ---------------------------------------------------------------------- */
.heading {margin-top: 0ex;margin-bottom: 1ex;
}.heading .description {margin-top: 4ex;margin-bottom: 6ex;
}/* -- 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; }
</style>
"""# ------------------------------------------------------------------------# Heading#HEADING_TMPL = """<div class='heading'>
<h1 style="font-family: Microsoft YaHei">%(title)s</h1>
%(parameters)s
<p class='description'>%(description)s</p>
</div>""" # variables: (title, parameters, description)HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s : </strong> %(value)s</p>
""" # variables: (name, value)# ------------------------------------------------------------------------# Report## 汉化,加美化效果 --FindyouREPORT_TMPL = """
<p id='show_detail_line'>
<a class="btn btn-primary" href='javascript:showCase(4)'>概要{ %(passrate)s }</a>
<a class="btn btn-success" href='javascript:showCase(0)'>通过{ %(Pass)s }</a>
<a class="btn btn-danger" href='javascript:showCase(1)'>失败{ %(fail)s }</a>
<a class="btn btn-warning" href='javascript:showCase(2)'>错误{ %(error)s }</a>
<a class="btn btn-info" href='javascript:showCase(3)'>所有{ %(count)s }</a>
</p>
<table id='result_table' class="table table-condensed table-bordered table-hover">
<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 active" style="font-weight: bold;font-size: 14px;"><td>用例集/测试用例</td><td>总计</td><td>通过</td><td>失败</td><td>错误</td><td>详细</td>
</tr>
%(test_list)s
<tr id='total_row' class="text-center info"><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 = r"""
<tr class='%(style)s'><td>%(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)" class="detail" id='%(cid)s'>详细</a></td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)#有output内容的样式,去掉原来JS效果,美化展示效果  -Findyou v0.8.2.3REPORT_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'><!--默认收起output信息 -Findyou<button id='btn_%(tid)s' type="button"  class="btn-xs collapsed" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button><div id='div_%(tid)s' class="collapse">  --><!-- 默认展开output信息 -Findyou --><button id='btn_%(tid)s' type="button"  class="btn-xs" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button><div id='div_%(tid)s' class="collapse in"><pre>%(script)s</pre></div></td>
</tr>
""" # variables: (tid, Class, style, desc, status)# 无output内容样式改为button,按钮效果为不可点击  -Findyou v0.8.2.3REPORT_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'><button id='btn_%(tid)s' type="button"  class="btn-xs" disabled="disabled" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button></td>
</tr>
""" # variables: (tid, Class, style, desc, status)REPORT_TEST_OUTPUT_TMPL = r"""
%(id)s: %(output)s
""" # variables: (id, output)# ------------------------------------------------------------------------# ENDING## 增加返回顶部按钮  --FindyouENDING_TMPL = """<div id='ending'>&nbsp;</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 = []#增加一个测试通过率 --Findyouself.passrate=float(0)def startTest(self, test):TestResult.startTest(self, test)# just one buffer for both stdout and stderrself.outputBuffer = 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):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')class HTMLTestReportCN(Template_mixin):""""""def __init__(self, stream=sys.stdout, verbosity=1,title=None,description=None,tester=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 = descriptionif tester is None:self.tester = self.DEFAULT_TESTERelse:self.tester = testerself.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 >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)sys.stderr.write('\nTime Elapsed: %s' % (self.stopTime-self.startTime))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 not rmap.has_key(cls):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 r#替换测试结果status为通过率 --Findyoudef 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 = []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  )if status:status = ','.join(status)# 合入Github:boafantasy代码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))else:self.passrate = "0.00 %"else:status = 'none'return [(u'测试人员', self.tester),(u'开始时间',startTime),(u'合计耗时',duration),(u'测试结果',status + ",通过率= "+self.passrate),]def generateReport(self, test, result):report_attrs = self.getReportAttributes(result)generator = 'HTMLTestReportCN %s' % __version__stylesheet = self._generate_stylesheet()heading = self._generate_heading(report_attrs)report = self._generate_report(result)ending = self._generate_ending()output = self.HTML_TMPL % dict(title = saxutils.escape(self.title),generator = generator,stylesheet = stylesheet,heading = heading,report = report,ending = ending,)self.stream.write(output.encode('utf8'))def _generate_stylesheet(self):return self.STYLESHEET_TMPL#增加Tester显示 -Findyoudef _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),tester= saxutils.escape(self.tester),)return heading#生成报告  --Findyou添加注释def _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 'warning' or nf > 0 and 'danger' or 'success',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),fail = str(result.failure_count),error = str(result.error_count),passrate =self.passrate,)return reportdef _generate_report_test(self, rows, cid, tid, n, t, o, e):# e.g. 'pt1.1', 'ft1.1', etchas_output = bool(o or e)# ID修改点为下划线,支持Bootstrap折叠展开特效 - Findyou v0.8.2.1#增加error分类 - Findyou v0.8.2.3tid = (n == 0 and 'p' or n == 1 and 'f' or 'e') + '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_TMPL# utf-8 支持中文 - Findyou# o and e should be byte string because they are collected from stdout and stderr?if isinstance(o, str):# TODO: some problem with 'string_escape': it escape \n and mess up formating# uo = unicode(o.encode('string_escape'))try:uo = oexcept:uo = o.decode('utf-8')else:uo = oif isinstance(e, str):# TODO: some problem with 'string_escape': it escape \n and mess up formating# ue = unicode(e.encode('string_escape'))try:ue = eexcept:ue = e.decode('utf-8')else:ue = escript = self.REPORT_TEST_OUTPUT_TMPL % dict(id = tid,output = saxutils.escape(uo+ue),)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 HTMLTestReportCN as the default test runner.# base class's testRunner parameter is not useful because it means# we have to instantiate HTMLTestReportCN before we know self.verbosity.if self.testRunner is None:self.testRunner = HTMLTestReportCN(verbosity=self.verbosity)unittest.TestProgram.runTests(self)main = TestProgram##############################################################################
# Executing this module from the command line
##############################################################################if __name__ == "__main__":main(module=None)

http://www.ppmy.cn/news/1580085.html

相关文章

Android视频渲染SurfaceView强制全屏与原始比例切换

1.创建UI添加强制全屏与播放按钮 2.SurfaceView控件设置全屏显示 3.全屏点击事件处理实现 4.播放点击事件处理 5.使用接口更新强制全屏与原始比例文字 强制全屏/原始比例 点击实现

力扣hot100二刷——二叉树

第二次刷题不在idea写代码&#xff0c;而是直接在leetcode网站上写&#xff0c;“逼”自己掌握常用的函数。 标志掌握程度解释办法⭐Fully 完全掌握看到题目就有思路&#xff0c;编程也很流利⭐⭐Basically 基本掌握需要稍作思考&#xff0c;或者看到提示方法后能解答⭐⭐⭐Sl…

完善 Django 框架以实现传递视频、图片给算法模块进行识别分析

要完善 Django 框架以实现传递视频、图片给算法模块进行识别分析&#xff0c;可按以下步骤进行&#xff1a; 1. 项目初始化 首先&#xff0c;确保你已经安装了 Django 和其他必要的库&#xff08;如 Pillow 用于图片处理&#xff09;。创建一个新的 Django 项目和应用&#x…

甲骨文找回二次验证的方法(超简单)

因为更换手机丢失了二次验证。 然后给客服沟通&#xff0c;获得了找到二次验证的办法&#xff0c;希望对你有用。 1、登录到账号登陆界面&#xff0c;查看地址栏当中自己的IDCE地址&#xff08;yourIDCS_Stripe_here&#xff09;部分&#xff0c;并复制。 https://idcs-yourID…

ZooKeeper的五大核心作用及其在分布式系统中的关键价值

引言 在分布式系统的复杂架构中&#xff0c;协调多个节点的一致性、可靠性和高可用性始终是技术挑战的核心。​Apache ZooKeeper作为业界广泛采用的分布式协调服务&#xff0c;凭借其简洁的树形数据模型&#xff08;ZNode&#xff09;和高效的原子广播协议&#xff08;ZAB&…

Secure and Privacy-Preserving Decentralized Federated Learning同态加密联邦学习文献阅读

Secure and Privacy-Preserving Decentralized Federated Learning for Personalized Recommendations in Consumer Electronics Using Blockchain and Homomorphic Encryption 区块链加同态联邦学习 概括 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目…

游戏引擎学习第161天

回顾并计划今天的工作 我们从头开始编写一款完整的游戏&#xff0c;完全不依赖游戏引擎和库。我们会从最基本的渲染代码开始&#xff0c;一直到高层的AI代码&#xff0c;涵盖其中的一切。 目前&#xff0c;我们正在做一些比较轻松有趣的事情&#xff0c;可以说是比较随意的内…

登录Xshell主机及Linux基本指令

✅博客主页:爆打维c-CSDN博客​​​​​​ &#x1f43e; &#x1f539;分享c、c知识及代码 &#x1f43e; &#x1f539;Gitee代码仓库 五彩斑斓黑1 (colorful-black-1) - Gitee.com 一、操作系统简介 Linux其实跟我们熟知的Window一样&#xff0c;它们都是操作系统。 &#x…