Python使用TestLoader、TestSuite、HTMLTestRunner组织测试用例实例

HTMLTestRunner.py:http://tungwaiyip.info/software/HTMLTestRunner.html

上述的生成html格式的测试报告的库文件下载链接,如果下载不成,我会在文章底部附上该文件源码。

该篇文章是讲述一个文件包含两个测试用例,如何组织运行。整体的架构如下:

关于calcutor前的文章有相应的源码。可以找到,下面附出withTestLoader.py的源码:

1#coding=utf8 2''' 3unittest提供一个TestLoader类用于自动创建一个测试集并把单个测试放入到测试集中。 4TestLoader自动运行测试用例以test开头的方法的测试方法。 5在多个测试用例在决定运行哪个测试用例的策略是通过内建函数对测试函数名排序决定的。 6 7通常习惯将测试集组合在一起,以便系统一次运行所有的测试用例。 8TestSuite实例添加到一个TestSuite中就像把一个TestCase实例添加到一个TestSuite中。 9----------------------------------------------------------------------------------------- 10suite1=module1.TheTestSuite() 11suite2=module2.TheTestSuite() 12alltests=unittest.TestSuite([suite1,suite2]) 13----------------------------------------------------------------------------------------- 14 15可以将测试用例和测试套件的定义放在与要测试的代码相同的模块中,但将测试代码放在单独的模块中有几个好处: 161、测试模块可以从命令行独立运行。 172、测试代码可以更容易地与运输代码分开。 183、有更少的诱惑改变测试代码,以适应代码测试没有一个很好的理由。 194、测试代码的修改频率要比它测试的代码少得多。 205、测试代码可以更容易重构。 216、用C编写的模块的测试必须在单独的模块中,所以为什么不一致? 227、如果测试策略更改,则不需要更改源代码。 23''' 24from __future__ import division 25from Lib.HTMLTestRunner import HTMLTestRunner 26from unittest import TestCase,TestLoader,TestSuite 27from source.calcutor import calculatorClass 28 29class TestMul(TestCase): 30 def setUp(self): 31 pass 32 33 def test_defaultMul(self): 34 cal=calculatorClass() 35 self.assertEqual(cal.mul(),200 , "The result should be equal 200") 36 37 def test_negtiveMul(self): 38 cal=calculatorClass(-10,-25) 39 self.assertEqual(cal.mul(),250 , "The result should be equal 250") 40 41 def test_floatMul(self): 42 cal=calculatorClass(0.25,0.4) 43 self.assertEqual(cal.mul(),0.1, "The result should be equal 0.1") 44 45 def tearDown(self): 46 pass 47 48class TestDiv(TestCase): 49 def setUp(self): 50 pass 51 52 def test_defaultDiv(self): 53 cal=calculatorClass() 54 self.assertEqual(cal.mul(),0.5 , "The result should be equal 0.5") 55 56 def test_negtiveDiv(self): 57 cal=calculatorClass(-10,-25) 58 self.assertEqual(cal.mul(),0.4 , "The result should be equal 250") 59 60 def test_floatDiv(self): 61 cal=calculatorClass(0.24,0.4) 62 self.assertEqual(cal.mul(),0.6, "The result should be equal 0.1") 63 64 def tearDown(self): 65 pass 66 67def allTest(): 68 ''' 创建测试集''' 69 suite1=TestLoader().loadTestsFromTestCase(TestMul) 70 suite2=TestLoader().loadTestsFromTestCase(TestDiv) 71 alltests=TestSuite([suite1,suite2]) 72 return alltests 73 74if __name__=="__main__": 75 '''创建保存测试结果的文件''' 76 html=file("..\\report.html","wb+") 77 '''调用HTMLTestRunner类生成html格式测试运行报告''' 78 runner=HTMLTestRunner(stream=html,title="Test Report",description="The state of the run testcase") 79 runner.run(allTest()) 80 html.close()

测试运行结果报告如下:

附录:

HTMLTestRunner.py

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

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )