报告样式效果:

报告源码:HTMLTestRunner.py

   1 """
2 A TestRunner for use with the Python unit testing framework. It
3 generates a HTML report to show the result at a glance.
4
5 ------------------------------------------------------------------------
6 Copyright (c) 2004-2020, Wai Yip Tung
7 All rights reserved.
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are
10 met:
11 * Redistributions of source code must retain the above copyright notice,
12 this list of conditions and the following disclaimer.
13 * Redistributions in binary form must reproduce the above copyright
14 notice, this list of conditions and the following disclaimer in the
15 documentation and/or other materials provided with the distribution.
16 * Neither the name Wai Yip Tung nor the names of its contributors may be
17 used to endorse or promote products derived from this software without
18 specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
23 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 """
31
32 # URL: http://tungwaiyip.info/software/HTMLTestRunner.html
33
34 __author__ = "Wai Yip Tung , bugmaster"
35 __version__ = "0.9.0"
36
37 """
38 Change History
39
40 Version 0.9.0
41 * Increased repeat execution
42 * Added failure screenshots
43
44 Version 0.8.2
45 * Show output inline instead of popup window (Viorel Lupu).
46
47 Version in 0.8.1
48 * Validated XHTML (Wolfgang Borgert).
49 * Added description of test classes and test cases.
50
51 Version in 0.8.0
52 * Define Template_mixin class for customization.
53 * Workaround a IE 6 bug that it does not treat <script> block as CDATA.
54
55 Version in 0.7.1
56 * Back port to Python 2.3 (Frank Horowitz).
57 * Fix missing scroll bars in detail log (Podi).
58 """
59
60 # TODO: color stderr
61 # TODO: simplify javascript using ,ore than 1 class in the class attribute?
62
63 import datetime
64 import io
65 import sys
66 import time
67 import copy
68 import unittest
69 from xml.sax import saxutils
70
71
72 # ------------------------------------------------------------------------
73 # The redirectors below are used to capture output during testing. Output
74 # sent to sys.stdout and sys.stderr are automatically captured. However
75 # in some cases sys.stdout is already cached before HTMLTestRunner is
76 # invoked (e.g. calling logging.basicConfig). In order to capture those
77 # output, use the redirectors for the cached stream.
78 #
79 # e.g.
80 # >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
81 # >>>
82
83 class OutputRedirector(object):
84 """ Wrapper to redirect stdout or stderr """
85
86 def __init__(self, fp):
87 self.fp = fp
88
89 def write(self, s):
90 self.fp.write(s)
91
92 def writelines(self, lines):
93 self.fp.writelines(lines)
94
95 def flush(self):
96 self.fp.flush()
97
98
99 stdout_redirector = OutputRedirector(sys.stdout)
100 stderr_redirector = OutputRedirector(sys.stderr)
101
102
103 # ----------------------------------------------------------------------
104 # Template
105
106 class Template_mixin(object):
107 """
108 Define a HTML template for report customerization and generation.
109 Overall structure of an HTML report
110 HTML
111 +------------------------+
112 |<html> |
113 | <head> |
114 | |
115 | STYLESHEET |
116 | +----------------+ |
117 | | | |
118 | +----------------+ |
119 | |
120 | </head> |
121 | |
122 | <body> |
123 | |
124 | HEADING |
125 | +----------------+ |
126 | | | |
127 | +----------------+ |
128 | |
129 | REPORT |
130 | +----------------+ |
131 | | | |
132 | +----------------+ |
133 | |
134 | ENDING |
135 | +----------------+ |
136 | | | |
137 | +----------------+ |
138 | |
139 | </body> |
140 |</html> |
141 +------------------------+
142 """
143
144 STATUS = {
145 0: 'pass',
146 1: 'fail',
147 2: 'error',
148 3: 'skip',
149 }
150
151 DEFAULT_TITLE = 'Unit Test Report'
152 DEFAULT_DESCRIPTION = ''
153
154 # ------------------------------------------------------------------------
155 # HTML Template
156
157 HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
158 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
159 <html xmlns="http://www.w3.org/1999/xhtml">
160 <head>
161 <title>%(title)s</title>
162 <meta name="generator" content="%(generator)s"/>
163 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
164 <script src="https://lib.baomitu.com/jquery/3.5.1/jquery.min.js"></script>
165 <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
166 <script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
167 <script src="http://apps.bdimg.com/libs/Chart.js/0.2.0/Chart.min.js"></script>
168 <link rel="stylesheet" href="http://img.itest.info/seldom.css">
169
170 %(stylesheet)s
171 </head>
172 <body>
173 <script language="javascript" type="text/javascript">
174
175 function show_img(obj) {
176 var obj1 = obj.nextElementSibling
177 obj1.style.display='block'
178 var index = 0;//每张图片的下标,
179 var len = obj1.getElementsByTagName('img').length;
180 var imgyuan = obj1.getElementsByClassName('imgyuan')[0]
181 //var start=setInterval(autoPlay,500);
182 obj1.onmouseover=function(){//当鼠标光标停在图片上,则停止轮播
183 clearInterval(start);
184 }
185 obj1.onmouseout=function(){//当鼠标光标停在图片上,则开始轮播
186 start=setInterval(autoPlay,1000);
187 }
188 for (var i = 0; i < len; i++) {
189 var font = document.createElement('font')
190 imgyuan.appendChild(font)
191 }
192 var lis = obj1.getElementsByTagName('font');//得到所有圆圈
193 changeImg(0)
194 var funny = function (i) {
195 lis[i].onmouseover = function () {
196 index=i
197 changeImg(i)
198 }
199 }
200 for (var i = 0; i < lis.length; i++) {
201 funny(i);
202 }
203
204 function autoPlay(){
205 if(index>len-1){
206 index=0;
207 clearInterval(start); //运行一轮后停止
208 }
209 changeImg(index++);
210 }
211 imgyuan.style.width= 25*len +"px";
212 //对应圆圈和图片同步
213 function changeImg(index) {
214 var list = obj1.getElementsByTagName('img');
215 var list1 = obj1.getElementsByTagName('font');
216 for (i = 0; i < list.length; i++) {
217 list[i].style.display = 'none';
218 list1[i].style.backgroundColor = 'white';
219 }
220 list[index].style.display = 'block';
221 list1[index].style.backgroundColor = 'red';
222 }
223 }
224
225 function hide_img(obj){
226 obj.parentElement.style.display = "none";
227 obj.parentElement.getElementsByClassName('imgyuan')[0].innerHTML = "";
228 }
229
230 output_list = Array();
231 /* level - 0:Summary; 1:Failed; 2:Skip; 3:All */
232 function showCase(level, channel) {
233 trs = document.getElementsByTagName("tr");
234 for (var i = 0; i < trs.length; i++) {
235 tr = trs[i];
236 id = tr.id;
237 if (["ft","pt","et","st"].indexOf(id.substr(0,2))!=-1){
238 if ( level == 0 && id.substr(2,1) == channel ) {
239 tr.className = 'hiddenRow';
240 }
241 }
242 if (id.substr(0,3) == 'pt'+ channel) {
243 if ( level == 1){
244 tr.className = '';
245 }
246 else if (level > 4 && id.substr(2,1) == channel ){
247 tr.className = '';
248 }
249 else {
250 tr.className = 'hiddenRow';
251 }
252 }
253 if (id.substr(0,3) == 'ft'+channel) {
254 if (level == 2) {
255 tr.className = '';
256 }
257 else if (level > 4 && id.substr(2,1) == channel ){
258 tr.className = '';
259 }
260 else {
261 tr.className = 'hiddenRow';
262 }
263 }
264 if (id.substr(0,3) == 'et'+channel) {
265 if (level == 3) {
266 tr.className = '';
267 }
268 else if (level > 4 && id.substr(2,1) == channel ){
269 tr.className = '';
270 }
271 else {
272 tr.className = 'hiddenRow';
273 }
274 }
275 if (id.substr(0,3) == 'st'+channel) {
276 if (level == 4) {
277 tr.className = '';
278 }
279 else if (level > 4 && id.substr(2,1) == channel ){
280 tr.className = '';
281 }
282 else {
283 tr.className = 'hiddenRow';
284 }
285 }
286 }
287 }
288 function showClassDetail(cid, count) {
289 var id_list = Array(count);
290 var toHide = 1;
291 for (var i = 0; i < count; i++) {
292 tid0 = 't' + cid.substr(1) + '.' + (i+1);
293 tid = 'f' + tid0;
294 tr = document.getElementById(tid);
295 if (!tr) {
296 tid = 'p' + tid0;
297 tr = document.getElementById(tid);
298 }
299 if (!tr) {
300 tid = 'e' + tid0;
301 tr = document.getElementById(tid);
302 }
303 if (!tr) {
304 tid = 's' + tid0;
305 tr = document.getElementById(tid);
306 }
307 id_list[i] = tid;
308 if (tr.className) {
309 toHide = 0;
310 }
311 }
312 for (var i = 0; i < count; i++) {
313 tid = id_list[i];
314 if (toHide) {
315 document.getElementById(tid).className = 'hiddenRow';
316 }
317 else {
318 document.getElementById(tid).className = '';
319 }
320 }
321 }
322 function showTestDetail(div_id){
323 var details_div = document.getElementById(div_id)
324 var displayState = details_div.style.display
325 // alert(displayState)
326 if (displayState != 'block' ) {
327 displayState = 'block'
328 details_div.style.display = 'block'
329 }
330 else {
331 details_div.style.display = 'none'
332 }
333 }
334 function html_escape(s) {
335 s = s.replace(/&/g,'&amp;');
336 s = s.replace(/</g,'&lt;');
337 s = s.replace(/>/g,'&gt;');
338 return s;
339 }
340 /* obsoleted by detail in <div>
341 function showOutput(id, name) {
342 var w = window.open("", //url
343 name,
344 "resizable,scrollbars,status,width=800,height=450");
345 d = w.document;
346 d.write("<pre>");
347 d.write(html_escape(output_list[id]));
348 d.write("\n");
349 d.write("<a href='javascript:window.close()'>close</a>\n");
350 d.write("</pre>\n");
351 d.close();
352 }
353 */
354 </script>
355 %(heading)s
356 %(report)s
357 %(ending)s
358 %(chart_script)s
359 </body>
360 </html>
361 """
362 # variables: (title, generator, stylesheet, heading, report, ending)
363
364 # ------------------------------------------------------------------------
365 # Stylesheet
366 #
367 # alternatively use a <link> for external style sheet, e.g.
368 # <link rel="stylesheet" href="$url" type="text/css">
369
370 STYLESHEET_TMPL = """
371 <style type="text/css" media="screen">
372 body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
373 table { font-size: 100%; }
374 pre { }
375 /* -- heading ---------------------------------------------------------------------- */
376 h1 {
377 font-size: 16pt;
378 color: gray;
379 }
380 .heading {
381 margin-top: 20px;
382 margin-bottom: 1ex;
383 margin-left: 10px;
384 margin-right: 10px;
385 width: 23%;
386 float: left;
387 padding-top: 10px;
388 padding-left: 10px;
389 padding-bottom: 10px;
390 padding-right: 10px;
391 box-shadow:0px 0px 5px #000;
392 }
393 .heading .attribute {
394 margin-top: 1ex;
395 margin-bottom: 0;
396 }
397 .heading .description {
398 margin-top: 4ex;
399 margin-bottom: 6ex;
400 }
401 /* -- css div popup ------------------------------------------------------------------------ */
402 a.popup_link {
403 }
404 a.popup_link:hover {
405 color: red;
406 }
407 .popup_window {
408 display: none;
409 position: relative;
410 left: 0px;
411 top: 0px;
412 /*border: solid #627173 1px; */
413 font-family: "Lucida Console", "Courier New", Courier, monospace;
414 text-align: left;
415 font-size: 12pt;
416 width: 500px;
417 }
418 }
419 /* -- report ------------------------------------------------------------------------ */
420 #show_detail_line {
421 margin-top: 3ex;
422 margin-bottom: 1ex;
423 margin-left: 10px;
424 }
425
426 #header_row {
427 font-weight: bold;
428 color: #606060;
429 background-color: #f5f5f5;
430 border-top-width: 10px;
431 border-color: #d6e9c6;
432 font-size: 15px;
433 }
434
435 #total_row { font-weight: bold; background-color: #dee2e6;}
436 .passClass { background-color: #d6e9c6; }
437 .failClass { background-color: #faebcc; }
438 .errorClass { background-color: #ebccd1; }
439 .passCase { color: #28a745; font-weight: bold;}
440 .failCase { color: #c60; font-weight: bold; }
441 .errorCase { color: #c00; font-weight: bold; }
442 .hiddenRow { display: none; }
443 .none {color: #009900 }
444 .testcase { margin-left: 2em; }
445 /* -- ending ---------------------------------------------------------------------- */
446 #ending {
447 }
448 /* -- chars ---------------------------------------------------------------------- */
449 .testChars {width: 900px;margin-left: 0px;}
450 .error-color {
451 color: #fff;
452 background-color: #f44455;
453 border-color: #f44455;
454 }
455 .pass-color {
456 color: #fff;
457 background-color: #5fc27e;
458 border-color: #5fc27e;
459 }
460 .fail-color {
461 color: #fff;
462 background-color: #fcc100;
463 border-color: #fcc100;
464 }
465 .skip-color {
466 color: #fff;
467 background-color: #6c757d;
468 border-color: #6c757d;
469 }
470
471 /* -- screenshots ---------------------------------------------------------------------- */
472 .img{
473 height: 100%;
474 border-collapse: collapse;
475 }
476 .screenshots {
477 z-index: 100;
478 position:fixed;
479 height: 80%;
480 left: 50%;
481 top: 50%;
482 transform: translate(-50%,-50%);
483 display: none;
484 box-shadow:1px 2px 20px #333333;
485 }
486 .imgyuan{
487 height: 20px;
488 border-radius: 12px;
489 background-color: red;
490 padding-left: 13px;
491 margin: 0 auto;
492 position: relative;
493 top: -40px;
494 background-color: rgba(1, 150, 0, 0.3);
495 }
496 .imgyuan font{
497 border:1px solid white;
498 width:11px;
499 height:11px;
500 border-radius:50%;
501 margin-right: 9px;
502 margin-top: 4px;
503 display: block;
504 float: left;
505 background-color: white;
506 }
507
508 .close_shots {
509 background-image: url(data:image/png;base64,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);
510 background-size: 22px 22px;
511 -moz-background-size: 22px 22px;
512 background-repeat: no-repeat;
513 position: absolute;
514 top: 5px;
515 right: 5px;
516 height: 22px;
517 z-index: 99;
518 width: 22px;
519 ox-shadow:1px 2px 5px #333333;
520 }
521
522 </style>
523 """
524
525 # ------------------------------------------------------------------------
526 # Heading
527 #
528
529 HEADING_TMPL = """
530 <nav class="navbar navbar-expand navbar-light bg-white">
531 <a class="sidebar-toggle d-flex mr-2">
532 <i class="hamburger align-self-center"></i>
533 </a>
534 <h1 style="margin-bottom: 0px;">seldom</h1>
535 <div class="navbar-collapse collapse">
536 <ul class="navbar-nav ml-auto">
537 <h3 style="float: right;">%(title)s</h3>
538 </ul>
539 </div>
540 </nav>
541 <div style="height: 260px; margin-top: 20px;">
542 <div class="col-12 col-lg-5 col-xl-3 d-flex" style="float:left">
543 <div class='card flex-fill'>
544 <div class="card-body my-2">
545 <table class="table my-0">
546 <tbody>
547 %(parameters)s
548 <tr><td>Description:</td><td class="text-right">%(description)s</td></tr>
549 </tbody>
550 </table>
551 </div>
552 </div>
553 </div>
554
555 <div style="float:left; margin-left: 10px; margin-top: 20px;">
556 <p> Test Case Pie charts </p>
557 <h2 class="d-flex align-items-center mb-0 font-weight-light pass-color">%(pass_count)s</h2>
558 <a>PASSED</a><br>
559 <h2 class="d-flex align-items-center mb-0 font-weight-light fail-color">%(fail_count)s</h2>
560 <a>FAILED</a>
561 <h2 class="d-flex align-items-center mb-0 font-weight-light error-color">%(error_count)s</h2>
562 <a>ERRORS</a><br>
563 <h2 class="d-flex align-items-center mb-0 font-weight-light skip-color">%(skip_count)s</h2>
564 <a>SKIPED</a><br>
565 </div>
566 <div class="testChars">
567 <canvas id="myChart" width="250" height="250"></canvas>
568 </div>
569
570 </div>
571 """ # variables: (title, parameters, description)
572
573 # ------------------------------------------------------------------------
574 # Pie chart
575 #
576
577 ECHARTS_SCRIPT = """
578 <script type="text/javascript">
579 var data = [
580 {
581 value: %(error)s,
582 color: "#f44455",
583 label: "Error",
584 labelColor: 'white',
585 labelFontSize: '16'
586 },
587 {
588 value : %(fail)s,
589 color : "#fcc100",
590 label: "Fail",
591 labelColor: 'white',
592 labelFontSize: '16'
593 },
594 {
595 value : %(Pass)s,
596 color : "#5fc27e",
597 label : "Pass",
598 labelColor: 'white',
599 labelFontSize: '16'
600 },
601 {
602 value : %(skip)s,
603 color : "#6c757d",
604 label : "skip",
605 labelColor: 'white',
606 labelFontSize: '16'
607 }
608 ]
609 var newopts = {
610 animationSteps: 100,
611 animationEasing: 'easeInOutQuart',
612 }
613 //Get the context of the canvas element we want to select
614 var ctx = document.getElementById("myChart").getContext("2d");
615 var myNewChart = new Chart(ctx).Pie(data,newopts);
616 </script>
617 """
618
619 HEADING_ATTRIBUTE_TMPL = """<tr><td>%(name)s:</td><td class="text-right">%(value)s</td></tr>
620 """ # variables: (name, value)
621
622 # ------------------------------------------------------------------------
623 # Report
624 #
625
626 REPORT_TMPL = """
627 <p id='show_detail_line' style="margin-left: 10px; margin-top: 30px;">
628 <a href='javascript:showCase(0, %(channel)s)' class="btn btn-dark btn-sm">Summary</a>
629 <a href='javascript:showCase(1, %(channel)s)' class="btn btn-success btn-sm">Pass</a>
630 <a href='javascript:showCase(2, %(channel)s)' class="btn btn-warning btn-sm">Failed</a>
631 <a href='javascript:showCase(3, %(channel)s)' class="btn btn-danger btn-sm">Error</a>
632 <a href='javascript:showCase(4, %(channel)s)' class="btn btn-light btn-sm">Skip</a>
633 <a href='javascript:showCase(5, %(channel)s)' class="btn btn-info btn-sm">All</a>
634 </p>
635 <table class="table mb-0">
636 <thead>
637 <tr id='header_row'>
638 <td>Test Group/Test case</td>
639 <td>Count</td>
640 <td>Pass</td>
641 <td>Fail</td>
642 <td>Error</td>
643 <td>View</td>
644 <td>Screenshots</td>
645 </tr>
646 </thead>
647 %(test_list)s
648 <tr id='total_row'>
649 <td>Total</td>
650 <td>%(count)s</td>
651 <td class="text text-success">%(Pass)s</td>
652 <td class="text text-danger">%(fail)s</td>
653 <td class="text text-warning">%(error)s</td>
654 <td>&nbsp;</td>
655 <td>&nbsp;</td>
656 </tr>
657 </table>
658 """ # variables: (test_list, count, Pass, fail, error)
659
660 REPORT_CLASS_TMPL = r"""
661 <tr class='%(style)s'>
662 <td>%(desc)s</td>
663 <td>%(count)s</td>
664 <td>%(Pass)s</td>
665 <td>%(fail)s</td>
666 <td>%(error)s</td>
667 <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
668 <td>&nbsp;</td>
669 </tr>
670 """ # variables: (style, desc, count, Pass, fail, error, cid)
671
672 REPORT_TEST_WITH_OUTPUT_TMPL = r"""
673 <tr id='%(tid)s' class='%(Class)s'>
674 <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
675 <td colspan='5' align='center'>
676 <!--css div popup start-->
677 <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
678 %(status)s</a>
679 <div id='div_%(tid)s' class="popup_window">
680 <div style='text-align: right; color:red;cursor:pointer'>
681 <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
682 [x]</a>
683 </div>
684 <pre>
685 %(script)s
686 </pre>
687 </div>
688 <!--css div popup end-->
689 </td>
690 <td>%(img)s</td>
691 </tr>
692 """ # variables: (tid, Class, style, desc, status)
693
694 REPORT_TEST_NO_OUTPUT_TMPL = r"""
695 <tr id='%(tid)s' class='%(Class)s'>
696 <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
697 <td colspan='5' align='center'>%(status)s</td>
698 <td>%(img)s</td>
699 </tr>
700 """ # variables: (tid, Class, style, desc, status)
701
702 REPORT_TEST_OUTPUT_TMPL = r"""
703 %(id)s: %(output)s
704 """ # variables: (id, output)
705
706 IMG_TMPL = r"""
707 <a onfocus='this.blur();' href="javacript:void(0);" onclick="show_img(this)">show</a>
708 <div align="center" class="screenshots" style="display:none">
709 <a class="close_shots" onclick="hide_img(this)"></a>
710 {imgs}
711 <div class="imgyuan"></div>
712 </div>
713 """
714 # ------------------------------------------------------------------------
715 # ENDING
716 #
717
718 ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""
719
720
721 # -------------------- The end of the Template class -------------------
722
723
724 TestResult = unittest.TestResult
725
726
727 class _TestResult(TestResult):
728 # note: _TestResult is a pure representation of results.
729 # It lacks the output and reporting ability compares to unittest._TextTestResult.
730
731 def __init__(self, verbosity=1, rerun=0, save_last_run=False):
732 TestResult.__init__(self)
733 self.stdout0 = None
734 self.stderr0 = None
735 self.success_count = 0
736 self.failure_count = 0
737 self.error_count = 0
738 self.skip_count = 0
739 self.verbosity = verbosity
740 self.rerun = rerun
741 self.save_last_run = save_last_run
742 self.status = 0
743 self.runs = 0
744 self.result = []
745
746 def startTest(self, test):
747 test.imgs = getattr(test, "imgs", [])
748 # TestResult.startTest(self, test)
749 # just one buffer for both stdout and stderr
750 self.outputBuffer = io.StringIO()
751 stdout_redirector.fp = self.outputBuffer
752 stderr_redirector.fp = self.outputBuffer
753 self.stdout0 = sys.stdout
754 self.stderr0 = sys.stderr
755 sys.stdout = stdout_redirector
756 sys.stderr = stderr_redirector
757
758 def complete_output(self):
759 """
760 Disconnect output redirection and return buffer.
761 Safe to call multiple times.
762 """
763 if self.stdout0:
764 sys.stdout = self.stdout0
765 sys.stderr = self.stderr0
766 self.stdout0 = None
767 self.stderr0 = None
768 return self.outputBuffer.getvalue()
769
770 def stopTest(self, test):
771 # Usually one of addSuccess, addError or addFailure would have been called.
772 # But there are some path in unittest that would bypass this.
773 # We must disconnect stdout in stopTest(), which is guaranteed to be called.
774 if self.rerun and self.rerun >= 1:
775 if self.status == 1:
776 self.runs += 1
777 if self.runs <= self.rerun:
778 if self.save_last_run:
779 t = self.result.pop(-1)
780 if t[0] == 1:
781 self.failure_count -= 1
782 else:
783 self.error_count -= 1
784 test = copy.copy(test)
785 sys.stderr.write("Retesting... ")
786 sys.stderr.write(str(test))
787 sys.stderr.write('..%d \n' % self.runs)
788 doc = getattr(test, '_testMethodDoc', u"") or u''
789 if doc.find('->rerun') != -1:
790 doc = doc[:doc.find('->rerun')]
791 desc = "%s->rerun:%d" % (doc, self.runs)
792 if isinstance(desc, str):
793 desc = desc
794 test._testMethodDoc = desc
795 test(self)
796 else:
797 self.status = 0
798 self.runs = 0
799 self.complete_output()
800
801 def addSuccess(self, test):
802 self.success_count += 1
803 self.status = 0
804 TestResult.addSuccess(self, test)
805 output = self.complete_output()
806 self.result.append((0, test, output, ''))
807 if self.verbosity > 1:
808 sys.stderr.write('ok ')
809 sys.stderr.write(str(test))
810 sys.stderr.write('\n')
811 else:
812 sys.stderr.write('.' + str(self.success_count))
813
814 def addError(self, test, err):
815 self.error_count += 1
816 self.status = 1
817 TestResult.addError(self, test, err)
818 _, _exc_str = self.errors[-1]
819 output = self.complete_output()
820 self.result.append((2, test, output, _exc_str))
821 if not getattr(test, "driver", ""):
822 pass
823 else:
824 try:
825 driver = getattr(test, "driver")
826 test.imgs.append(driver.get_screenshot_as_base64())
827 except BaseException:
828 pass
829 if self.verbosity > 1:
830 sys.stderr.write('E ')
831 sys.stderr.write(str(test))
832 sys.stderr.write('\n')
833 else:
834 sys.stderr.write('E')
835
836 def addFailure(self, test, err):
837 self.failure_count += 1
838 self.status = 1
839 TestResult.addFailure(self, test, err)
840 _, _exc_str = self.failures[-1]
841 output = self.complete_output()
842 self.result.append((1, test, output, _exc_str))
843 if not getattr(test, "driver", ""):
844 pass
845 else:
846 try:
847 driver = getattr(test, "driver")
848 test.imgs.append(driver.get_screenshot_as_base64())
849 except BaseException:
850 pass
851 if self.verbosity > 1:
852 sys.stderr.write('F ')
853 sys.stderr.write(str(test))
854 sys.stderr.write('\n')
855 else:
856 sys.stderr.write('F')
857
858 def addSkip(self, test, reason):
859 self.skip_count += 1
860 self.status = 0
861 TestResult.addSkip(self, test, reason)
862 output = self.complete_output()
863 self.result.append((3, test, output, reason))
864 if self.verbosity > 1:
865 sys.stderr.write('S')
866 sys.stderr.write(str(test))
867 sys.stderr.write('\n')
868 else:
869 sys.stderr.write('S')
870
871
872 class HTMLTestRunner(Template_mixin):
873 """
874 """
875
876 def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None, save_last_run=True):
877 self.stream = stream
878 self.verbosity = verbosity
879 self.save_last_run = save_last_run
880 self.run_times = 0
881 if title is None:
882 self.title = self.DEFAULT_TITLE
883 else:
884 self.title = title
885 if description is None:
886 self.description = self.DEFAULT_DESCRIPTION
887 else:
888 self.description = description
889
890 self.startTime = datetime.datetime.now()
891
892 def run(self, test, rerun=0, save_last_run=False):
893 """Run the given test case or test suite."""
894 result = _TestResult(self.verbosity, rerun=rerun, save_last_run=save_last_run)
895 test(result)
896 self.stopTime = datetime.datetime.now()
897 self.run_times += 1
898 self.generateReport(test, result)
899 return result
900
901 def sortResult(self, result_list):
902 # unittest does not seems to run in any particular order.
903 # Here at least we want to group them together by class.
904 rmap = {}
905 classes = []
906 for n, t, o, e in result_list:
907 cls = t.__class__
908 if not cls in rmap:
909 rmap[cls] = []
910 classes.append(cls)
911 rmap[cls].append((n, t, o, e))
912 r = [(cls, rmap[cls]) for cls in classes]
913 return r
914
915 def getReportAttributes(self, result):
916 """
917 Return report attributes as a list of (name, value).
918 Override this to add custom attributes.
919 """
920 startTime = str(self.startTime)[:19]
921 duration = str(self.stopTime - self.startTime)
922 status = []
923 if result.success_count:
924 status.append('Passed:%s' % result.success_count)
925 if result.failure_count:
926 status.append('Failed:%s' % result.failure_count)
927 if result.error_count:
928 status.append('Errors:%s' % result.error_count)
929 if result.skip_count:
930 status.append('Skiped:%s' % result.skip_count)
931 if status:
932 status = ' '.join(status)
933 else:
934 status = 'none'
935 result = {
936 "pass": result.success_count,
937 "fail": result.failure_count,
938 "error": result.error_count,
939 "skip": result.skip_count,
940 }
941 return [
942 ('Start Time', startTime),
943 ('Duration', duration),
944 ('Status', status),
945 ('Result', result),
946 ]
947
948 def generateReport(self, test, result):
949 report_attrs = self.getReportAttributes(result)
950 generator = 'HTMLTestRunner %s' % __version__
951 stylesheet = self._generate_stylesheet()
952 heading = self._generate_heading(report_attrs)
953 report = self._generate_report(result)
954 ending = self._generate_ending()
955 chart = self._generate_chart(result)
956 output = self.HTML_TMPL % dict(
957 title=saxutils.escape(self.title),
958 generator=generator,
959 stylesheet=stylesheet,
960 heading=heading,
961 report=report,
962 ending=ending,
963 chart_script=chart,
964 channel=self.run_times,
965 )
966 self.stream.write(output.encode('utf8'))
967
968 def _generate_stylesheet(self):
969 return self.STYLESHEET_TMPL
970
971 def _generate_heading(self, report_attrs):
972 a_lines = []
973 for name, value in report_attrs:
974 result = {}
975 if name == "Result":
976 result = value
977 else:
978 line = self.HEADING_ATTRIBUTE_TMPL % dict(
979 name=saxutils.escape(name),
980 value=saxutils.escape(value),
981 )
982 a_lines.append(line)
983 heading = self.HEADING_TMPL % dict(
984 title=saxutils.escape(self.title),
985 parameters=''.join(a_lines),
986 description=saxutils.escape(self.description),
987 pass_count=saxutils.escape(str(result["pass"])),
988 fail_count=saxutils.escape(str(result["fail"])),
989 error_count=saxutils.escape(str(result["error"])),
990 skip_count=saxutils.escape(str(result["skip"])),
991 )
992 return heading
993
994 def _generate_report(self, result):
995 rows = []
996 sortedResult = self.sortResult(result.result)
997 for cid, (cls, cls_results) in enumerate(sortedResult):
998 # subtotal for a class
999 np = nf = ne = ns = 0
1000 for n, t, o, e in cls_results:
1001 if n == 0:
1002 np += 1
1003 elif n == 1:
1004 nf += 1
1005 elif n == 2:
1006 ne += 1
1007 else:
1008 ns += 1
1009
1010 # format class description
1011 if cls.__module__ == "__main__":
1012 name = cls.__name__
1013 else:
1014 name = "%s.%s" % (cls.__module__, cls.__name__)
1015 doc = cls.__doc__ or ""
1016 desc = doc and '%s: %s' % (name, doc) or name
1017
1018 row = self.REPORT_CLASS_TMPL % dict(
1019 style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
1020 desc=desc,
1021 count=np + nf + ne,
1022 Pass=np,
1023 fail=nf,
1024 error=ne,
1025 cid='c%s.%s' % (self.run_times, cid + 1),
1026 )
1027 rows.append(row)
1028
1029 for tid, (n, t, o, e) in enumerate(cls_results):
1030 print("o", o)
1031 self._generate_report_test(rows, cid, tid, n, t, o, e)
1032
1033 report = self.REPORT_TMPL % dict(
1034 test_list=''.join(rows),
1035 count=str(result.success_count + result.failure_count + result.error_count),
1036 Pass=str(result.success_count),
1037 fail=str(result.failure_count),
1038 error=str(result.error_count),
1039 skip=str(result.skip_count),
1040 total=str(result.success_count + result.failure_count + result.error_count),
1041 channel=str(self.run_times),
1042 )
1043 return report
1044
1045 def _generate_chart(self, result):
1046 chart = self.ECHARTS_SCRIPT % dict(
1047 Pass=str(result.success_count),
1048 fail=str(result.failure_count),
1049 error=str(result.error_count),
1050 skip=str(result.skip_count),
1051 )
1052 return chart
1053
1054 def _generate_report_test(self, rows, cid, tid, n, t, o, e):
1055 # e.g. 'pt1.1', 'ft1.1','et1.1', 'st1.1' etc
1056 has_output = bool(o or e)
1057 if n == 0:
1058 tmp = "p"
1059 elif n == 1:
1060 tmp = "f"
1061 elif n == 2:
1062 tmp = "e"
1063 else:
1064 tmp = "s"
1065 tid = tmp + 't%d.%d.%d' % (self.run_times, cid + 1, tid + 1)
1066 # tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid + 1, tid + 1)
1067 name = t.id().split('.')[-1]
1068 doc = t.shortDescription() or ""
1069 desc = doc and ('%s: %s' % (name, doc)) or name
1070 tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
1071
1072 # o and e should be byte string because they are collected from stdout and stderr?
1073 if isinstance(o, str):
1074 # TODO: some problem with 'string_escape': it escape \n and mess up formating
1075 # uo = unicode(o.encode('string_escape'))
1076 uo = o
1077 else:
1078 uo = o
1079 if isinstance(e, str):
1080 # TODO: some problem with 'string_escape': it escape \n and mess up formating
1081 # ue = unicode(e.encode('string_escape'))
1082 ue = e
1083 else:
1084 ue = e
1085
1086 script = self.REPORT_TEST_OUTPUT_TMPL % dict(
1087 id=tid,
1088 output=saxutils.escape(uo + ue),
1089 )
1090 if getattr(t, 'imgs', []):
1091 # 判断截图列表,如果有则追加
1092 tmp = ""
1093 for i, img in enumerate(t.imgs):
1094 if i == 0:
1095 tmp += """<img src="data:image/jpg;base64,{}" style="display: block;" class="img"/>\n""".format(img)
1096 else:
1097 tmp += """<img src="data:image/jpg;base64,{}" style="display: none;" class="img"/>\n""".format(img)
1098 screenshots_html = self.IMG_TMPL.format(imgs=tmp)
1099 else:
1100 screenshots_html = """"""
1101
1102 row = tmpl % dict(
1103 tid=tid,
1104 Class=(n == 0 and 'hiddenRow' or 'none'),
1105 style=n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase'),
1106 desc=desc,
1107 script=script,
1108 status=self.STATUS[n],
1109 img=screenshots_html
1110 )
1111 rows.append(row)
1112 if not has_output:
1113 return
1114
1115 def _generate_ending(self):
1116 return self.ENDING_TMPL
1117
1118
1119 ##############################################################################
1120 # Facilities for running tests from the command line
1121 ##############################################################################
1122
1123 # Note: Reuse unittest.TestProgram to launch test. In the future we may
1124 # build our own launcher to support more specific command line
1125 # parameters like test title, CSS, etc.
1126 class TestProgram(unittest.TestProgram):
1127 """
1128 A variation of the unittest.TestProgram. Please refer to the base
1129 class for command line parameters.
1130 """
1131
1132 def runTests(self):
1133 # Pick HTMLTestRunner as the default test runner.
1134 # base class's testRunner parameter is not useful because it means
1135 # we have to instantiate HTMLTestRunner before we know self.verbosity.
1136 if self.testRunner is None:
1137 self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
1138 unittest.TestProgram.runTests(self)
1139
1140
1141 main = TestProgram
1142
1143 ##############################################################################
1144 # Executing this module from the command line
1145 ##############################################################################
1146
1147 if __name__ == "__main__":
1148 main(module=None)

测试报告模板:HTMLTestRunner.py(新版)的更多相关文章

  1. python使用 HTMLTestRunner.py生成测试报告

    HTMLTestRunner.py python 2版本 下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html 使用时,先建立一个”PyDe ...

  2. python - HTMLTestRunner 测试报告模板设置

    python - HTMLTestRunner 测试报告模板设置 优化模板下载地址: http://download.csdn.net/download/chinayyj2010/10039097   ...

  3. Python+Selenium----使用HTMLTestRunner.py生成自动化测试报告2(使用PyCharm )

    1.说明 在我前一篇文件(Python+Selenium----使用HTMLTestRunner.py生成自动化测试报告1(使用IDLE ))中简单的写明了,如何生产测试报告,但是使用IDLE很麻烦, ...

  4. Python+Selenium----使用HTMLTestRunner.py生成自动化测试报告1(使用IDLE)

    1.说明 自动化测试报告是一个很重要的测试数据,网上看了一下,使用HTMLTestRunner.py生成自动化测试报告使用的比较多,但是呢,小白刚刚入手,不太懂,看了很多博客,终于生成了一个测试报告, ...

  5. python使用HTMLTestRunner.py生成测试报告

    这里我使用的是python selenium webdriver环境,浏览器驱动安装见selenium 1.下载HTMLTestRunner.py:http://tungwaiyip.info/sof ...

  6. python3-如何正常使用HTMLTestRunner.py,生成自动化测试报告

    其实HTMLTestRunner.py是基于python2开发的,为了使其支持python3环境,需要对其的部分内容进行修改.下面我们通过编辑器打开HTMLTestRunner.py文件(编辑器可以选 ...

  7. Python3+HTMLTestRunner生成html测试报告时报错HTMLTestRunner.py line 687, in generateReport  self.stream.write(output.encode('utf8'))

    1.测试环境及场景: Python3.5+unittest+HTMLTestRunner 生成html报告时候报错 2.报错内容: ..Traceback (most recent call last ...

  8. 解决python3.7无法使用HTMLTestRunner.py生成html测试报告的问题2019.04

    **一:首先下载这个HTMLTestRunner.py文件:链接: https://pan.baidu.com/s/1jQFsMYLM3ysY6shgRF40Kw 提取码: evq2二:把该文件放在p ...

  9. python3中用HTMLTestRunner.py报ImportError: No module named 'StringIO'如何解决

    python3中用HTMLTestRunner.py报ImportError: No module named 'StringIO'的解决方法: 1.原因是官网的是python2语法写的,看官手动把官 ...

随机推荐

  1. oracle 导入导出dmp

    exp 用户名/密码@地址:端口/serviceName file=D:\710.dmp exp test710/test710@192.168.15.134:1521/doit file=D:\71 ...

  2. 痞子衡嵌入式:系统时钟配置不当会导致i.MXRT1xxx系列下OTFAD加密启动失败

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是系统时钟配置不当会导致i.MXRT1xxx系列下OTFAD加密启动失败问题. 我们知道,i.MXRT1xxx家族早期型号(RT1050/ ...

  3. PID算法验证

    算法: struct PID { float kp; float kpnfac; float ki; float kinfac; float kd; }; float gCurPPM = 1300; ...

  4. wxWidgets源码分析(8) - MVC架构

    目录 MVC架构 wxDocManager文档管理器 模板类创建文档对象 视图对象的创建 创建顺序 框架菜单命令的执行过程 wxDocParentFrame菜单入口 wxDocManager类的处理 ...

  5. C++多文件结构和预编译命令

    下面随笔将给出C++多文件结构和预编译命令细节. 多文件结构和编译预处理命令 c++程序的一般组织结构 一个工程可以划分多个源文件 类声明文件(.h文件) 类实现文件(.cpp文件) 类的使用文件(m ...

  6. jQuery实现游戏推荐

    1.需求:点击添加游戏按钮实现添加游戏,点击删除按钮,删除游戏. 2.实现思路:分别给添加按钮和删除按钮添加click事件. 3.遇到的问题:自己添加的游戏不能进行删除. 4.原因分析:文档加载完毕后 ...

  7. 剑指 Offer 04. 二维数组中的查找 (思维)

    剑指 Offer 04. 二维数组中的查找 题目链接 本题的解法是从矩阵的右上角开始寻找目标值. 根据矩阵的元素分布特性, 当目标值大于当前位置的值时将row行号++,因为此时目标值一定位于当前行的下 ...

  8. 再来认识一下 Java 序列化

    前言 在面试中,Java 序列化被问到的几率还是挺高的.所以搜集了 Java 序列化常见的问题,由浅入深的帮助大家进一步学习和理解. 序列化基础知识 什么是序列化? Java 序列化是 JDK 1.1 ...

  9. 如何用Flink把数据sink到kafka多个不同(成百上千)topic中

    需求与场景 上游某业务数据量特别大,进入到kafka一个topic中(当然了这个topic的partition数必然多,有人肯定疑问为什么非要把如此庞大的数据写入到1个topic里,历史留下的问题,现 ...

  10. Linux发行版及其目标用户

    1.Debian Debian 众所周知,是Deepin,Ubuntu和Mint等流行Linux发行版的母亲,这些发行版提供了可靠的性能,稳定性和无与伦比的用户体验.最新的稳定发行版是Debian 1 ...