前面咱們講到過在作自動化測試或單元測試的時候使用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測試報告啦!