• Python3之HTMLTestRunner测试报告美化


      前面我们讲到过在做自动化测试或单元测试的时候使用HTMLTestRunner来生成测试报告,并且由于Python2 和 Python3 对于HTMLTestRunner的支持稍微有点差异,所以我们将HTMLTestRunner进行了改造,从而适配Python3,详细改造步骤可以参考:HTMLTestRunner修改成Python3版本

      但是改造后的HTMLTestRunner生成的测试报告不是特别的美观,所以我又对HTMLTestRunner进行了进一步的改造,主要是做一些美化。

      美化之前的测试报告如下:

       美化之后的测试报告如下:

       从上面两个报告的对比来看,第二个测试报告是不是更为美观呢?

    下面是我改造后的HTMLTestRunner源码:

      1 # -*- coding: utf-8 -*-
      2 
      3 """
      4 A TestRunner for use with the Python unit testing framework. It
      5 generates a HTML report to show the result at a glance.
      6 
      7 The simplest way to use this is to invoke its main method. E.g.
      8 
      9     import unittest
     10     import HTMLTestRunner
     11 
     12     ... define your tests ...
     13 
     14     if __name__ == '__main__':
     15         HTMLTestRunner.main()
     16 
     17 
     18 For more customization options, instantiates a HTMLTestRunner object.
     19 HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
     20 
     21     # output to a file
     22     fp = file('my_report.html', 'wb')
     23     runner = HTMLTestRunner.HTMLTestRunner(
     24                 stream=fp,
     25                 title='My unit test',
     26                 description='This demonstrates the report output by HTMLTestRunner.'
     27                 )
     28 
     29     # Use an external stylesheet.
     30     # See the Template_mixin class for more customizable options
     31     runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
     32 
     33     # run the test
     34     runner.run(my_test_suite)
     35 
     36 
     37 ------------------------------------------------------------------------
     38 Copyright (c) 2004-2007, Wai Yip Tung
     39 All rights reserved.
     40 
     41 Redistribution and use in source and binary forms, with or without
     42 modification, are permitted provided that the following conditions are
     43 met:
     44 
     45 * Redistributions of source code must retain the above copyright notice,
     46   this list of conditions and the following disclaimer.
     47 * Redistributions in binary form must reproduce the above copyright
     48   notice, this list of conditions and the following disclaimer in the
     49   documentation and/or other materials provided with the distribution.
     50 * Neither the name Wai Yip Tung nor the names of its contributors may be
     51   used to endorse or promote products derived from this software without
     52   specific prior written permission.
     53 
     54 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
     55 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     56 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
     57 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
     58 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     59 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     60 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     61 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     62 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     63 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     64 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     65 """
     66 
     67 # URL: http://tungwaiyip.info/software/HTMLTestRunner.html
     68 
     69 __author__ = "Wai Yip Tung"
     70 __version__ = "0.8.2.3"
     71 
     72 
     73 """
     74 Change History
     75 Version 0.8.2.1 -Findyou
     76 * 改为支持python3
     77 
     78 Version 0.8.2.1 -Findyou
     79 * 支持中文,汉化
     80 * 调整样式,美化(需要连入网络,使用的百度的Bootstrap.js)
     81 * 增加 通过分类显示、测试人员、通过率的展示
     82 * 优化“详细”与“收起”状态的变换
     83 * 增加返回顶部的锚点
     84 
     85 Version 0.8.2
     86 * Show output inline instead of popup window (Viorel Lupu).
     87 
     88 Version in 0.8.1
     89 * Validated XHTML (Wolfgang Borgert).
     90 * Added description of test classes and test cases.
     91 
     92 Version in 0.8.0
     93 * Define Template_mixin class for customization.
     94 * Workaround a IE 6 bug that it does not treat <script> block as CDATA.
     95 
     96 Version in 0.7.1
     97 * Back port to Python 2.3 (Frank Horowitz).
     98 * Fix missing scroll bars in detail log (Podi).
     99 """
    100 
    101 # TODO: color stderr
    102 # TODO: simplify javascript using ,ore than 1 class in the class attribute?
    103 
    104 import datetime
    105 import io
    106 import sys
    107 import time
    108 import unittest
    109 from xml.sax import saxutils
    110 import sys
    111 
    112 # ------------------------------------------------------------------------
    113 # The redirectors below are used to capture output during testing. Output
    114 # sent to sys.stdout and sys.stderr are automatically captured. However
    115 # in some cases sys.stdout is already cached before HTMLTestRunner is
    116 # invoked (e.g. calling logging.basicConfig). In order to capture those
    117 # output, use the redirectors for the cached stream.
    118 #
    119 # e.g.
    120 #   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
    121 #   >>>
    122 
    123 class OutputRedirector(object):
    124     """ Wrapper to redirect stdout or stderr """
    125     def __init__(self, fp):
    126         self.fp = fp
    127 
    128     def write(self, s):
    129         self.fp.write(s)
    130 
    131     def writelines(self, lines):
    132         self.fp.writelines(lines)
    133 
    134     def flush(self):
    135         self.fp.flush()
    136 
    137 stdout_redirector = OutputRedirector(sys.stdout)
    138 stderr_redirector = OutputRedirector(sys.stderr)
    139 
    140 # ----------------------------------------------------------------------
    141 # Template
    142 
    143 class Template_mixin(object):
    144     """
    145     Define a HTML template for report customerization and generation.
    146 
    147     Overall structure of an HTML report
    148 
    149     HTML
    150     +------------------------+
    151     |<html>                  |
    152     |  <head>                |
    153     |                        |
    154     |   STYLESHEET           |
    155     |   +----------------+   |
    156     |   |                |   |
    157     |   +----------------+   |
    158     |                        |
    159     |  </head>               |
    160     |                        |
    161     |  <body>                |
    162     |                        |
    163     |   HEADING              |
    164     |   +----------------+   |
    165     |   |                |   |
    166     |   +----------------+   |
    167     |                        |
    168     |   REPORT               |
    169     |   +----------------+   |
    170     |   |                |   |
    171     |   +----------------+   |
    172     |                        |
    173     |   ENDING               |
    174     |   +----------------+   |
    175     |   |                |   |
    176     |   +----------------+   |
    177     |                        |
    178     |  </body>               |
    179     |</html>                 |
    180     +------------------------+
    181     """
    182 
    183     STATUS = {
    184     0: '通过',
    185     1: '失败',
    186     2: '错误',
    187     }
    188     # 默认测试标题
    189     DEFAULT_TITLE = 'API自动化测试报告'
    190     DEFAULT_DESCRIPTION = ''
    191     # 默认测试人员
    192     DEFAULT_TESTER = 'lwjnicole'
    193 
    194     # ------------------------------------------------------------------------
    195     # HTML Template
    196 
    197     HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
    198 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    199 <html xmlns="http://www.w3.org/1999/xhtml">
    200 <head>
    201     <title>%(title)s</title>
    202     <meta name="generator" content="%(generator)s"/>
    203     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    204     <link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
    205     <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    206     <script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
    207     %(stylesheet)s
    208 </head>
    209 <body >
    210 <script language="javascript" type="text/javascript">
    211 output_list = Array();
    212 
    213 /*level 调整增加只显示通过用例的分类 --Adil
    214 0:Summary //all hiddenRow
    215 1:Failed  //pt hiddenRow, ft none
    216 2:Pass    //pt none, ft hiddenRow
    217 3:Error   // pt hiddenRow, ft none
    218 4:All     //pt none, ft none
    219 下面设置 按钮展开逻辑  --Yang Yao Jun
    220 */
    221 function showCase(level) {
    222     trs = document.getElementsByTagName("tr");
    223     for (var i = 0; i < trs.length; i++) {
    224         tr = trs[i];
    225         id = tr.id;
    226         if (id.substr(0,2) == 'ft') {
    227             if (level == 2 || level == 0 ) {
    228                 tr.className = 'hiddenRow';
    229             }
    230             else {
    231                 tr.className = '';
    232             }
    233         }
    234         if (id.substr(0,2) == 'pt') {
    235             if (level < 2 || level ==3 ) {
    236                 tr.className = 'hiddenRow';
    237             }
    238             else {
    239                 tr.className = '';
    240             }
    241         }
    242     }
    243 
    244     //加入【详细】切换文字变化 --Findyou
    245     detail_class=document.getElementsByClassName('detail');
    246     //console.log(detail_class.length)
    247     if (level == 3) {
    248         for (var i = 0; i < detail_class.length; i++){
    249             detail_class[i].innerHTML="收起"
    250         }
    251     }
    252     else{
    253             for (var i = 0; i < detail_class.length; i++){
    254             detail_class[i].innerHTML="详细"
    255         }
    256     }
    257 }
    258 
    259 function showClassDetail(cid, count) {
    260     var id_list = Array(count);
    261     var toHide = 1;
    262     for (var i = 0; i < count; i++) {
    263         //ID修改 点 为 下划线 -Findyou
    264         tid0 = 't' + cid.substr(1) + '_' + (i+1);
    265         tid = 'f' + tid0;
    266         tr = document.getElementById(tid);
    267         if (!tr) {
    268             tid = 'p' + tid0;
    269             tr = document.getElementById(tid);
    270         }
    271         id_list[i] = tid;
    272         if (tr.className) {
    273             toHide = 0;
    274         }
    275     }
    276     for (var i = 0; i < count; i++) {
    277         tid = id_list[i];
    278         //修改点击无法收起的BUG,加入【详细】切换文字变化 --Findyou
    279         if (toHide) {
    280             document.getElementById(tid).className = 'hiddenRow';
    281             document.getElementById(cid).innerText = "详细"
    282         }
    283         else {
    284             document.getElementById(tid).className = '';
    285             document.getElementById(cid).innerText = "收起"
    286         }
    287     }
    288 }
    289 
    290 function html_escape(s) {
    291     s = s.replace(/&/g,'&');
    292     s = s.replace(/</g,'<');
    293     s = s.replace(/>/g,'>');
    294     return s;
    295 }
    296 </script>
    297 %(heading)s
    298 %(report)s
    299 %(ending)s
    300 
    301 </body>
    302 </html>
    303 """
    304     # variables: (title, generator, stylesheet, heading, report, ending)
    305 
    306 
    307     # ------------------------------------------------------------------------
    308     # Stylesheet
    309     #
    310     # alternatively use a <link> for external style sheet, e.g.
    311     #   <link rel="stylesheet" href="$url" type="text/css">
    312 
    313     STYLESHEET_TMPL = """
    314 <style type="text/css" media="screen">
    315 body        { font-family: Microsoft YaHei,Tahoma,arial,helvetica,sans-serif;padding: 20px; font-size: 80%; }
    316 table       { font-size: 100%; }
    317 
    318 /* -- heading ---------------------------------------------------------------------- */
    319 .heading {
    320     margin-top: 0ex;
    321     margin-bottom: 1ex;
    322 }
    323 
    324 .heading .description {
    325     margin-top: 4ex;
    326     margin-bottom: 6ex;
    327 }
    328 
    329 /* -- report ------------------------------------------------------------------------ */
    330 #total_row  { font-weight: bold; }
    331 .passCase   { color: #5cb85c; }
    332 .failCase   { color: #d9534f; font-weight: bold; }
    333 .errorCase  { color: #f0ad4e; font-weight: bold; }
    334 .hiddenRow  { display: none; }
    335 .testcase   { margin-left: 2em; }
    336 </style>
    337 """
    338 
    339     # ------------------------------------------------------------------------
    340     # Heading
    341     #
    342 
    343     HEADING_TMPL = """<div class='heading'>
    344 <h1 style="font-family: Microsoft YaHei">%(title)s</h1>
    345 %(parameters)s
    346 <p class='description'>%(description)s</p>
    347 </div>
    348 
    349 """ # variables: (title, parameters, description)
    350 
    351     HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s : </strong> %(value)s</p>
    352 """ # variables: (name, value)
    353 
    354 
    355 
    356     # ------------------------------------------------------------------------
    357     # Report
    358     #
    359     # 汉化,加美化效果 --Yang Yao Jun
    360     #
    361     # 这里涉及到了 Bootstrap 前端技术,Bootstrap 按钮 资料介绍详见:http://www.runoob.com/bootstrap/bootstrap-buttons.html
    362     #
    363     REPORT_TMPL = """
    364     <p id='show_detail_line'>
    365     <a class="btn btn-primary" href='javascript:showCase(0)'>通过率 [%(passrate)s ]</a>
    366     <a class="btn btn-success" href='javascript:showCase(2)'>通过[ %(Pass)s ]</a>
    367     <a class="btn btn-warning" href='javascript:showCase(3)'>错误[ %(error)s ]</a>
    368     <a class="btn btn-danger" href='javascript:showCase(1)'>失败[ %(fail)s ]</a>
    369     <a class="btn btn-info" href='javascript:showCase(4)'>所有[ %(count)s ]</a>
    370     </p>
    371 <table id='result_table' class="table table-condensed table-bordered table-hover">
    372 <colgroup>
    373 <col align='left' />
    374 <col align='right' />
    375 <col align='right' />
    376 <col align='right' />
    377 <col align='right' />
    378 <col align='right' />
    379 </colgroup>
    380 <tr id='header_row' class="text-center success" style="font-weight: bold;font-size: 14px;">
    381     <td>用例集/测试用例</td>
    382     <td>总计</td>
    383     <td>通过</td>
    384     <td>错误</td>
    385     <td>失败</td>
    386     <td>详细</td>
    387 </tr>
    388 %(test_list)s
    389 <tr id='total_row' class="text-center active">
    390     <td>总计</td>
    391     <td>%(count)s</td>
    392     <td>%(Pass)s</td>
    393     <td>%(error)s</td>
    394     <td>%(fail)s</td>
    395     <td>通过率:%(passrate)s</td>
    396 </tr>
    397 </table>
    398 """ # variables: (test_list, count, Pass, fail, error ,passrate)
    399 
    400     REPORT_CLASS_TMPL = r"""
    401 <tr class='%(style)s warning'>
    402     <td>%(desc)s</td>
    403     <td class="text-center">%(count)s</td>
    404     <td class="text-center">%(Pass)s</td>
    405     <td class="text-center">%(error)s</td>
    406     <td class="text-center">%(fail)s</td>
    407     <td class="text-center"><a href="javascript:showClassDetail('%(cid)s',%(count)s)" class="detail" id='%(cid)s'>详细</a></td>
    408 </tr>
    409 """ # variables: (style, desc, count, Pass, fail, error, cid)
    410 
    411     #失败 的样式,去掉原来JS效果,美化展示效果  -Findyou
    412     REPORT_TEST_WITH_OUTPUT_TMPL = r"""
    413 <tr id='%(tid)s' class='%(Class)s'>
    414     <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    415     <td colspan='5' align='center'>
    416     <!--默认收起错误信息 -Findyou
    417     <button id='btn_%(tid)s' type="button"  class="btn btn-danger btn-xs collapsed" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
    418     <div id='div_%(tid)s' class="collapse">  -->
    419 
    420     <!-- 默认展开错误信息 -Findyou -->
    421     <button id='btn_%(tid)s' type="button"  class="btn btn-danger btn-xs" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
    422     <div id='div_%(tid)s' class="collapse in" style='text-align: left; color:red;cursor:pointer'>
    423     <pre>
    424     %(script)s
    425     </pre>
    426     </div>
    427     </td>
    428 </tr>
    429 """ # variables: (tid, Class, style, desc, status)
    430 
    431     # 通过 的样式,加标签效果  -Findyou
    432     REPORT_TEST_NO_OUTPUT_TMPL = r"""
    433 <tr id='%(tid)s' class='%(Class)s'>
    434     <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    435     <td colspan='5' align='center'><span class="label label-success success">%(status)s</span></td>
    436 </tr>
    437 """ # variables: (tid, Class, style, desc, status)
    438 
    439     REPORT_TEST_OUTPUT_TMPL = r"""
    440 %(id)s: %(output)s
    441 """ # variables: (id, output)
    442 
    443     # ------------------------------------------------------------------------
    444     # ENDING
    445     #
    446     # 增加返回顶部按钮  --Findyou
    447     ENDING_TMPL = """<div id='ending'> </div>
    448     <div style=" position:fixed;right:50px; bottom:30px; 20px; height:20px;cursor:pointer">
    449     <a href="#"><span class="glyphicon glyphicon-eject" style = "font-size:30px;" aria-hidden="true">
    450     </span></a></div>
    451     """
    452 
    453 # -------------------- The end of the Template class -------------------
    454 
    455 
    456 TestResult = unittest.TestResult
    457 
    458 class _TestResult(TestResult):
    459     # note: _TestResult is a pure representation of results.
    460     # It lacks the output and reporting ability compares to unittest._TextTestResult.
    461 
    462     def __init__(self, verbosity=1):
    463         TestResult.__init__(self)
    464         self.stdout0 = None
    465         self.stderr0 = None
    466         self.success_count = 0
    467         self.failure_count = 0
    468         self.error_count = 0
    469         self.verbosity = verbosity
    470 
    471         # result is a list of result in 4 tuple
    472         # (
    473         #   result code (0: success; 1: fail; 2: error),
    474         #   TestCase object,
    475         #   Test output (byte string),
    476         #   stack trace,
    477         # )
    478         self.result = []
    479         #增加一个测试通过率 --Findyou
    480         self.passrate=float(0)
    481 
    482 
    483     def startTest(self, test):
    484         TestResult.startTest(self, test)
    485         # just one buffer for both stdout and stderr
    486         self.outputBuffer = io.StringIO()
    487         stdout_redirector.fp = self.outputBuffer
    488         stderr_redirector.fp = self.outputBuffer
    489         self.stdout0 = sys.stdout
    490         self.stderr0 = sys.stderr
    491         sys.stdout = stdout_redirector
    492         sys.stderr = stderr_redirector
    493 
    494 
    495     def complete_output(self):
    496         """
    497         Disconnect output redirection and return buffer.
    498         Safe to call multiple times.
    499         """
    500         if self.stdout0:
    501             sys.stdout = self.stdout0
    502             sys.stderr = self.stderr0
    503             self.stdout0 = None
    504             self.stderr0 = None
    505         return self.outputBuffer.getvalue()
    506 
    507 
    508     def stopTest(self, test):
    509         # Usually one of addSuccess, addError or addFailure would have been called.
    510         # But there are some path in unittest that would bypass this.
    511         # We must disconnect stdout in stopTest(), which is guaranteed to be called.
    512         self.complete_output()
    513 
    514 
    515     def addSuccess(self, test):
    516         self.success_count += 1
    517         TestResult.addSuccess(self, test)
    518         output = self.complete_output()
    519         self.result.append((0, test, output, ''))
    520         if self.verbosity > 1:
    521             sys.stderr.write('ok ')
    522             sys.stderr.write(str(test))
    523             sys.stderr.write('
    ')
    524         else:
    525             sys.stderr.write('.')
    526 
    527     def addError(self, test, err):
    528         self.error_count += 1
    529         TestResult.addError(self, test, err)
    530         _, _exc_str = self.errors[-1]
    531         output = self.complete_output()
    532         self.result.append((2, test, output, _exc_str))
    533         if self.verbosity > 1:
    534             sys.stderr.write('E  ')
    535             sys.stderr.write(str(test))
    536             sys.stderr.write('
    ')
    537         else:
    538             sys.stderr.write('E')
    539 
    540     def addFailure(self, test, err):
    541         self.failure_count += 1
    542         TestResult.addFailure(self, test, err)
    543         _, _exc_str = self.failures[-1]
    544         output = self.complete_output()
    545         self.result.append((1, test, output, _exc_str))
    546         if self.verbosity > 1:
    547             sys.stderr.write('F  ')
    548             sys.stderr.write(str(test))
    549             sys.stderr.write('
    ')
    550         else:
    551             sys.stderr.write('F')
    552 
    553 
    554 class HTMLTestRunner(Template_mixin):
    555     """
    556     """
    557     def __init__(self, stream=sys.stdout, verbosity=1,title=None,description=None,tester=None):
    558         self.stream = stream
    559         self.verbosity = verbosity
    560         if title is None:
    561             self.title = self.DEFAULT_TITLE
    562         else:
    563             self.title = title
    564         if description is None:
    565             self.description = self.DEFAULT_DESCRIPTION
    566         else:
    567             self.description = description
    568         if tester is None:
    569             self.tester = self.DEFAULT_TESTER
    570         else:
    571             self.tester = tester
    572 
    573         self.startTime = datetime.datetime.now()
    574 
    575 
    576     def run(self, test):
    577         "Run the given test case or test suite."
    578         result = _TestResult(self.verbosity)
    579         test(result)
    580         self.stopTime = datetime.datetime.now()
    581         self.generateReport(test, result)
    582         print('
    Time Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)
    583         return result
    584 
    585 
    586     def sortResult(self, result_list):
    587         # unittest does not seems to run in any particular order.
    588         # Here at least we want to group them together by class.
    589         rmap = {}
    590         classes = []
    591         for n,t,o,e in result_list:
    592             cls = t.__class__
    593             if cls not in rmap:
    594                 rmap[cls] = []
    595                 classes.append(cls)
    596             rmap[cls].append((n,t,o,e))
    597         r = [(cls, rmap[cls]) for cls in classes]
    598         return r
    599 
    600     #替换测试结果status为通过率 --Findyou
    601     def getReportAttributes(self, result):
    602         """
    603         Return report attributes as a list of (name, value).
    604         Override this to add custom attributes.
    605         """
    606         startTime = str(self.startTime)[:19]
    607         duration = str(self.stopTime - self.startTime)
    608         status = []
    609         status.append('共 %s' % (result.success_count + result.failure_count + result.error_count))
    610         if result.success_count: status.append('通过 %s'    % result.success_count)
    611         if result.failure_count: status.append('失败 %s' % result.failure_count)
    612         if result.error_count:   status.append('错误 %s'   % result.error_count  )
    613         if status:
    614             status = ''.join(status)
    615             self.passrate = str("%.2f%%" % (float(result.success_count) / float(result.success_count + result.failure_count + result.error_count) * 100))
    616         else:
    617             status = 'none'
    618         return [
    619             ('测试人员', self.tester),
    620             ('开始时间',startTime),
    621             ('合计耗时',duration),
    622             ('测试结果',status + ",通过率= "+self.passrate),
    623         ]
    624 
    625 
    626     def generateReport(self, test, result):
    627         report_attrs = self.getReportAttributes(result)
    628         generator = 'HTMLTestRunner %s' % __version__
    629         stylesheet = self._generate_stylesheet()
    630         heading = self._generate_heading(report_attrs)
    631         report = self._generate_report(result)
    632         ending = self._generate_ending()
    633         output = self.HTML_TMPL % dict(
    634             title = saxutils.escape(self.title),
    635             generator = generator,
    636             stylesheet = stylesheet,
    637             heading = heading,
    638             report = report,
    639             ending = ending,
    640         )
    641         self.stream.write(output.encode('utf8'))
    642 
    643 
    644     def _generate_stylesheet(self):
    645         return self.STYLESHEET_TMPL
    646 
    647     #增加Tester显示 -Findyou
    648     def _generate_heading(self, report_attrs):
    649         a_lines = []
    650         for name, value in report_attrs:
    651             line = self.HEADING_ATTRIBUTE_TMPL % dict(
    652                     name = saxutils.escape(name),
    653                     value = saxutils.escape(value),
    654                 )
    655             a_lines.append(line)
    656         heading = self.HEADING_TMPL % dict(
    657             title = saxutils.escape(self.title),
    658             parameters = ''.join(a_lines),
    659             description = saxutils.escape(self.description),
    660             tester= saxutils.escape(self.tester),
    661         )
    662         return heading
    663 
    664     #生成报告  --Findyou添加注释
    665     def _generate_report(self, result):
    666         rows = []
    667         sortedResult = self.sortResult(result.result)
    668         for cid, (cls, cls_results) in enumerate(sortedResult):
    669             # subtotal for a class
    670             np = nf = ne = 0
    671             for n,t,o,e in cls_results:
    672                 if n == 0: np += 1
    673                 elif n == 1: nf += 1
    674                 else: ne += 1
    675 
    676             # format class description
    677             if cls.__module__ == "__main__":
    678                 name = cls.__name__
    679             else:
    680                 name = "%s.%s" % (cls.__module__, cls.__name__)
    681             doc = cls.__doc__ and cls.__doc__.split("
    ")[0] or ""
    682             desc = doc and '%s: %s' % (name, doc) or name
    683 
    684             row = self.REPORT_CLASS_TMPL % dict(
    685                 style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
    686                 desc = desc,
    687                 count = np+nf+ne,
    688                 Pass = np,
    689                 fail = nf,
    690                 error = ne,
    691                 cid = 'c%s' % (cid+1),
    692             )
    693             rows.append(row)
    694 
    695             for tid, (n,t,o,e) in enumerate(cls_results):
    696                 self._generate_report_test(rows, cid, tid, n, t, o, e)
    697 
    698         report = self.REPORT_TMPL % dict(
    699             test_list = ''.join(rows),
    700             count = str(result.success_count+result.failure_count+result.error_count),
    701             Pass = str(result.success_count),
    702             fail = str(result.failure_count),
    703             error = str(result.error_count),
    704             passrate =self.passrate,
    705         )
    706         return report
    707 
    708 
    709     def _generate_report_test(self, rows, cid, tid, n, t, o, e):
    710         # e.g. 'pt1.1', 'ft1.1', etc
    711         has_output = bool(o or e)
    712         # ID修改点为下划线,支持Bootstrap折叠展开特效 - Findyou
    713         tid = (n == 0 and 'p' or 'f') + 't%s_%s' % (cid+1,tid+1)
    714         name = t.id().split('.')[-1]
    715         doc = t.shortDescription() or ""
    716         desc = doc and ('%s: %s' % (name, doc)) or name
    717         tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
    718 
    719         # utf-8 支持中文 - Findyou
    720          # o and e should be byte string because they are collected from stdout and stderr?
    721         if isinstance(o, str):
    722             # TODO: some problem with 'string_escape': it escape 
     and mess up formating
    723             # uo = unicode(o.encode('string_escape'))
    724             # uo = o.decode('latin-1')
    725             uo = o
    726         else:
    727             uo = o
    728         if isinstance(e, str):
    729             # TODO: some problem with 'string_escape': it escape 
     and mess up formating
    730             # ue = unicode(e.encode('string_escape'))
    731             # ue = e.decode('latin-1')
    732             ue = e
    733         else:
    734             ue = e
    735 
    736         script = self.REPORT_TEST_OUTPUT_TMPL % dict(
    737             id = tid,
    738             output = saxutils.escape(uo+ue),
    739         )
    740 
    741         row = tmpl % dict(
    742             tid = tid,
    743             Class = (n == 0 and 'hiddenRow' or 'none'),
    744             style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase'),
    745             desc = desc,
    746             script = script,
    747             status = self.STATUS[n],
    748         )
    749         rows.append(row)
    750         if not has_output:
    751             return
    752 
    753     def _generate_ending(self):
    754         return self.ENDING_TMPL
    755 
    756 
    757 ##############################################################################
    758 # Facilities for running tests from the command line
    759 ##############################################################################
    760 
    761 # Note: Reuse unittest.TestProgram to launch test. In the future we may
    762 # build our own launcher to support more specific command line
    763 # parameters like test title, CSS, etc.
    764 class TestProgram(unittest.TestProgram):
    765     """
    766     A variation of the unittest.TestProgram. Please refer to the base
    767     class for command line parameters.
    768     """
    769     def runTests(self):
    770         # Pick HTMLTestRunner as the default test runner.
    771         # base class's testRunner parameter is not useful because it means
    772         # we have to instantiate HTMLTestRunner before we know self.verbosity.
    773         if self.testRunner is None:
    774             self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
    775         unittest.TestProgram.runTests(self)
    776 
    777 main = TestProgram
    778 
    779 ##############################################################################
    780 # Executing this module from the command line
    781 ##############################################################################
    782 
    783 if __name__ == "__main__":
    784     main(module=None)

     如何使用呢?

      使用方法:

        1.将上面的源码复制一份,将文件命名为HTMLTestRunner.py ,然后将HTMLTestRunner.py 文件放到Python安装目录的Lib目录下:

        2.导入HTMLTestRunner.py模块,运行以下代码,即可生成美化后的测试报告: 

     1 #!/usr/bin/env python
     2 # -*- coding: utf-8 -*-
     3 # @Time    : 2019/12/29 15:41
     4 # @Author  : lwjnicole
     5 # @File    : run_tests.py
     6 
     7 import unittest
     8 import time
     9 from HTMLTestRunner import HTMLTestRunner
    10 
    11 testcase_dir = './interface/configcenter'
    12 discover = unittest.defaultTestLoader.discover(testcase_dir, '*_test.py')
    13 
    14 if __name__ == '__main__':
    15     now = time.strftime("%Y-%m-%d %H_%M_%S")
    16     filename = './report/' + now + '_result.html'
    17     with open(filename, 'wb') as f:
    18         runner = HTMLTestRunner(stream=f, title='接口自动化测试报告', description='营销线接口')
    19         runner.run(discover)

      testcase_dir:指定需要执行的测试用例目录路径;

      HTMLTestRunner(stream=f, title='接口自动化测试报告', description='营销线接口'):还可以传入tester='lwjnicole',tester表示测试人员;

      执行完成之后,就会在上面自定义report目录下生成指定格式文件名的html测试报告啦!

  • 相关阅读:
    Dart Learn Notes 04
    Dart Learn Notes 03
    Dart Learn Notes 02
    一介书生,仅此而已
    计算机技术的演进及编程语言的多样
    C#方法(用法,参数)
    C#数组--(Array类的属性和方法)
    C#数组--(一维数组,二维数组的声明,使用及遍历)
    程序设计的编程方法
    C#流程控制语句--跳转语句(break,continue,goto,return,)
  • 原文地址:https://www.cnblogs.com/lwjnicole/p/12339874.html
Copyright © 2020-2023  润新知