Python3之HTMLTestRunner測試報告美化

  前面咱們講到過在作自動化測試或單元測試的時候使用HTMLTestRunner來生成測試報告,而且因爲Python2 和 Python3 對於HTMLTestRunner的支持稍微有點差別,因此咱們將HTMLTestRunner進行了改造,從而適配Python3,詳細改造步驟能夠參考:HTMLTestRunner修改爲Python3版本javascript

  可是改造後的HTMLTestRunner生成的測試報告不是特別的美觀,因此我又對HTMLTestRunner進行了進一步的改造,主要是作一些美化。css

  美化以前的測試報告以下:html

 

   美化以後的測試報告以下:前端

   從上面兩個報告的對比來看,第二個測試報告是否是更爲美觀呢?java

 

下面是我改造後的HTMLTestRunner源碼:python

 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; width: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('\n') 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('\n') 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('\n') 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('\nTime 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("\n")[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 \n 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 \n 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)

 如何使用呢?

  使用方法:jquery

    1.將上面的源碼複製一份,將文件命名爲HTMLTestRunner.py ,而後將HTMLTestRunner.py 文件放到Python安裝目錄的Lib目錄下:bootstrap

 

 

    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:指定須要執行的測試用例目錄路徑;app

  HTMLTestRunner(stream=f, title='接口自動化測試報告', description='營銷線接口'):還能夠傳入tester='lwjnicole',tester表示測試人員;

  執行完成以後,就會在上面自定義report目錄下生成指定格式文件名的html測試報告啦!

相關文章
相關標籤/搜索