一、直接使用TestCase

注意所有测试方法都需要以test开头。代码如下:

  1. import unittest
  2.  
  3. class Test1(unittest.TestCase):
  4. @classmethod
  5. def setUpClass(self):
  6. print("execute setUpClass")
  7.  
  8. @classmethod
  9. def tearDownClass(self):
  10. print("execute tearDownClass")
  11.  
  12. def setUp(self):
  13. print("execute setUp")
  14.  
  15. def tearDown(self):
  16. print("execute tearDown")
  17.  
  18. def test_one(self):
  19. print('execute test_one')
  20. self.assertTrue('FOO'.isupper())
  21.  
  22. def test_two(self):
  23. print('execute test_two')
  24.  
  25. if __name__ == '__main__':
  26. unittest.main()

执行如下:

二、使用TestSuite

直接写TestCase执行时是按字母排序的顺序执行的,如果要设定测试用例的执行先后顺序则需要将TestCase封装到TestSuite。代码如下:

  1. import unittest
  2.  
  3. class Test2(unittest.TestCase):
  4. @classmethod
  5. def setUpClass(self):
  6. print("execute setUpClass")
  7.  
  8. @classmethod
  9. def tearDownClass(self):
  10. print("execute tearDownClass")
  11.  
  12. def setUp(self):
  13. print("execute setUp")
  14.  
  15. def tearDown(self):
  16. print("execute tearDown")
  17.  
  18. def test_one(self):
  19. print('execute test_one')
  20. self.assertTrue('FOO'.isupper())
  21.  
  22. def test_two(self):
  23. print('execute test_two')
  24.  
  25. if __name__ == '__main__':
  26. suite = unittest.TestSuite()
  27. # Test2是要测试的类名,test_two是要执行的测试方法
  28. suite.addTest(Test2("test_two"))
  29. suite.addTest(Test2("test_one"))
  30. runner = unittest.TextTestRunner()
  31. runner.run(suite)

执行如下,可以看到先添加的test_two先执行:

三、使用HTMLTestRunner

我们可能还会希望生成一个简单的HTML报告,可使用HTMLTestRunner实现。但pypi官网上最新的都是只支持python2.x的0.8.2版本。可将以下代码自行保存成HTMLTestRunner.py放到自己项目目录下

  1. # -*- coding: utf-8 -*-
  2. """
  3. A TestRunner for use with the Python unit testing framework. It
  4. generates a HTML report to show the result at a glance.
  5.  
  6. The simplest way to use this is to invoke its main method. E.g.
  7.  
  8. import unittest
  9. import HTMLTestRunner
  10.  
  11. ... define your tests ...
  12.  
  13. if __name__ == '__main__':
  14. HTMLTestRunner.main()
  15.  
  16. For more customization options, instantiates a HTMLTestRunner object.
  17. HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
  18.  
  19. # output to a file
  20. fp = file('my_report.html', 'wb')
  21. runner = HTMLTestRunner.HTMLTestRunner(
  22. stream=fp,
  23. title='My unit test',
  24. description='This demonstrates the report output by HTMLTestRunner.'
  25. )
  26.  
  27. # Use an external stylesheet.
  28. # See the Template_mixin class for more customizable options
  29. runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
  30.  
  31. # run the test
  32. runner.run(my_test_suite)
  33.  
  34. ------------------------------------------------------------------------
  35. Copyright (c) 2004-2007, Wai Yip Tung
  36. All rights reserved.
  37.  
  38. Redistribution and use in source and binary forms, with or without
  39. modification, are permitted provided that the following conditions are
  40. met:
  41.  
  42. * Redistributions of source code must retain the above copyright notice,
  43. this list of conditions and the following disclaimer.
  44. * Redistributions in binary form must reproduce the above copyright
  45. notice, this list of conditions and the following disclaimer in the
  46. documentation and/or other materials provided with the distribution.
  47. * Neither the name Wai Yip Tung nor the names of its contributors may be
  48. used to endorse or promote products derived from this software without
  49. specific prior written permission.
  50.  
  51. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  52. IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  53. TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  54. PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
  55. OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  56. EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  57. PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  58. PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  59. LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  60. NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  61. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  62. """
  63.  
  64. # URL: http://tungwaiyip.info/software/HTMLTestRunner.html
  65.  
  66. __author__ = "Wai Yip Tung"
  67. __version__ = "0.9.1"
  68.  
  69. """
  70. Change History
  71. Version 0.9.1
  72. * 用Echarts添加执行情况统计图 (灰蓝)
  73.  
  74. Version 0.9.0
  75. * 改成Python 3.x (灰蓝)
  76.  
  77. Version 0.8.3
  78. * 使用 Bootstrap稍加美化 (灰蓝)
  79. * 改为中文 (灰蓝)
  80.  
  81. Version 0.8.2
  82. * Show output inline instead of popup window (Viorel Lupu).
  83.  
  84. Version in 0.8.1
  85. * Validated XHTML (Wolfgang Borgert).
  86. * Added description of test classes and test cases.
  87.  
  88. Version in 0.8.0
  89. * Define Template_mixin class for customization.
  90. * Workaround a IE 6 bug that it does not treat <script> block as CDATA.
  91.  
  92. Version in 0.7.1
  93. * Back port to Python 2.3 (Frank Horowitz).
  94. * Fix missing scroll bars in detail log (Podi).
  95. """
  96.  
  97. # TODO: color stderr
  98. # TODO: simplify javascript using ,ore than 1 class in the class attribute?
  99.  
  100. import datetime
  101. import sys
  102. import io
  103. import time
  104. import unittest
  105. from xml.sax import saxutils
  106.  
  107. # ------------------------------------------------------------------------
  108. # The redirectors below are used to capture output during testing. Output
  109. # sent to sys.stdout and sys.stderr are automatically captured. However
  110. # in some cases sys.stdout is already cached before HTMLTestRunner is
  111. # invoked (e.g. calling logging.basicConfig). In order to capture those
  112. # output, use the redirectors for the cached stream.
  113. #
  114. # e.g.
  115. # >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
  116. # >>>
  117.  
  118. class OutputRedirector(object):
  119. """ Wrapper to redirect stdout or stderr """
  120. def __init__(self, fp):
  121. self.fp = fp
  122.  
  123. def write(self, s):
  124. self.fp.write(s)
  125.  
  126. def writelines(self, lines):
  127. self.fp.writelines(lines)
  128.  
  129. def flush(self):
  130. self.fp.flush()
  131.  
  132. stdout_redirector = OutputRedirector(sys.stdout)
  133. stderr_redirector = OutputRedirector(sys.stderr)
  134.  
  135. # ----------------------------------------------------------------------
  136. # Template
  137.  
  138. class Template_mixin(object):
  139. """
  140. Define a HTML template for report customerization and generation.
  141.  
  142. Overall structure of an HTML report
  143.  
  144. HTML
  145. +------------------------+
  146. |<html> |
  147. | <head> |
  148. | |
  149. | STYLESHEET |
  150. | +----------------+ |
  151. | | | |
  152. | +----------------+ |
  153. | |
  154. | </head> |
  155. | |
  156. | <body> |
  157. | |
  158. | HEADING |
  159. | +----------------+ |
  160. | | | |
  161. | +----------------+ |
  162. | |
  163. | REPORT |
  164. | +----------------+ |
  165. | | | |
  166. | +----------------+ |
  167. | |
  168. | ENDING |
  169. | +----------------+ |
  170. | | | |
  171. | +----------------+ |
  172. | |
  173. | </body> |
  174. |</html> |
  175. +------------------------+
  176. """
  177.  
  178. STATUS = {
  179. 0: u'通过',
  180. 1: u'失败',
  181. 2: u'错误',
  182. }
  183.  
  184. DEFAULT_TITLE = 'Unit Test Report'
  185. DEFAULT_DESCRIPTION = ''
  186.  
  187. # ------------------------------------------------------------------------
  188. # HTML Template
  189.  
  190. HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
  191. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  192. <html xmlns="http://www.w3.org/1999/xhtml">
  193. <head>
  194. <title>%(title)s</title>
  195. <meta name="generator" content="%(generator)s"/>
  196. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  197.  
  198. <link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
  199. <script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script>
  200. <!-- <script type="text/javascript" src="js/echarts.common.min.js"></script> -->
  201.  
  202. %(stylesheet)s
  203.  
  204. </head>
  205. <body>
  206. <script language="javascript" type="text/javascript"><!--
  207. output_list = Array();
  208.  
  209. /* level - 0:Summary; 1:Failed; 2:All */
  210. function showCase(level) {
  211. trs = document.getElementsByTagName("tr");
  212. for (var i = 0; i < trs.length; i++) {
  213. tr = trs[i];
  214. id = tr.id;
  215. if (id.substr(0,2) == 'ft') {
  216. if (level < 1) {
  217. tr.className = 'hiddenRow';
  218. }
  219. else {
  220. tr.className = '';
  221. }
  222. }
  223. if (id.substr(0,2) == 'pt') {
  224. if (level > 1) {
  225. tr.className = '';
  226. }
  227. else {
  228. tr.className = 'hiddenRow';
  229. }
  230. }
  231. }
  232. }
  233.  
  234. function showClassDetail(cid, count) {
  235. var id_list = Array(count);
  236. var toHide = 1;
  237. for (var i = 0; i < count; i++) {
  238. tid0 = 't' + cid.substr(1) + '.' + (i+1);
  239. tid = 'f' + tid0;
  240. tr = document.getElementById(tid);
  241. if (!tr) {
  242. tid = 'p' + tid0;
  243. tr = document.getElementById(tid);
  244. }
  245. id_list[i] = tid;
  246. if (tr.className) {
  247. toHide = 0;
  248. }
  249. }
  250. for (var i = 0; i < count; i++) {
  251. tid = id_list[i];
  252. if (toHide) {
  253. document.getElementById('div_'+tid).style.display = 'none'
  254. document.getElementById(tid).className = 'hiddenRow';
  255. }
  256. else {
  257. document.getElementById(tid).className = '';
  258. }
  259. }
  260. }
  261.  
  262. function showTestDetail(div_id){
  263. var details_div = document.getElementById(div_id)
  264. var displayState = details_div.style.display
  265. // alert(displayState)
  266. if (displayState != 'block' ) {
  267. displayState = 'block'
  268. details_div.style.display = 'block'
  269. }
  270. else {
  271. details_div.style.display = 'none'
  272. }
  273. }
  274.  
  275. function html_escape(s) {
  276. s = s.replace(/&/g,'&amp;');
  277. s = s.replace(/</g,'&lt;');
  278. s = s.replace(/>/g,'&gt;');
  279. return s;
  280. }
  281.  
  282. /* obsoleted by detail in <div>
  283. function showOutput(id, name) {
  284. var w = window.open("", //url
  285. name,
  286. "resizable,scrollbars,status,width=800,height=450");
  287. d = w.document;
  288. d.write("<pre>");
  289. d.write(html_escape(output_list[id]));
  290. d.write("\n");
  291. d.write("<a href='javascript:window.close()'>close</a>\n");
  292. d.write("</pre>\n");
  293. d.close();
  294. }
  295. */
  296. --></script>
  297.  
  298. <div id="div_base">
  299. %(heading)s
  300. %(report)s
  301. %(ending)s
  302. %(chart_script)s
  303. </div>
  304. </body>
  305. </html>
  306. """ # variables: (title, generator, stylesheet, heading, report, ending, chart_script)
  307.  
  308. ECHARTS_SCRIPT = """
  309. <script type="text/javascript">
  310. // 基于准备好的dom,初始化echarts实例
  311. var myChart = echarts.init(document.getElementById('chart'));
  312.  
  313. // 指定图表的配置项和数据
  314. var option = {
  315. title : {
  316. text: '测试执行情况',
  317. x:'center'
  318. },
  319. tooltip : {
  320. trigger: 'item',
  321. formatter: "{a} <br/>{b} : {c} ({d}%%)"
  322. },
  323. color: ['#95b75d', 'grey', '#b64645'],
  324. legend: {
  325. orient: 'vertical',
  326. left: 'left',
  327. data: ['通过','失败','错误']
  328. },
  329. series : [
  330. {
  331. name: '测试执行情况',
  332. type: 'pie',
  333. radius : '60%%',
  334. center: ['50%%', '60%%'],
  335. data:[
  336. {value:%(Pass)s, name:'通过'},
  337. {value:%(fail)s, name:'失败'},
  338. {value:%(error)s, name:'错误'}
  339. ],
  340. itemStyle: {
  341. emphasis: {
  342. shadowBlur: 10,
  343. shadowOffsetX: 0,
  344. shadowColor: 'rgba(0, 0, 0, 0.5)'
  345. }
  346. }
  347. }
  348. ]
  349. };
  350.  
  351. // 使用刚指定的配置项和数据显示图表。
  352. myChart.setOption(option);
  353. </script>
  354. """ # variables: (Pass, fail, error)
  355.  
  356. # ------------------------------------------------------------------------
  357. # Stylesheet
  358. #
  359. # alternatively use a <link> for external style sheet, e.g.
  360. # <link rel="stylesheet" href="$url" type="text/css">
  361.  
  362. STYLESHEET_TMPL = """
  363. <style type="text/css" media="screen">
  364. body { font-family: Microsoft YaHei,Consolas,arial,sans-serif; font-size: 80%; }
  365. table { font-size: 100%; }
  366. pre { white-space: pre-wrap;word-wrap: break-word; }
  367.  
  368. /* -- heading ---------------------------------------------------------------------- */
  369. h1 {
  370. font-size: 16pt;
  371. color: gray;
  372. }
  373. .heading {
  374. margin-top: 0ex;
  375. margin-bottom: 1ex;
  376. }
  377.  
  378. .heading .attribute {
  379. margin-top: 1ex;
  380. margin-bottom: 0;
  381. }
  382.  
  383. .heading .description {
  384. margin-top: 2ex;
  385. margin-bottom: 3ex;
  386. }
  387.  
  388. /* -- css div popup ------------------------------------------------------------------------ */
  389. a.popup_link {
  390. }
  391.  
  392. a.popup_link:hover {
  393. color: red;
  394. }
  395.  
  396. .popup_window {
  397. display: none;
  398. position: relative;
  399. left: 0px;
  400. top: 0px;
  401. /*border: solid #627173 1px; */
  402. padding: 10px;
  403. /*background-color: #E6E6D6; */
  404. font-family: "Lucida Console", "Courier New", Courier, monospace;
  405. text-align: left;
  406. font-size: 8pt;
  407. /* width: 500px;*/
  408. }
  409.  
  410. }
  411. /* -- report ------------------------------------------------------------------------ */
  412. #show_detail_line {
  413. margin-top: 3ex;
  414. margin-bottom: 1ex;
  415. }
  416. #result_table {
  417. width: 99%;
  418. }
  419. #header_row {
  420. font-weight: bold;
  421. color: #303641;
  422. background-color: #ebebeb;
  423. }
  424. #total_row { font-weight: bold; }
  425. .passClass { background-color: #bdedbc; }
  426. .failClass { background-color: #ffefa4; }
  427. .errorClass { background-color: #ffc9c9; }
  428. .passCase { color: #6c6; }
  429. .failCase { color: #FF6600; font-weight: bold; }
  430. .errorCase { color: #c00; font-weight: bold; }
  431. .hiddenRow { display: none; }
  432. .testcase { margin-left: 2em; }
  433.  
  434. /* -- ending ---------------------------------------------------------------------- */
  435. #ending {
  436. }
  437.  
  438. #div_base {
  439. position:absolute;
  440. top:0%;
  441. left:5%;
  442. right:5%;
  443. width: auto;
  444. height: auto;
  445. margin: -15px 0 0 0;
  446. }
  447. </style>
  448. """
  449.  
  450. # ------------------------------------------------------------------------
  451. # Heading
  452. #
  453.  
  454. HEADING_TMPL = """
  455. <div class='page-header'>
  456. <h1>%(title)s</h1>
  457. %(parameters)s
  458. </div>
  459. <div style="float: left;width:50%%;"><p class='description'>%(description)s</p></div>
  460. <div id="chart" style="width:50%%;height:400px;float:left;"></div>
  461. """ # variables: (title, parameters, description)
  462.  
  463. HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
  464. """ # variables: (name, value)
  465.  
  466. # ------------------------------------------------------------------------
  467. # Report
  468. #
  469.  
  470. REPORT_TMPL = u"""
  471. <div class="btn-group btn-group-sm">
  472. <button class="btn btn-default" onclick='javascript:showCase(0)'>总结</button>
  473. <button class="btn btn-default" onclick='javascript:showCase(1)'>失败</button>
  474. <button class="btn btn-default" onclick='javascript:showCase(2)'>全部</button>
  475. </div>
  476. <p></p>
  477. <table id='result_table' class="table table-bordered">
  478. <colgroup>
  479. <col align='left' />
  480. <col align='right' />
  481. <col align='right' />
  482. <col align='right' />
  483. <col align='right' />
  484. <col align='right' />
  485. </colgroup>
  486. <tr id='header_row'>
  487. <td>测试套件/测试用例</td>
  488. <td>总数</td>
  489. <td>通过</td>
  490. <td>失败</td>
  491. <td>错误</td>
  492. <td>查看</td>
  493. </tr>
  494. %(test_list)s
  495. <tr id='total_row'>
  496. <td>总计</td>
  497. <td>%(count)s</td>
  498. <td>%(Pass)s</td>
  499. <td>%(fail)s</td>
  500. <td>%(error)s</td>
  501. <td>&nbsp;</td>
  502. </tr>
  503. </table>
  504. """ # variables: (test_list, count, Pass, fail, error)
  505.  
  506. REPORT_CLASS_TMPL = u"""
  507. <tr class='%(style)s'>
  508. <td>%(desc)s</td>
  509. <td>%(count)s</td>
  510. <td>%(Pass)s</td>
  511. <td>%(fail)s</td>
  512. <td>%(error)s</td>
  513. <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">详情</a></td>
  514. </tr>
  515. """ # variables: (style, desc, count, Pass, fail, error, cid)
  516.  
  517. REPORT_TEST_WITH_OUTPUT_TMPL = r"""
  518. <tr id='%(tid)s' class='%(Class)s'>
  519. <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
  520. <td colspan='5' align='center'>
  521.  
  522. <!--css div popup start-->
  523. <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
  524. %(status)s</a>
  525.  
  526. <div id='div_%(tid)s' class="popup_window">
  527. <pre>%(script)s</pre>
  528. </div>
  529. <!--css div popup end-->
  530.  
  531. </td>
  532. </tr>
  533. """ # variables: (tid, Class, style, desc, status)
  534.  
  535. REPORT_TEST_NO_OUTPUT_TMPL = r"""
  536. <tr id='%(tid)s' class='%(Class)s'>
  537. <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
  538. <td colspan='5' align='center'>%(status)s</td>
  539. </tr>
  540. """ # variables: (tid, Class, style, desc, status)
  541.  
  542. REPORT_TEST_OUTPUT_TMPL = r"""%(id)s: %(output)s""" # variables: (id, output)
  543.  
  544. # ------------------------------------------------------------------------
  545. # ENDING
  546. #
  547.  
  548. ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""
  549.  
  550. # -------------------- The end of the Template class -------------------
  551.  
  552. TestResult = unittest.TestResult
  553.  
  554. class _TestResult(TestResult):
  555. # note: _TestResult is a pure representation of results.
  556. # It lacks the output and reporting ability compares to unittest._TextTestResult.
  557.  
  558. def __init__(self, verbosity=1):
  559. TestResult.__init__(self)
  560. self.stdout0 = None
  561. self.stderr0 = None
  562. self.success_count = 0
  563. self.failure_count = 0
  564. self.error_count = 0
  565. self.verbosity = verbosity
  566.  
  567. # result is a list of result in 4 tuple
  568. # (
  569. # result code (0: success; 1: fail; 2: error),
  570. # TestCase object,
  571. # Test output (byte string),
  572. # stack trace,
  573. # )
  574. self.result = []
  575. self.subtestlist = []
  576.  
  577. def startTest(self, test):
  578. TestResult.startTest(self, test)
  579. # just one buffer for both stdout and stderr
  580. self.outputBuffer = io.StringIO()
  581. stdout_redirector.fp = self.outputBuffer
  582. stderr_redirector.fp = self.outputBuffer
  583. self.stdout0 = sys.stdout
  584. self.stderr0 = sys.stderr
  585. sys.stdout = stdout_redirector
  586. sys.stderr = stderr_redirector
  587.  
  588. def complete_output(self):
  589. """
  590. Disconnect output redirection and return buffer.
  591. Safe to call multiple times.
  592. """
  593. if self.stdout0:
  594. sys.stdout = self.stdout0
  595. sys.stderr = self.stderr0
  596. self.stdout0 = None
  597. self.stderr0 = None
  598. return self.outputBuffer.getvalue()
  599.  
  600. def stopTest(self, test):
  601. # Usually one of addSuccess, addError or addFailure would have been called.
  602. # But there are some path in unittest that would bypass this.
  603. # We must disconnect stdout in stopTest(), which is guaranteed to be called.
  604. self.complete_output()
  605.  
  606. def addSuccess(self, test):
  607. if test not in self.subtestlist:
  608. self.success_count += 1
  609. TestResult.addSuccess(self, test)
  610. output = self.complete_output()
  611. self.result.append((0, test, output, ''))
  612. if self.verbosity > 1:
  613. sys.stderr.write('ok ')
  614. sys.stderr.write(str(test))
  615. sys.stderr.write('\n')
  616. else:
  617. sys.stderr.write('.')
  618.  
  619. def addError(self, test, err):
  620. self.error_count += 1
  621. TestResult.addError(self, test, err)
  622. _, _exc_str = self.errors[-1]
  623. output = self.complete_output()
  624. self.result.append((2, test, output, _exc_str))
  625. if self.verbosity > 1:
  626. sys.stderr.write('E ')
  627. sys.stderr.write(str(test))
  628. sys.stderr.write('\n')
  629. else:
  630. sys.stderr.write('E')
  631.  
  632. def addFailure(self, test, err):
  633. self.failure_count += 1
  634. TestResult.addFailure(self, test, err)
  635. _, _exc_str = self.failures[-1]
  636. output = self.complete_output()
  637. self.result.append((1, test, output, _exc_str))
  638. if self.verbosity > 1:
  639. sys.stderr.write('F ')
  640. sys.stderr.write(str(test))
  641. sys.stderr.write('\n')
  642. else:
  643. sys.stderr.write('F')
  644.  
  645. def addSubTest(self, test, subtest, err):
  646. if err is not None:
  647. if getattr(self, 'failfast', False):
  648. self.stop()
  649. if issubclass(err[0], test.failureException):
  650. self.failure_count += 1
  651. errors = self.failures
  652. errors.append((subtest, self._exc_info_to_string(err, subtest)))
  653. output = self.complete_output()
  654. self.result.append((1, test, output + '\nSubTestCase Failed:\n' + str(subtest),
  655. self._exc_info_to_string(err, subtest)))
  656. if self.verbosity > 1:
  657. sys.stderr.write('F ')
  658. sys.stderr.write(str(subtest))
  659. sys.stderr.write('\n')
  660. else:
  661. sys.stderr.write('F')
  662. else:
  663. self.error_count += 1
  664. errors = self.errors
  665. errors.append((subtest, self._exc_info_to_string(err, subtest)))
  666. output = self.complete_output()
  667. self.result.append(
  668. (2, test, output + '\nSubTestCase Error:\n' + str(subtest), self._exc_info_to_string(err, subtest)))
  669. if self.verbosity > 1:
  670. sys.stderr.write('E ')
  671. sys.stderr.write(str(subtest))
  672. sys.stderr.write('\n')
  673. else:
  674. sys.stderr.write('E')
  675. self._mirrorOutput = True
  676. else:
  677. self.subtestlist.append(subtest)
  678. self.subtestlist.append(test)
  679. self.success_count += 1
  680. output = self.complete_output()
  681. self.result.append((0, test, output + '\nSubTestCase Pass:\n' + str(subtest), ''))
  682. if self.verbosity > 1:
  683. sys.stderr.write('ok ')
  684. sys.stderr.write(str(subtest))
  685. sys.stderr.write('\n')
  686. else:
  687. sys.stderr.write('.')
  688.  
  689. class HTMLTestRunner(Template_mixin):
  690.  
  691. def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
  692. self.stream = stream
  693. self.verbosity = verbosity
  694. if title is None:
  695. self.title = self.DEFAULT_TITLE
  696. else:
  697. self.title = title
  698. if description is None:
  699. self.description = self.DEFAULT_DESCRIPTION
  700. else:
  701. self.description = description
  702.  
  703. self.startTime = datetime.datetime.now()
  704.  
  705. def run(self, test):
  706. "Run the given test case or test suite."
  707. result = _TestResult(self.verbosity)
  708. test(result)
  709. self.stopTime = datetime.datetime.now()
  710. self.generateReport(test, result)
  711. print('\nTime Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)
  712. return result
  713.  
  714. def sortResult(self, result_list):
  715. # unittest does not seems to run in any particular order.
  716. # Here at least we want to group them together by class.
  717. rmap = {}
  718. classes = []
  719. for n,t,o,e in result_list:
  720. cls = t.__class__
  721. if cls not in rmap:
  722. rmap[cls] = []
  723. classes.append(cls)
  724. rmap[cls].append((n,t,o,e))
  725. r = [(cls, rmap[cls]) for cls in classes]
  726. return r
  727.  
  728. def getReportAttributes(self, result):
  729. """
  730. Return report attributes as a list of (name, value).
  731. Override this to add custom attributes.
  732. """
  733. startTime = str(self.startTime)[:19]
  734. duration = str(self.stopTime - self.startTime)
  735. status = []
  736. if result.success_count: status.append(u'通过 %s' % result.success_count)
  737. if result.failure_count: status.append(u'失败 %s' % result.failure_count)
  738. if result.error_count: status.append(u'错误 %s' % result.error_count )
  739. if status:
  740. status = ' '.join(status)
  741. else:
  742. status = 'none'
  743. return [
  744. (u'开始时间', startTime),
  745. (u'运行时长', duration),
  746. (u'状态', status),
  747. ]
  748.  
  749. def generateReport(self, test, result):
  750. report_attrs = self.getReportAttributes(result)
  751. generator = 'HTMLTestRunner %s' % __version__
  752. stylesheet = self._generate_stylesheet()
  753. heading = self._generate_heading(report_attrs)
  754. report = self._generate_report(result)
  755. ending = self._generate_ending()
  756. chart = self._generate_chart(result)
  757. output = self.HTML_TMPL % dict(
  758. title = saxutils.escape(self.title),
  759. generator = generator,
  760. stylesheet = stylesheet,
  761. heading = heading,
  762. report = report,
  763. ending = ending,
  764. chart_script = chart
  765. )
  766. self.stream.write(output.encode('utf8'))
  767. self.stream.write("gg".encode('utf8'))
  768.  
  769. def _generate_stylesheet(self):
  770. return self.STYLESHEET_TMPL
  771.  
  772. def _generate_heading(self, report_attrs):
  773. a_lines = []
  774. for name, value in report_attrs:
  775. line = self.HEADING_ATTRIBUTE_TMPL % dict(
  776. name = saxutils.escape(name),
  777. value = saxutils.escape(value),
  778. )
  779. a_lines.append(line)
  780. heading = self.HEADING_TMPL % dict(
  781. title = saxutils.escape(self.title),
  782. parameters = ''.join(a_lines),
  783. description = saxutils.escape(self.description),
  784. )
  785. return heading
  786.  
  787. def _generate_report(self, result):
  788. rows = []
  789. sortedResult = self.sortResult(result.result)
  790. for cid, (cls, cls_results) in enumerate(sortedResult):
  791. # subtotal for a class
  792. np = nf = ne = 0
  793. for n,t,o,e in cls_results:
  794. if n == 0: np += 1
  795. elif n == 1: nf += 1
  796. else: ne += 1
  797.  
  798. # format class description
  799. if cls.__module__ == "__main__":
  800. name = cls.__name__
  801. else:
  802. name = "%s.%s" % (cls.__module__, cls.__name__)
  803. doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
  804. desc = doc and '%s: %s' % (name, doc) or name
  805.  
  806. row = self.REPORT_CLASS_TMPL % dict(
  807. style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
  808. desc = desc,
  809. count = np+nf+ne,
  810. Pass = np,
  811. fail = nf,
  812. error = ne,
  813. cid = 'c%s' % (cid+1),
  814. )
  815. rows.append(row)
  816.  
  817. for tid, (n,t,o,e) in enumerate(cls_results):
  818. self._generate_report_test(rows, cid, tid, n, t, o, e)
  819.  
  820. report = self.REPORT_TMPL % dict(
  821. test_list = ''.join(rows),
  822. count = str(result.success_count+result.failure_count+result.error_count),
  823. Pass = str(result.success_count),
  824. fail = str(result.failure_count),
  825. error = str(result.error_count),
  826. )
  827. return report
  828.  
  829. def _generate_chart(self, result):
  830. chart = self.ECHARTS_SCRIPT % dict(
  831. Pass=str(result.success_count),
  832. fail=str(result.failure_count),
  833. error=str(result.error_count),
  834. )
  835. return chart
  836.  
  837. def _generate_report_test(self, rows, cid, tid, n, t, o, e):
  838. # e.g. 'pt1.1', 'ft1.1', etc
  839. has_output = bool(o or e)
  840. tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
  841. name = t.id().split('.')[-1]
  842. doc = t.shortDescription() or ""
  843. desc = doc and ('%s: %s' % (name, doc)) or name
  844. tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
  845.  
  846. script = self.REPORT_TEST_OUTPUT_TMPL % dict(
  847. id=tid,
  848. output=saxutils.escape(o+e),
  849. )
  850.  
  851. row = tmpl % dict(
  852. tid=tid,
  853. Class=(n == 0 and 'hiddenRow' or 'none'),
  854. style=(n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none')),
  855. desc=desc,
  856. script=script,
  857. status=self.STATUS[n],
  858. )
  859. rows.append(row)
  860. if not has_output:
  861. return
  862.  
  863. def _generate_ending(self):
  864. return self.ENDING_TMPL
  865.  
  866. ##############################################################################
  867. # Facilities for running tests from the command line
  868. ##############################################################################
  869.  
  870. # Note: Reuse unittest.TestProgram to launch test. In the future we may
  871. # build our own launcher to support more specific command line
  872. # parameters like test title, CSS, etc.
  873. class TestProgram(unittest.TestProgram):
  874. """
  875. A variation of the unittest.TestProgram. Please refer to the base
  876. class for command line parameters.
  877. """
  878. def runTests(self):
  879. # Pick HTMLTestRunner as the default test runner.
  880. # base class's testRunner parameter is not useful because it means
  881. # we have to instantiate HTMLTestRunner before we know self.verbosity.
  882. if self.testRunner is None:
  883. self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
  884. unittest.TestProgram.runTests(self)
  885.  
  886. main = TestProgram
  887.  
  888. ##############################################################################
  889. # Executing this module from the command line
  890. ##############################################################################
  891.  
  892. if __name__ == "__main__":
  893. main(module=None)

代码如下:

  1. import unittest
  2. import HTMLTestRunner
  3.  
  4. class Test3(unittest.TestCase):
  5. @classmethod
  6. def setUpClass(self):
  7. print("execute setUpClass")
  8.  
  9. @classmethod
  10. def tearDownClass(self):
  11. print("execute tearDownClass")
  12.  
  13. def setUp(self):
  14. print("execute setUp")
  15.  
  16. def tearDown(self):
  17. print("execute tearDown")
  18.  
  19. def test_one(self):
  20. print('execute test_one')
  21. self.assertTrue('FOO'.isupper())
  22.  
  23. def test_two(self):
  24. print('execute test_two')
  25.  
  26. if __name__ == '__main__':
  27. suite = unittest.TestSuite()
  28. # Test3是要测试的类名,test_one是要执行的测试方法
  29. suite.addTest(Test3("test_one"))
  30. suite.addTest(Test3("test_two"))
  31. # 实践中发现执行时的当前路径,不一定是此文件所在的文件夹,所以使用绝对路径
  32. # print(f"{os.getcwd()}")
  33. filename = 'F:\\PycharmProjects\\test3\\apptestresult.html'
  34. fb = open(filename, 'wb')
  35. runner = HTMLTestRunner.HTMLTestRunner(stream=fb, title="测试HTMLTestRunner", description="测试HTMLTestRunner")
  36. runner.run(suite)
  37. fb.close()

执行如下:

报告如下:

四、HTMLTestRunner未生成报告问题处理

4.1 pytest干扰

如果python环境中安装了pytest,那么默认运行时会是Run 'py.test for'(如下图所示),这时if __name__ == "__main__"下的语句是不被执行的所以报告肯定不会生成(原理未知)。

些时需要依次点开pycharm菜单----Run----Edit Configurations...自行添加一个运行配置,如下图

4.2 路径问题

一是注意自己把报告输出到了哪个位置,特别是如果有几个地方都会输出报告时不要弄混了,搞得自己在浏览器中刷新半天没看到有变化,认为没生成报告。

二是实践中通过os.getcwd()发现python运行时的目录不一定是当前目录,所以如果报告使用相对路径然后又没看到有报告生成,那就注意看os.getcwd()返回的运行路径是不是当前文件夹。

参考:

https://docs.python.org/3/library/unittest.html

https://www.cnblogs.com/youreyebows/p/7867508.html

Python3+unittest使用教程的更多相关文章

  1. 18年selenium3+python3+unittest自动化测试教程(下)

    第六章 自动化测试进阶实战篇幅 1.自动化测试实战进阶之网页单选性别资料实战 简介:讲解使用selenium修改input输入框和单选框 2.自动化测试之页面常见弹窗处理 简介:讲解使用seleniu ...

  2. 18年selenium3+python3+unittest自动化测试教程(上)

    第一章 自动化测试课程介绍和课程大纲 1.自动化测试课程介绍 简介:讲解什么是自动化测试和课程大纲讲解,课程需要的基础和学后的水平 python3.7+selenium3 pycharm 第二章自动化 ...

  3. Python3.x爬虫教程:爬网页、爬图片、自己主动登录

    林炳文Evankaka原创作品. 转载请注明出处http://blog.csdn.net/evankaka 摘要:本文将使用Python3.4爬网页.爬图片.自己主动登录.并对HTTP协议做了一个简单 ...

  4. python3+unittest+HTMLTestRunner

    参考博客1 参考博客2 python3版HTMLTestRunner.py见博客园‘链接’(已经上传到博客园) import unittest class operatinon_unittest(un ...

  5. python3 unittest框架失败重跑加截图支持python2,python3

    github源码地址下载:https://github.com/GoverSky/HTMLTestRunner_cn.git 解压文件后取出/HTMLTestRunner_cn.py文件丢进C:\Py ...

  6. [教程]Tensorflow + win10 + CPU + Python3.6+ 安装教程

    由于各种原因,清华镜像源已经彻底挂掉了,但是目前网上的各种教程基本上都是采取设置清华镜像源来加快下载速度,所以这给小白带来了很大的困扰!这里我将通过合理上网工具来直接下载源镜像. 注意:本次教程适用于 ...

  7. python3+unittest参考

    Python3+Selenium+unittest自动化UI测试框架:https://www.cnblogs.com/G2Bent/p/8376001.html unittest --- 单元测试框架 ...

  8. Python Unittest简明教程

    1 概述 单元测试框架是一种软件测试方法,通过来测试源代码中的各个单元,例如类,方法等,以确定它们是否符合要求.直观上来说,可以将单元视为最小的可测试部分.单元测试是程序员在开发过程中创建的短代码片段 ...

  9. python3 字符串str 教程

    字符串可以用单引号或双引号来创建. Python 不支持单字符类型,单字符也在Python也是作为一个字符串使用. 例: var1 = 'Hello World!' var2 = "Pyth ...

随机推荐

  1. 2018-2019-2 20165215《网络对抗技术》Exp4 恶意代码分析

    目录 实践目标 实践内容 基础问题回答 实验步骤 使用schtasks指令监控系统 使用sysmon工具监控系统 使用VirusTotal分析恶意软件 使用PEiD进行外壳检测 使用PE explor ...

  2. Git 爬坑路(从小白开始入门) ——(1)

    通过git管理项目之前,需要先注册一个GitHub账号,方便在远程仓库进行项目管理. Git之项目在本地仓库的管理(从小白开始): 一.push到远程项目 1.在个人的GitHub账号中,创建一个远程 ...

  3. SQL查询数据时报错

    在开发过程中如果查询报如下的错误: org.springframework.jdbc.UncategorizedSQLException: Error attempting to get column ...

  4. 【js】了解前端缓存,收获不止于此!

    了解前端缓存,收获不止于此! 这次我们来讲一下关于前端缓存的问题.感谢赵欢同学提供doc素材. 首先,开局我画了一张图,你会对文章有一个大局了解. 今天讲的是前端缓存. 前端缓存有3大种:如图,分为H ...

  5. UML类图新手入门级介绍(转)

    首先,看动物矩形框,它代表一个类(Class).类图分三层,第一层显示类的名称,如果是抽象类,则就用斜体显示.第二层是类的特性,通常就是字段和属性.第三层是类的操作,通常是方法或行为.前面的符号,+ ...

  6. msgid 属性

    Android源码中的String.xml文件,msgid这个属性是干嘛的? 全局资源,方便引用.比如在布局的text和activity中用到.

  7. 2018年-2019年第二学期第七周C#学习个人总结

    在本周我又学习了,第五章面向对象高级中的5.5异常和5.6命名空间和程序集.在5.5异常中我知道了异常层次结构:所有的异常类都继承自Exception类.由于发生了异常程序立即终止无法再继续向下执行. ...

  8. 2019充值新骗局手游折扣App靠谱程度一览表

    随着互联网的快速发展,游戏产业也迎来了盛开的春天.特别是进入网络游戏时代后,来自世界各地的朋友,甚至来自地球村的朋友一起玩游戏.在这个阶段,游戏制作者还专注于设计副本,活动,皮肤和充值.无论你是否关心 ...

  9. Eclipse/MyEclipse导入导出注释模板

    1.Eclipse/MyEclipse导入注释模板 (1)打开Eclipse/MyEclipse工具,打开或创建一个Java工程,点击菜单Window->Preferences弹出首选项设置窗口 ...

  10. 【新特性】JDK1.9

    一.目录结构 JDK9具体目录结构如下所示: bin: 该目录包含所有的命令. conf: 包含用户可以编辑的配置文件,例如以前位于jre\lib 目录中的.properties 和 .policy ...