HtmlReport为我所用
1 import datetime 2 import StringIO 3 import sys 4 import time 5 import unittest 6 from xml.sax import saxutils 7 8 9 class Template_mixin(object): 10 """ 11 Define a HTML template for report customerization and generation. 12 13 Overall structure of an HTML report 14 15 HTML 16 +------------------------+ 17 |<html> | 18 | <head> | 19 | | 20 | STYLESHEET | 21 | +----------------+ | 22 | | | | 23 | +----------------+ | 24 | | 25 | </head> | 26 | | 27 | <body> | 28 | | 29 | HEADING | 30 | +----------------+ | 31 | | | | 32 | +----------------+ | 33 | | 34 | REPORT | 35 | +----------------+ | 36 | | | | 37 | +----------------+ | 38 | | 39 | ENDING | 40 | +----------------+ | 41 | | | | 42 | +----------------+ | 43 | | 44 | </body> | 45 |</html> | 46 +------------------------+ 47 """ 48 49 STATUS = { 50 0: 'pass', 51 1: 'fail', 52 2: 'error', 53 } 54 55 DEFAULT_TITLE = 'Unit Test Report' 56 DEFAULT_DESCRIPTION = '' 57 58 # ------------------------------------------------------------------------ 59 # HTML Template 60 61 HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?> 62 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 63 <html xmlns="http://www.w3.org/1999/xhtml"> 64 <head> 65 <title>%(title)s</title> 66 <meta name="generator" content="%(generator)s"/> 67 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 68 %(stylesheet)s 69 </head> 70 <body> 71 <script language="javascript" type="text/javascript"><!-- 72 output_list = Array(); 73 74 /* level - 0:Summary; 1:Failed; 2:All */ 75 function showCase(level) { 76 trs = document.getElementsByTagName("tr"); 77 for (var i = 0; i < trs.length; i++) { 78 tr = trs[i]; 79 id = tr.id; 80 if (id.substr(0,2) == 'ft') { 81 if (level < 1) { 82 tr.className = 'hiddenRow'; 83 } 84 else { 85 tr.className = ''; 86 } 87 } 88 if (id.substr(0,2) == 'pt') { 89 if (level > 1) { 90 tr.className = ''; 91 } 92 else { 93 tr.className = 'hiddenRow'; 94 } 95 } 96 } 97 } 98 99 100 function showClassDetail(cid, count) { 101 var id_list = Array(count); 102 var toHide = 1; 103 for (var i = 0; i < count; i++) { 104 tid0 = 't' + cid.substr(1) + '.' + (i+1); 105 tid = 'f' + tid0; 106 tr = document.getElementById(tid); 107 if (!tr) { 108 tid = 'p' + tid0; 109 tr = document.getElementById(tid); 110 } 111 id_list[i] = tid; 112 if (tr.className) { 113 toHide = 0; 114 } 115 } 116 for (var i = 0; i < count; i++) { 117 tid = id_list[i]; 118 if (toHide) { 119 document.getElementById('div_'+tid).style.display = 'none' 120 document.getElementById(tid).className = 'hiddenRow'; 121 } 122 else { 123 document.getElementById(tid).className = ''; 124 } 125 } 126 } 127 128 129 function showTestDetail(div_id){ 130 var details_div = document.getElementById(div_id) 131 var displayState = details_div.style.display 132 // alert(displayState) 133 if (displayState != 'block' ) { 134 displayState = 'block' 135 details_div.style.display = 'block' 136 } 137 else { 138 details_div.style.display = 'none' 139 } 140 } 141 142 143 function html_escape(s) { 144 s = s.replace(/&/g,'&'); 145 s = s.replace(/</g,'<'); 146 s = s.replace(/>/g,'>'); 147 return s; 148 } 149 150 /* obsoleted by detail in <div> 151 function showOutput(id, name) { 152 var w = window.open("", //url 153 name, 154 "resizable,scrollbars,status,width=800,height=450"); 155 d = w.document; 156 d.write("<pre>"); 157 d.write(html_escape(output_list[id])); 158 d.write("\n"); 159 d.write("<a href='javascript:window.close()'>close</a>\n"); 160 d.write("</pre>\n"); 161 d.close(); 162 } 163 */ 164 --></script> 165 166 %(heading)s 167 %(report)s 168 %(ending)s 169 170 </body> 171 </html> 172 """ 173 # variables: (title, generator, stylesheet, heading, report, ending) 174 175 176 # ------------------------------------------------------------------------ 177 # Stylesheet 178 # 179 # alternatively use a <link> for external style sheet, e.g. 180 # <link rel="stylesheet" href="$url" type="text/css"> 181 182 STYLESHEET_TMPL = """ 183 <style type="text/css" media="screen"> 184 body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; } 185 table { font-size: 100%; } 186 pre { } 187 188 /* -- heading ---------------------------------------------------------------------- */ 189 h1 { 190 font-size: 16pt; 191 color: gray; 192 } 193 .heading { 194 margin-top: 0ex; 195 margin-bottom: 1ex; 196 } 197 198 .heading .attribute { 199 margin-top: 1ex; 200 margin-bottom: 0; 201 } 202 203 .heading .description { 204 margin-top: 4ex; 205 margin-bottom: 6ex; 206 } 207 208 /* -- css div popup ------------------------------------------------------------------------ */ 209 a.popup_link { 210 } 211 212 a.popup_link:hover { 213 color: red; 214 } 215 216 .popup_window { 217 display: none; 218 position: relative; 219 left: 0px; 220 top: 0px; 221 /*border: solid #627173 1px; */ 222 padding: 10px; 223 background-color: #E6E6D6; 224 font-family: "Lucida Console", "Courier New", Courier, monospace; 225 text-align: left; 226 font-size: 8pt; 227 width: 500px; 228 } 229 230 } 231 /* -- report ------------------------------------------------------------------------ */ 232 #show_detail_line { 233 margin-top: 3ex; 234 margin-bottom: 1ex; 235 } 236 #result_table { 237 width: 80%; 238 border-collapse: collapse; 239 border: 1px solid #777; 240 } 241 #header_row { 242 font-weight: bold; 243 color: white; 244 background-color: #777; 245 } 246 #result_table td { 247 border: 1px solid #777; 248 padding: 2px; 249 } 250 #total_row { font-weight: bold; } 251 .passClass { background-color: #6c6; } 252 .failClass { background-color: #c60; } 253 .errorClass { background-color: #c00; } 254 .passCase { color: #6c6; } 255 .failCase { color: #c60; font-weight: bold; } 256 .errorCase { color: #c00; font-weight: bold; } 257 .hiddenRow { display: none; } 258 .testcase { margin-left: 2em; } 259 260 261 /* -- ending ---------------------------------------------------------------------- */ 262 #ending { 263 } 264 265 </style> 266 """ 267 268 269 270 # ------------------------------------------------------------------------ 271 # Heading 272 # 273 274 HEADING_TMPL = """<div class='heading'> 275 <h1>%(title)s</h1> 276 %(parameters)s 277 <p class='description'>%(description)s</p> 278 </div> 279 280 """ # variables: (title, parameters, description) 281 282 HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p> 283 """ # variables: (name, value) 284 285 286 287 # ------------------------------------------------------------------------ 288 # Report 289 # 290 291 REPORT_TMPL = """ 292 <p id='show_detail_line'>Show 293 <a href='javascript:showCase(0)'>Summary</a> 294 <a href='javascript:showCase(1)'>Failed</a> 295 <a href='javascript:showCase(2)'>All</a> 296 </p> 297 <table id='result_table'> 298 <colgroup> 299 <col align='left' /> 300 <col align='right' /> 301 <col align='right' /> 302 <col align='right' /> 303 <col align='right' /> 304 <col align='right' /> 305 </colgroup> 306 <tr id='header_row'> 307 <td>Test Group/Test case</td> 308 <td>Count</td> 309 <td>Pass</td> 310 <td>Fail</td> 311 <td>Error</td> 312 <td>View</td> 313 </tr> 314 %(test_list)s 315 <tr id='total_row'> 316 <td>Total</td> 317 <td>%(count)s</td> 318 <td>%(Pass)s</td> 319 <td>%(fail)s</td> 320 <td>%(error)s</td> 321 <td> </td> 322 </tr> 323 </table> 324 """ # variables: (test_list, count, Pass, fail, error) 325 326 REPORT_CLASS_TMPL = r""" 327 <tr class='%(style)s'> 328 <td>%(desc)s</td> 329 <td>%(count)s</td> 330 <td>%(Pass)s</td> 331 <td>%(fail)s</td> 332 <td>%(error)s</td> 333 <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td> 334 </tr> 335 """ # variables: (style, desc, count, Pass, fail, error, cid) 336 337 338 REPORT_TEST_WITH_OUTPUT_TMPL = r""" 339 <tr id='%(tid)s' class='%(Class)s'> 340 <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> 341 <td colspan='5' align='center'> 342 343 <!--css div popup start--> 344 <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" > 345 %(status)s</a> 346 347 <div id='div_%(tid)s' class="popup_window"> 348 <div style='text-align: right; color:red;cursor:pointer'> 349 <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " > 350 [x]</a> 351 </div> 352 <pre> 353 test 354 %(script)s 355 </pre> 356 </div> 357 <!--css div popup end--> 358 359 </td> 360 </tr> 361 """ # variables: (tid, Class, style, desc, status) 362 363 364 REPORT_TEST_NO_OUTPUT_TMPL = r""" 365 <tr id='%(tid)s' class='%(Class)s'> 366 <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> 367 <td colspan='5' align='center'>%(status)s</td> 368 </tr> 369 """ # variables: (tid, Class, style, desc, status) 370 371 372 REPORT_TEST_OUTPUT_TMPL = r""" 373 %(id)s: %(output)s 374 """ # variables: (id, output) 375 376 377 378 # ------------------------------------------------------------------------ 379 # ENDING 380 # 381 382 ENDING_TMPL = """<div id='ending'> </div>""" 383 384 385 class HTMLTestRunner(Template_mixin): 386 """ 387 """ 388 def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None): 389 self.stream = stream 390 self.verbosity = verbosity 391 if title is None: 392 self.title = self.DEFAULT_TITLE 393 else: 394 self.title = title 395 if description is None: 396 self.description = self.DEFAULT_DESCRIPTION 397 else: 398 self.description = description 399 400 self.startTime = datetime.datetime.now() 401 402 403 #def run(self, test): 404 # "Run the given test case or test suite." 405 # result = _TestResult(self.verbosity) 406 # test(result) 407 # self.stopTime = datetime.datetime.now() 408 # self.generateReport(test, result) 409 # print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime) 410 # return result 411 412 def sortResult(self, result_list): 413 # unittest does not seems to run in any particular order. 414 # Here at least we want to group them together by class. 415 rmap = {} 416 classes = [] 417 for n,t,o,e in result_list: 418 cls = t.__class__ 419 if not rmap.has_key(cls): 420 rmap[cls] = [] 421 classes.append(cls) 422 rmap[cls].append((n,t,o,e)) 423 r = [(cls, rmap[cls]) for cls in classes] 424 return r 425 426 427 def getReportAttributes(self, result): 428 """ 429 Return report attributes as a list of (name, value). 430 Override this to add custom attributes. 431 """ 432 startTime = str(self.startTime)[:19] 433 self.stopTime = self.startTime + datetime.timedelta(days =1) 434 self.startTime = datetime.datetime.now() 435 duration = str(self.stopTime - self.startTime) 436 status = [] 437 if result.success_count: status.append('Pass %s' % result.success_count) 438 if result.failure_count: status.append('Failure %s' % result.failure_count) 439 if result.error_count: status.append('Error %s' % result.error_count ) 440 if status: 441 status = ' '.join(status) 442 else: 443 status = 'none' 444 return [ 445 ('Start Time', startTime), 446 ('Duration', duration), 447 ('Status', status), 448 ] 449 450 451 def generateReport(self, result): 452 report_attrs = self.getReportAttributes(result) 453 generator = 'HTMLTestRunner %s' % '111111' 454 stylesheet = self._generate_stylesheet() 455 heading = self._generate_heading(report_attrs) 456 report = self._generate_report(result) 457 ending = self._generate_ending() 458 output = self.HTML_TMPL % dict( 459 title = saxutils.escape(self.title), 460 generator = generator, 461 stylesheet = stylesheet, 462 heading = heading, 463 report = report, 464 ending = ending, 465 ) 466 self.stream.write(output.encode('utf8')) 467 468 469 def _generate_stylesheet(self): 470 return self.STYLESHEET_TMPL 471 472 473 def _generate_heading(self, report_attrs): 474 a_lines = [] 475 for name, value in report_attrs: 476 line = self.HEADING_ATTRIBUTE_TMPL % dict( 477 name = saxutils.escape(name), 478 value = saxutils.escape(value), 479 ) 480 a_lines.append(line) 481 heading = self.HEADING_TMPL % dict( 482 title = saxutils.escape(self.title), 483 parameters = ''.join(a_lines), 484 description = saxutils.escape(self.description), 485 ) 486 return heading 487 488 489 def _generate_report(self, result): 490 rows = [] 491 #sortedResult = self.sortResult(result.result) 492 #for cid, (cls, cls_results) in enumerate(sortedResult): 493 for cid, cls_results in enumerate(result.result): 494 # subtotal for a class 495 np = nf = ne = 0 496 for cls_item in cls_results: 497 if cls_item['status'] == 'passed': 498 np += 1 499 elif cls_item['status'] == 'failed': 500 nf += 1 501 else: 502 ne += 1 503 ''' 504 for n,t,o,e in cls_results: 505 if n == 0: np += 1 506 elif n == 1: nf += 1 507 else: ne += 1 508 ''' 509 510 # format class description 511 ''' 512 if cls.__module__ == "__main__": 513 name = cls.__name__ 514 else: 515 name = "%s.%s" % (cls.__module__, cls.__name__) 516 doc = cls.__doc__ and cls.__doc__.split("\n")[0] or "" 517 desc = doc and '%s: %s' % (name, doc) or name 518 ''' 519 desc = 'testttttttttttttttttttt' 520 row = self.REPORT_CLASS_TMPL % dict( 521 style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass', 522 desc = desc, 523 count = np+nf+ne, 524 Pass = np, 525 fail = nf, 526 error = ne, 527 cid = 'c%s' % (cid+1), 528 ) 529 print 'style',ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass' 530 rows.append(row) 531 532 for tid, cls_items in enumerate(cls_results): 533 534 self._generate_report_test(rows, cid, tid, cls_items['status'], 535 cls_items['name'], 'aaaaaaaaa', 536 'bbbbbbb\n\n\n\n\n\n\n\n\ccccccccccc') 537 ''' 538 for tid, (n,t,o,e) in enumerate(cls_results): 539 self._generate_report_test(rows, cid, tid, n, t, o, e) 540 ''' 541 report = self.REPORT_TMPL % dict( 542 test_list = ''.join(rows), 543 count = str(result.success_count+result.failure_count+result.error_count), 544 Pass = str(result.success_count), 545 fail = str(result.failure_count), 546 error = str(result.error_count), 547 ) 548 return report 549 550 551 def _generate_report_test(self, rows, cid, tid, n, t, o, e): 552 # e.g. 'pt1.1', 'ft1.1', etc 553 has_output = bool(o or e) 554 tid = (n == 'passed' and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1) 555 ''' 556 name = t.id().split('.')[-1] 557 doc = t.shortDescription() or "" 558 desc = doc and ('%s: %s' % (name, doc)) or name 559 ''' 560 desc = t 561 tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL 562 563 # o and e should be byte string because they are collected from stdout and stderr? 564 if isinstance(o,str): 565 # TODO: some problem with 'string_escape': it escape \n and mess up formating 566 # uo = unicode(o.encode('string_escape')) 567 uo = o.decode('latin-1') 568 else: 569 uo = o 570 if isinstance(e,str): 571 # TODO: some problem with 'string_escape': it escape \n and mess up formating 572 # ue = unicode(e.encode('string_escape')) 573 ue = e.decode('latin-1') 574 else: 575 ue = e 576 577 script = self.REPORT_TEST_OUTPUT_TMPL % dict( 578 id = tid, 579 output = saxutils.escape(uo+ue), 580 ) 581 #print script,'aaaaaaaaaa' 582 row = tmpl % dict( 583 tid = tid, 584 Class = (n == 0 and 'hiddenRow' or 'none'), 585 style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'), 586 desc = desc, 587 script = script, 588 #status = self.STATUS[n], 589 status = n 590 ) 591 rows.append(row) 592 if not has_output: 593 return 594 595 def _generate_ending(self): 596 return self.ENDING_TMPL 597 598 class GetResult(): 599 600 def __init__(self): 601 self.success_count = 10 602 self.failure_count = 5 603 self.error_count = 3 604 self.result = [[{'status':'failed','name':'tc11',}, 605 {'status':'passed','name':'tc12',}, 606 {'status':'error','name':'tc13',}], 607 608 [{'status':'failed','name':'tc21',}, 609 {'status':'passed','name':'tc22',}, 610 {'status':'error','name':'tc23',}], 611 ] 612 613 if __name__ == '__main__': 614 615 filename="./xxx111.html" 616 fp=file(filename,'wb') 617 result = GetResult() 618 print enumerate(result.result) 619 620 for a,b in enumerate(result.result): 621 print a 622 print b 623 for w,c in enumerate(b): 624 print w 625 print c 626 627 #print c 628 report = HTMLTestRunner(stream=fp,title='Report_title',description='Report_description') 629 report.generateReport(result) 630 pass
posted on 2014-08-15 14:43 Newbie wang 阅读(1208) 评论(0) 收藏 举报
浙公网安备 33010602011771号