当前位置: 首页 > news >正文

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

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的内容。

测试结果的查看

在这里插入图片描述

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

  • .表示测试用例通过。
  • F 表示测试用例未通过。
  • E 表示测试用例代码有问题。

TestCase(测试用例)的书写

在这里插入图片描述

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

在这里插入图片描述

TestLoader(测试加载)的书写

在这里插入图片描述

Fixture(测试夹具)的书写

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

方法级别

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

在这里插入图片描述

类级别

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

在这里插入图片描述

模块级别

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

在这里插入图片描述

UnitTest 断言

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

断言的结果:

  • True————该测试用例通过。
  • False————代码抛出异常,该测试用例不通过。

为什么学习断言

  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(生成英文测试报告)
"""
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(生成中文测试报告)
#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)

相关文章:

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

UnitTest框架 UnitTest是Python自带一个单元测试框架&#xff0c;常用它来做单元测试。 注意:对于测试来说&#xff0c;UnitTest框架的作用是 自动化脚本(用例代码)执行框架————(使用UnitTest框架来管理 运行多个测试用例的) 为什么使用UnitTest框架 能够组织多个用例去执…...

后端接口开发完成后,接口地址访问不到提示404,Spring项目的包结构错误

后端接口开发完成后&#xff0c;接口地址访问不到提示404&#xff0c;Spring项目的包结构错误 是因为包结构错误&#xff0c;导致无法在请求地址下找到对应的方法&#xff0c;原来错误的包结构&#xff08;自建controller、service、config等包建到了项目包之外、而非项目包之下…...

算法竞赛-基础算法-位运算

目录 1.快速幂 2.快速乘 3.lowbit(n) 4.其他 5.相关题目 6.小结 引言&#xff1a;位运算的主要特点之一是在二进制表示下不进位&#xff0c;一下为一些基础的位运算操作&#xff1a; 与或非异或and,&or,|not,~xor 1.快速幂 快速幂的计算原理就是基于位运算&#x…...

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能📚页面效果📚指令输入�…...

深入解析音频编解码器(Audio CODEC):硬件、接口与驱动开发

音频编解码器&#xff08;Audio CODEC&#xff09;是音频处理系统中的核心组件&#xff0c;负责 模拟信号与数字信号的相互转换&#xff0c;广泛应用于 智能音箱、嵌入式系统、消费电子产品 等设备。本篇文章将从 硬件结构、接口解析、驱动开发 和 软件配置 等方面&#xff0c;…...

什么是 Fisher 信息矩阵

什么是 Fisher 信息矩阵 Fisher 信息矩阵是统计学和机器学习中一个重要的概念,它用于衡量样本数据所包含的关于模型参数的信息量。 伯努利分布示例 问题描述 假设我们有一个服从伯努利分布的随机变量 X X X,其概率质量函数为 P ( X ...

【项目合集】智能语音小车-微信小程序控制

功能需求&#xff1a; 车子检测环境温度、湿度&#xff0c;上报 APP、WEB 端显示实时数据可通过 APP 控制小车前进、左转、右转可通过语音控制小车前进后退车上一个 LED 灯&#xff0c;可通过 WEB、小程序控制在 APP、WEB 上均可注册登录 硬件清单 硬件 功能 备注 ESP32 …...

Vue3项目中可以尝试封装那些组件

在 Vue 3 项目中&#xff0c;组件的封装可以根据功能、复用性和业务需求进行划分。以下是一些常见的组件类型&#xff0c;适合封装为独立组件&#xff1a; 1. 基础 UI 组件 按钮 (Button) 封装不同样式、大小、状态的按钮。支持 disabled、loading 等状态。 输入框 (Input) 封…...

Leetcode——151.反转字符串中的单词

题解一 思路 最开始的想法是把一个字符串分为字符串数组&#xff0c;但是不知道一共有几个单词&#xff08;当时没想起来split()&#xff09;&#xff0c;所以选择了用ArrayList储存字符串&#xff0c;在输出时没有考虑ArrayList可以存储空字符串&#xff0c;所以最开始的输出…...

Deepseek+QuickAPI:打造 MySQL AI 智能体入门篇(一)

目录 一、什么是 MySQL AI 智能体&#xff1f; 二、准备工作&#xff1a;认识工具 1. Deepseek 的大模型能力 2. QuickAPI 的功能 3. MySQL 数据库 三、动手实践&#xff1a;用自然语言打造智能体 1. 创建一个用户表 2. 添加样本数据 3. 执行查询 四、效果展示 五、…...

Redis系列:深入理解缓存穿透、缓存击穿、缓存雪崩及其解决方案

在使用Redis作为缓存系统时&#xff0c;我们经常会遇到“缓存穿透”、“缓存击穿”和“缓存雪崩”等问题&#xff0c;这些问题一旦出现&#xff0c;会严重影响应用性能甚至造成服务不可用。因此&#xff0c;理解这些问题的产生原因和解决方案非常重要。 本文将全面讲解缓存穿透…...

python局部变量和全局变量

文章目录 1.局部变量和全局变量2.局部变量2.1 局部变量的作用2.2 局部变量的生命周期 3. 全局变量3.1 函数不能直接修改全局变量的引用3.2 在函数内部修改全局变量的值3.3 全局变量定义的位置3.4 全局变量命名的建议 1.局部变量和全局变量 &#xff08;1&#xff09;局部变量 …...

⭐算法OJ⭐两数之和【哈希表】(C++ 实现)Two Sum

“两数之和”&#xff08;Two Sum&#xff09;是一道非常经典的算法题目&#xff0c;几乎是算法入门和面试准备的必做题之一。它的经典性体现在以下几个方面&#xff1a; 1. 算法入门的基础题目 这道题目是许多初学者接触 哈希表&#xff08;Hash Table&#xff09; 或 字典&…...

【AVRCP】Notification PDUs 深入解析与应用

目录 一、Notification PDUs 概述 二、GetPlayStatus:同步查询播放状态 2.1 命令功能与应用场景 2.2 请求格式(CT → TG) 2.3 响应格式(TG → CT) 2.4 注意事项 2.5 协议实现示例(伪代码) 三、RegisterNotification:异步事件订阅 3.1 命令概述 3.2 命令格式 …...

算法题(100):腐烂的苹果

审题&#xff1a; 本题需要我们判断苹果是否可以完全腐烂&#xff0c;若可以完全腐烂&#xff0c;那么最短腐烂的所需时间是多少 思路&#xff1a; 方法一&#xff1a;多源BFS 首先我们分析腐烂过程&#xff0c;第一批腐烂苹果开始扩散&#xff0c;然后第二批腐烂苹果继续扩散&…...

某快餐店用户市场数据挖掘与可视化

1、必要库的载入 import pandas as pd import matplotlib.pyplot as plt import seaborn as sns2、加载并清洗数据 # 2.1 加载数据 df pd.read_csv(/home/mw/input/survey6263/mcdonalds.csv)# 2.2 数据清洗 # 2.2.1 检查缺失值 print(缺失值情况&#xff1a;) print(df.isn…...

【微服务】如何用Azure容器应用Job处理异步HTTP API请求

【微服务】如何用Azure容器应用Job处理异步HTTP API请求 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 【微服务】如何用Azure容器应用Job处理异步HTTP API请求Azure容器应用中的长…...

安卓edge://inspect 和 chrome://inspect调试移动设备上的网页

edge://inspect 和 chrome://inspect 是用于调试浏览器中运行的网页和移动设备上的网页的工具。这两个工具分别属于 Microsoft Edge 和 Google Chrome 浏览器。以下是它们的详细介绍&#xff1a; chrome://inspect 如果是直接使用数据线调试&#xff0c;则只需要执行下面的第一…...

TX-LCN 框架

TX-LCN 框架通俗教学&#xff08;面试场景版&#xff09; 一句话概括 TX-LCN 是分布式事务的 “交通警察”&#xff0c;确保多个微服务操作要么全部成功&#xff08;比如转账扣款和到账&#xff09;&#xff0c;要么全部回滚&#xff08;比如网购下单失败后库存自动恢复&#…...

玩转github

me github 可以给仓库添加开发人员吗 4o 是的&#xff0c;GitHub允许仓库管理员为仓库添加开发人员&#xff0c;并设置这些开发人员的角色和权限。这里是一个简单的步骤指导&#xff0c;教你如何给一个 GitHub 仓库添加开发人员&#xff1a; 前提条件 你必须有这个仓库的权限&…...

Dubbo 深度解析

Dubbo 深度解析与实战指南 一、Dubbo 核心设计理念与应用场景 1.1 为什么需要 Dubbo&#xff1f; 随着互联网业务规模扩大&#xff0c;单体架构面临以下挑战&#xff1a; 服务依赖复杂&#xff1a;模块间耦合度高&#xff0c;难以独立迭代[[5]]。性能瓶颈&#xff1a;单一应…...

基于javaweb的SpringBoot校园运动会管理系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...

Socket封装---模板方法类

目录 一、模板方法类 二、Socket的框架 三、TCPSocket对父类的虚函数重写 在平时写网络代码的时候&#xff0c;经常会涉及到socket套接字的部分&#xff0c;这一部分用的十分频繁&#xff0c;但是流程都是固定的&#xff0c;我实在是饱受其苦&#xff0c;但是由于C不像java一…...

牛客周赛84 题解 Java ABCDEFG AK实录

目录 题目地址 做题情况 A 题 B 题 C / D 题 E 题 F / G 题 题目地址 牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ 做题情况 A 题 import java.io.*; import java.math.*; import java.util.*;// xixi♡西 public class Main {static IOS scnew…...

如何使用MySQL快速定位慢SQL问题?企业级开发中常见业务场景中实际发生的例子,涉及分页查询问题。(二)

如何使用MySQL快速定位慢SQL问题&#xff1f; 在企业级开发中&#xff0c;尤其是涉及到订单查询的业务时&#xff0c;经常会发生慢查询的问题。比如用户翻页到后面的页数时&#xff0c;查询变慢&#xff0c;因为传统的LIMIT offset, size在数据量大时效率低下。这时候&#xff…...

双链笔记新选择!使用Docker私有化部署Logseq知识库远程团队协作

前言&#xff1a;嘿&#xff0c;小伙伴们&#xff0c;今天要给大家安利一个超酷的技能——如何在本地Linux服务器上使用Docker轻松搭建Logseq笔记软件&#xff0c;并通过cpolar内网穿透工具实现远程访问。大家都知道&#xff0c;在快节奏的工作和学习中&#xff0c;一个好的笔记…...

C# 不同框架如何调用framework 和 net core

在 C# 中实现进程间通信&#xff08;IPC&#xff0c;Inter-Process Communication&#xff09;有多种方式&#xff0c;适用于不同场景。以下是常见 IPC 方法的实现方案、代码示例及适用场景对比&#xff1a; 1. 命名管道&#xff08;Named Pipes&#xff09; 特点&#xff1a;…...

【Linux-传输层协议TCP】TCP协议段格式+确认应答+超时重传+连接管理机制(三次握手、四次挥手、理解TIME_WAIT + CLOSE_WAIT)

TCP协议 TCP全称为“传输控制协议&#xff08;Transmission Control Protocol&#xff09;”人如其名&#xff0c;要对数据的传输进行一个详细的控制。 1.TCP协议段格式 下面是TCP报头各个字段的表格形式&#xff1a; 字段名称字段大小描述源端口16位发送端TCP端口号。目的端…...

怎样使用Modbus转Profinet网关连接USB转485模拟从站配置案例

怎样使用Modbus转Profinet网关连接USB转485模拟从站配置案例 Modbus转profinet网关可以将Modbus协议转化为profinet协议&#xff0c;以实现设备之间的数据交互。在实际使用过程中&#xff0c;我们需要使用Modbus协议进行设备通讯&#xff0c;而profinet协议则是用于工业自动化…...

从“自习室令牌”到线程同步:探秘锁与条件变量

目录 互斥 为什么需要锁 锁的原理--互斥 锁的使用 同步 锁的问题 条件变量 互斥 为什么需要锁 先看结果&#xff1a; 以下代码是我模拟创建线程抢票&#xff0c;由于不加锁导致票抢到了负数 main.cc: #include<vector> #include<iostream> #include"…...

Java 大视界 -- Java 大数据在智能政务舆情引导与公共危机管理中的应用(138)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

LeetCode[59]螺旋矩阵Ⅱ

思路&#xff1a; 这种题我第一次确实没做出来&#xff0c;第一次做的时候一圈一圈处理&#xff0c;发现圈数越往里&#xff0c;越乱&#xff0c;原来之前是没从圈数开始遍历。思路&#xff1a;第一个大循环先遍历圈数&#xff0c;一共遍历n/2圈&#xff0c;如果是奇数那就最后…...

【Python 算法 1.线性枚举】

我装作漠视一切&#xff0c;以为这样就可以不在乎 —— 25.3.17 一、线性枚举的基本概念 1.时间复杂度 线性枚举的时间复杂度为 O(nm)&#xff0c;其中 n是线性表的长度。m 是每次操作的量级&#xff0c;对于求最大值和求和来说&#xff0c;因为操作比较简单&#xff0c;所以 …...

C# 嵌套类 详解

一个类在它的包容类外没有多大意义&#xff0c;就适合设计成嵌套类。 嵌套类&#xff1a;定义在另一个类内部的类。 包容类&#xff08;外部类&#xff09;&#xff1a;包含嵌套类的类。 嵌套类的独特之处是可以为类自身指定private访问修饰符。 嵌套类能访问包容类的任何成…...

深度学习中学习率调整策略

学习率衰减策略是深度学习优化过程中的一个关键因素&#xff0c;它决定了训练过程中学习率的调整方式&#xff0c;从而影响模型收敛的速度和效果。不同的衰减策略在不同的任务和模型上可能有不同的表现&#xff0c;下面从我用到过的几个衰减策略进行记录&#xff0c;后续慢慢跟…...

基于Flask的东方财富网股票数据可视化分析系统

【大数据】基于Flask的东方财富网股票数据可视化分析系统 &#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统能够高效地从东方财富网抓取股票数据&#xff0c;并通过Python的强大数据处理能…...

卓越的用户体验需要智能内容

摘要&#xff1a;这篇文章指出静态文档已无法满足现代用户的需求&#xff0c;而智能内容则是构建卓越用户体验的关键。文章从智能内容的定义、优势和实际应用等方面进行了详细阐述&#xff0c;并强调了企业应积极拥抱智能内容&#xff0c;以提升客户满意度、降低成本并创造新的…...

c++基础知识-图论进阶

一、拓扑排序 1、基础知识 1&#xff09;什么是拓扑排序 对一个有向无环图G进行拓扑排序&#xff0c;是将G中所有顶点排成一个线性序列&#xff0c;使得图中任意一对顶点u和v&#xff0c;若&#xff0c;则u在线性序列中出现在v之前。 2&#xff09;拓扑排序的操作方法 重复执行…...

Java 买百鸡问题

二阶买百鸡问题&#xff1a;母鸡5元一只&#xff0c;公鸡3元一只&#xff0c;35元可以有多少种买法刚好用完&#xff1f; package com.software.first;import java.util.Scanner;public class Test {public static void main(String[] args) {Scanner scan new Scanner(Syste…...

为什么手机上用 mA 和 mAh 来表示功耗和能耗?

在手机上&#xff0c;我们经常会看到 mA&#xff08;毫安&#xff09; 和 mAh&#xff08;毫安时&#xff09; 这两个单位&#xff0c;它们分别用来表示 功耗水平 和 能耗水平。为什么用这两个单位呢&#xff1f;其实这和电流、时间以及电池的特性有关。 1.mA&#xff08;毫安…...

使用SDKMAN!安装springboot

在 Ubuntu 环境中使用 sdk install springboot 命令之前&#xff0c;您需要先安装 SDKMAN!&#xff08;Software Development Kit Manager&#xff09;。以下是详细的安装步骤&#xff1a; 安装 SDKMAN! 打开终端。 运行以下命令以安装 SDKMAN!&#xff1a; curl -s "htt…...

【AI学习从零至壹】Pytorch神经⽹络

Pytorch神经⽹络 神经网络简介神经元激活函数 神经网络神经⽹络的⼯作过程前向传播(forward) 反向传播(backward)训练神经⽹络 Pytorch搭建并训练神经⽹络神经⽹络构建和训练过程数据预处理构建模型优化器&提取训练数据训练样本 神经网络简介 神经元 在深度学习中&#x…...

Linux应用 / 驱动程序崩溃调试

文章目录 前言一、GDB 使用1. GDB 介绍2. Debug版本与Release版本3. 指令演示3.1 显示行号3.2 断点设置3.3 查看断点信息3.4 删除断点3.5 开启 / 禁用断点3.6 运行3.7 打印 / 追踪变量 4. 最常用指令 二、Linux 应用程序调试1. codedump 介绍2. 在 Linux 系统中使用 coredump2.…...

k8s集群-kubeadm init

为了使用阿里云的镜像源加速 kubeadm init 初始化 Kubernetes 集群的过程&#xff0c;你需要修改 kubeadm 的配置文件以指向阿里云提供的镜像仓库。以下是具体步骤&#xff1a; 1. 创建或编辑 kubeadm 配置文件 首先&#xff0c;创建一个 kubeadm 的配置文件&#xff08;如果还…...

Python 视频爬取教程

文章目录 前言基本原理环境准备Python安装选择Python开发环境安装必要库 示例 1&#xff1a;爬取简单直链视频示例 2&#xff1a;爬取基于 HTML5 的视频&#xff08;以某简单视频网站为例&#xff09; 前言 以下是一个较为完整的 Python 视频爬取教程&#xff0c;包含基本原理…...

Linux应用软件编程(多任务:进程间通信)

一.进程间通信 同一主机下&#xff1a; &#xff08;1&#xff09;无名管道&#xff1a;pipe &#xff08;2&#xff09;有名管道&#xff1a;fifo &#xff08;3&#xff09;信号&#xff1a;异步通知机制 &#xff08;4&#xff09;共享内存&a…...

工厂方法模式和抽象工厂模式详解

由于工厂方法模式和抽象工厂模式有点类似&#xff0c;可以放着一块说下。 一、工厂方法模式 (Factory Method Pattern) 场景描述 假设需要实现一个跨平台日志系统&#xff0c;支持文件日志和数据库日志&#xff0c;且未来可能扩展其他日志方式。通过工厂方法模式&#xff0c;…...

js给后端发送请求的方式有哪些

在 JavaScript 中&#xff0c;有多种方式可以向后端发送请求&#xff0c;以下为你详细介绍&#xff1a; 1. XMLHttpRequest XMLHttpRequest 是最早用于在浏览器和服务器间进行异步通信的 API。虽然它使用起来相对复杂&#xff0c;但兼容性很好&#xff0c;能兼容较旧的浏览器…...

无人机吊舱模块更换技术难点分析!

一、模块更换的可行性 模块化设计的支持 部分吊舱采用模块化设计&#xff0c;允许根据任务需求更换传感器模块。例如&#xff0c;某些吊舱系统支持定制化组合&#xff0c;如“红外激光测距”或“可见光激光测距”等。这表明在硬件结构上&#xff0c;若吊舱预留了标准化的接…...

高数1.4 无穷小与无穷大

1.无穷小 1.1.定义 1.2 常规性质 2.无穷大 2.1 定义 2.无穷小与无穷大的关系...