• Python2 HTMLTestRunner自动化测试报告美化


    python2 的测试报告美化,需要的同学直接用

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




    效果:

  • 相关阅读:
    回调函数设计方法
    C 时间函数总结
    linux多线程全面解析
    从为知笔记收费说起
    C++中strftime()的详细说明
    arguments.callee
    arguments 对象
    学习闭包
    this的call,apply,bind的方法总结--追梦子
    this指向--取自追梦子的文章
  • 原文地址:https://www.cnblogs.com/wangyinghao/p/10314887.html
Copyright © 2020-2023  润新知