单元测试框架unittest(单元测试概念,unittest框架介绍,assert断言,setup介绍,HTMLTestRunner报告)
单元测试基本概念
单元测试
单元测试(unit testing)是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。

越早介入越好、开发做
单元测试需要注意什么?

明确需求的预期是什么
单元测试框架列举
 
unittest pytest Nose Mock
单元测试覆盖率

语句覆盖、条件覆盖、判断覆盖、路径覆盖
被测试的代码片段

语句覆盖 -- 行覆盖

判断覆盖 -- 整个语句的true 和 false

条件覆盖 -- 每一个判断语句的true 和 false

路径覆盖 -- 可能的所有执行路径

单元测试框架
在单元测试框架出现之前,开发人员在创建可执行测试时饱受折磨。最初的做法是在应用程序中创建一个窗口,配有"测试控制工具(harness)"。它只是一个窗口,每个测试对应一个按钮。这些测试的结果要么是一个消息框,要么是直接在窗体本身给出某种显示结果。由于每个测试都需要一个按钮,所以这些窗口很快就会变得拥挤、不可管理。
单元测试框架提供了一种统一的编程模型,可以将测试定义为一些简单的类,这些类中的方法可以调用希望测试的应用程序代码。开发人员不需要编写自己的测试控制工具;单元测试框架提供了测试运行程序(runner),只需要单击按钮就可以执行所有测试。利用单元测试框架,可以很轻松地插入、设置和分解有关测试的功能。测试失败时,测试运行程序可以提供有关失败的信息,包含任何可供利用的异常信息和堆栈跟踪。 不同编程语言有不同的单元测试框架,如Java 的Junit, TestNg, c#的 Nunit,Python的unittest,Pyunit,testtools, subunit....
单元测试框架作用
- 提供用例组织与执行
- 提供丰富的断言方法
- 提供丰富的日志与测试结果
Python单元测试框架——unittest
unittest官方文档 https://docs.python.org/zh-cn/3/library/unittest.html
unittest 测试框架
- python 自带的单元测试框架,常用于单元测试
- 在自动化测试中提供用例组织和执行
- 提供丰富的断言语法-验证函数
- 加上HTMLTestRunner可以生成html报告
unittest编写规范
- 测试模块首先import unittest
- 测试类必须继承 unittest.TestCase
- 测试方法必须以"test_"开头
unittes工作原理

unittest核心要素
TestCase
一个TestCase的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。
单元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。
TestSuite
而多个测试用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。 TestLoader是用来加载TestCase到TestSuite中的,其中有几个loadTestsFrom__()方法,就是从各 个地方寻找TestCase,创建它们的实例,然后add到TestSuite中,再返回一个TestSuite实例。
TextTestRunner
TextTestRunner是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。 测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,
成功了多少,失败了多少等信息。
Fixture
而对一个测试用例环境的搭建和销毁,是一个fixture。
测试框架结构

setUp 和 tearDown
- 基于方法级别的setUp tearDown
执行每个测试方法都会执行一次setUp 和tearDown
- 基于类级别的setUpClass 和 tearDownClass
执行这个类里面的所有测试方法时 只执行一次setUp tearDown
需要加装饰器@classmethod
import unittest class TestMethods(unittest.TestCase): @classmethod def setUpClass(self) -> None: print('>>>>>>>>>>>method setUpClass') @classmethod def tearDownClass(self) -> None: print('>>>>>>>>>>>method tearDownClass') def test_upper(self): print('upper') self.assertEqual('foo'.upper(), 'FOO') def test_isupper(self): print('isupper') self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) class TestMethods1(unittest.TestCase): def setUp(self): print('method1 setup') def tearDown(self) -> None: print('method1 tearDown') def test_upper1(self): print('upper1') self.assertEqual('foo'.upper(), 'FOO') def test_isupper1(self): print('isupper1') self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) def test_split1(self): s = 'hello world1' print(s) self.assertEqual(s.split(), ['hello', 'world1']) # check that s.split fails when the separator is not a string with self.assertRaises(TypeError): s.split(2) if __name__ == '__main__': suite1 = unittest.TestLoader().loadTestsFromTestCase(TestMethods) suite2 = unittest.TestLoader().loadTestsFromTestCase(TestMethods1) suite = unittest.TestSuite([suite1,suite2]) unittest.TextTestRunner(verbosity=2).run(suite)
unittest断言
用unittest组件测试用例时,断言有很多,下面文档中包含常用的几种断言方法
https://www.cnblogs.com/fanpl/articles/9218067.html
最常用的断言方法:assertEqual() assertNotEqual() assertTrue() assertFalse()
unittest测试用例编写原则及执行顺序
- 编写unittest测试用例的原则
unittest会自动识别以test开头的函数作为测试代码,且test一定要小写
- 执行测试用例的顺序
以test后面字母的顺序,比如:test_a test_b test_c
unittest执行测试用例
多个测试用例的集合就是测试套件,通过测试套件来管理测试用例
执行方法一: 执行本文件中所有测试用例
if __name__ == '__main__': unittest.main()
执行方法二: 执行指定的测试用例
if __name__ == '__main__': suite = unittest.TestSuite() suite.addTest(TestMethods('test_split')) suite.addTest(TestMethods('test_isupper')) runner = unittest.TextTestRunner() runner.run(suite)
执行方法三: 执行多个类中的所有测试用例
if __name__ == '__main__': suite1 = unittest.TestLoader().loadTestsFromTestCase(TestMethods) suite2 = unittest.TestLoader().loadTestsFromTestCase(TestMethods1) suite = unittest.TestSuite([suite1,suite2]) unittest.TextTestRunner(verbosity=2).run(suite)
执行方法四: 匹配目录下所有以test开头的py文件,执行这些文件中所有测试用例
import unittest import HTMLTestRunner reportpath = 'pyresult.html' test_dir = './testcase' discover = unittest.defaultTestLoader.discover(test_dir,pattern='test*.py') with open(reportpath, 'wb') as fp: # 定义测试报告的标题与描述 runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u'我是fanpl的测试报告标题', description=u'我是fanpl的测试报告描述') runner.run(discover) fp.close()
测试用例执行过程
unittest测试报告 --HTMLTestRunner
自动化测试执行完成之后,我们需要生成测试报告来查看测试结果,使用HTMLTestRunner模块可以直接生成Html格式的报告。
存放路径: 将修改完成的模块存放在Python路径下Scripts目录里面即可
下面是Python3可用的一个HTMLTestRunner文件,可以直接拿来用
 
""" 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 unittest import 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 file fp = 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 options runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">' # run the test runner.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 copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Wai Yip Tung nor the names of its contributors may be used to endorse or promote products derived from this software without specific 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 History 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 #2.7版本为 import StringIO 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 = fp def 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) # ---------------------------------------------------------------------- # Template class Template_mixin(object): """ Define a HTML template for report customerization and generation. Overall structure of an HTML report HTML +------------------------+ |<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 Template HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <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,'&'); s = s.replace(/</g,'<'); s = s.replace(/>/g,'>'); return s; } /* obsoleted by detail in <div> function showOutput(id, name) { var w = window.open("", //url name, "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> </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'> </div>""" # -------------------- The end of the Template class ------------------- TestResult = unittest.TestResult class _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 = None self.stderr0 = None self.success_count = 0 self.failure_count = 0 self.error_count = 0 self.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 stderr # 2.7版本为 self.outputBuffer = StringIO.StringIO() self.outputBuffer = io.StringIO() stdout_redirector.fp = self.outputBuffer stderr_redirector.fp = self.outputBuffer self.stdout0 = sys.stdout self.stderr0 = sys.stderr sys.stdout = stdout_redirector sys.stderr = stderr_redirector def complete_output(self): """ Disconnect output redirection and return buffer. Safe to call multiple times. """ if self.stdout0: sys.stdout = self.stdout0 sys.stderr = self.stderr0 self.stdout0 = None self.stderr0 = None return 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 += 1 TestResult.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 += 1 TestResult.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 += 1 TestResult.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 = stream self.verbosity = verbosity if title is None: self.title = self.DEFAULT_TITLE else: self.title = title if description is None: self.description = self.DEFAULT_DESCRIPTION else: self.description = description self.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)) #2.7版本 print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime) return result def sortResult(self, result_list): # unittest does not seems to run in any particular order. # Here at least we want to group them together by class. rmap = {} classes = [] for n,t,o,e in result_list: cls = t.__class__ # 2.7版本 if not rmap.has_key(cls) if not cls in rmap: rmap[cls] = [] classes.append(cls) rmap[cls].append((n,t,o,e)) r = [(cls, rmap[cls]) for cls in classes] return r def 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_TMPL def _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 heading def _generate_report(self, result): rows = [] sortedResult = self.sortResult(result.result) for cid, (cls, cls_results) in enumerate(sortedResult): # subtotal for a class np = nf = ne = 0 for n,t,o,e in cls_results: if n == 0: np += 1 elif n == 1: nf += 1 else: ne += 1 # format class description if 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 name row = 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 report def _generate_report_test(self, rows, cid, tid, n, t, o, e): # e.g. 'pt1.1', 'ft1.1', etc has_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 name tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL # o and e should be byte string because they are collected from stdout and stderr? if isinstance(o,str): uo = e # TODO: some problem with 'string_escape': it escape \n and mess up formating # uo = unicode(o.encode('string_escape')) # 2.7版本uo = o.decode('latin-1') else: uo = o if isinstance(e,str): ue = e # TODO: some problem with 'string_escape': it escape \n and mess up formating # ue = unicode(e.encode('string_escape')) # 2.7 版本 ue = e.decode('latin-1') else: ue = e script = 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: return def _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 base class 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)
#/usr/bin python #-*- coding:UTF-8 -*- from selenium import webdriver from time import sleep import unittest class TestBaidu(unittest.TestCase): def setUp(self): print("baidu test ……") self.driver = webdriver.Chrome() self.driver.get("https://www.baidu.com/") def test_driver(self): self.driver.find_element_by_id('kw').clear() self.driver.find_element_by_id('kw').send_keys('Selenium 官网') self.driver.find_element_by_id('su').click() sleep(3) def tearDown(self): print("baidu end!!!!") self.driver.quit() if __name__ == "__main__": unittest.main()
执行用例的脚本
#/usr/bin python #-*- coding:UTF-8 -*- import unittest from time import * from HTMLTestRunner_PY3 import * test_dir='./' discover=unittest.defaultTestLoader.discover(test_dir,pattern="web_baidu.py") if __name__ == '__main__': #存放报告的文件夹 report_dir='./test_reports' #报告命名时间格式化 now=time.strftime("%Y-%m-%d %H_%M_%S") #报告文件完整路径 report_name=report_dir+'/'+now+'result.html' #打开文件在报告文件写入测试结果 with open(report_name,'wb')as f: runer=HTMLTestRunner(stream=f,title="Test Report",description='Test case result') runer.run(discover) f.close()
注:
HTMLTestRunner:stream 指定测试报告文件;title 用于定义测试报告的标题;description 用于定义测试报告的副标题。
生成的报告样式

总结:
- unittest是Python自带的单元测试框架,我们可以用其来作为我们自动化测试框架的用例组织执行框架。
- unittest的流程:写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例。
- 一个class继承unittest.TestCase即是一个TestCase,其中以 test开头的方法在load时被加载为一个真正的TestCase。
- verbosity参数可以控制执行结果的输出,0是简单报告、1是一般报告、2是详细报告。
- 可以通过addTest和addTests向suite中添加case或suite,可以用TestLoader的loadTestsFrom__()方法。
- 用 setUp()、tearDown()、setUpClass()以及tearDownClass()可以在用例执行前布置环境,以及在用例执行后清理环境
- 我们可以通过skip,skipIf,skipUnless装饰器跳过某个case,或者用TestCase.skipTest方法。
- 参数中加stream,可以将报告输出到文件:可以用TextTestRunner输出txt报告,以及可以用HTMLTestRunner输出html报告。
unittest案例
构造一个类Math 包含整数加法运算
#/usr/bin python #-*- coding:UTF-8 -*- ##计算模块## class Math: #用于存放初始化数据 def __int__(self,a,b): self.a = a self.b = b #待测的方法 def add(a,b): return a+b
测试案例的脚本:
#/usr/bin python #-*- coding:UTF-8 -*- from calculator import Math import unittest #测试用例继承TestCase类 class TestMath(unittest.TestCase): def setUp(self): print("test start") def test_add(self): j = Math.add(1,2) self.assertEqual(j,3) #self.assertNotEqual(j,4) print("######test_add######") def tearDown(self): print("test end") if __name__ == '__main__': ##构造测试集 suite = unittest.TestSuite() suite.addTest(TestMath("test_add")) ##执行测试 runner = unittest.TextTestRunner() runner.run(suite)
 
                    
                     
                    
                 
                    
                
 
  
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号