zoukankan      html  css  js  c++  java
  • Python3 HTMLTestRunner自动化测试报告美化

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

     效果:

  • 相关阅读:
    Python中怎么使用(冒泡排序)?
    Python中怎么定义与调用(函数)
    简易购物商城(1.0)版本
    非空即真 和 切片 处理方式
    python模块-random
    内置函数
    多维数组
    函数扩展
    文件操作扩展2
    文件操作扩展
  • 原文地址:https://www.cnblogs.com/wangyinghao/p/10314722.html
Copyright © 2011-2022 走看看