Python3之HTMLTestRunner测试报告美化

  前面我们讲到过在做自动化测试或单元测试的时候使用HTMLTestRunner来生成测试报告,并且由于Python2 和 Python3 对于HTMLTestRunner的支持稍微有点差异,所以我们将HTMLTestRunner进行了改造,从而适配Python3,详细改造步骤可以参考:HTMLTestRunner修改成Python3版本

  但是改造后的HTMLTestRunner生成的测试报告不是特别的美观,所以我又对HTMLTestRunner进行了进一步的改造,主要是做一些美化。

  美化之前的测试报告如下:

  美化之后的测试报告如下:

  从上面两个报告的对比来看,第二个测试报告是不是更为美观呢?

下面是我改造后的HTMLTestRunner源码:

  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)

如何使用呢?

  使用方法:

    1.将上面的源码复制一份,将文件命名为HTMLTestRunner.py ,然后将HTMLTestRunner.py 文件放到Python安装目录的Lib目录下:

    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:指定需要执行的测试用例目录路径;

  HTMLTestRunner(stream=f, title=‘接口自动化测试报告‘, description=‘营销线接口‘):还可以传入tester=‘lwjnicole‘,tester表示测试人员;

  执行完成之后,就会在上面自定义report目录下生成指定格式文件名的html测试报告啦!

原文地址:https://www.cnblogs.com/lwjnicole/p/12339874.html

时间: 2024-11-08 00:05:08

Python3之HTMLTestRunner测试报告美化的相关文章

HTMLTestRunner测试报告美化

前言 ?最近小伙伴们在学玩python,,看着那HTMLTestRunner生成的测试报告,左右看不顺眼,终觉得太丑.搜索了一圈没有找到合适的美化报告,于是忍不住自已动手进行了修改,因习惯python 2.7,所以,在原作者HTMLTestRunner.py的Version 0.8.2上进行了自已的修改定制.有时大伙会觉得英语会有莫名的逼格(感叹,沉默...),所以做了CN与EN两个版本,满足一下不同人的需要. 一.修改前后对比 [项目地址] https://github.com/findyou

python3中用HTMLTestRunner.py报ImportError: No module named &#39;StringIO&#39;如何解决

python3中用HTMLTestRunner.py报ImportError: No module named 'StringIO'的解决方法: 1.原因是官网的是python2语法写的,看官手动把官网的HTMLTestRunner.py改成python3的语法: 参考:http://bbs.chinaunix.net/thread-4154743-1-1.html 下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html 修改后下载地址:

python3中用HTMLTestRunner.py报ImportError: No module named &#39;StringIO&#39;如何解决【转载】

原文转自:http://bbs.chinaunix.net/thread-4154743-1-1.html python3中用HTMLTestRunner.py报ImportError: No module named 'StringIO'的解决方法: 1.原因是官网的是python2语法写的,看官手动把官网的HTMLTestRunner.py改成python3的语法: 参考:http://bbs.chinaunix.net/thread-4154743-1-1.html 下载地址:http:/

python3中用HTMLTestRunner.py报ImportError: No module named &#39;StringIO&#39;的解决方法:

全文转载至:http://www.cnblogs.com/testyao/p/5658200.html python3中用HTMLTestRunner.py报ImportError: No module named 'StringIO'的解决方法: 1.原因是官网的是python2语法写的,看官手动把官网的HTMLTestRunner.py改成python3的语法: 参考:http://bbs.chinaunix.net/thread-4154743-1-1.html 下载地址:http://t

python3+unittest+HTMLTestRunner

参考博客1 参考博客2 python3版HTMLTestRunner.py见博客园'链接'(已经上传到博客园) import unittest class operatinon_unittest(unittest.TestCase): def setUp(self): print('每个用例之前执行setup') def tearDown(self): print('每个用例执行之后') def test_one(self): print('test_one函数') def test_two(s

转 生成 HTMLTestRunner 测试报告

转自:http://www.cnblogs.com/hero-blog/p/4128575.html 04.生成 HTMLTestRunner  测试报告 1.HTMLTestRunner 是 Python 标准库的 unittest 模块的一个扩展.它生成易于使用的 HTML 测试报告 1>下载HTMLTestRunner.py文件,地址为: http://tungwaiyip.info/software/HTMLTestRunner.html Windows平台: 将下载的文件放入...\P

HTMLTestRunner测试报告中文乱码问题解决

在学习python selenium自动化测试学习中遇到HTMLTestRunner测试报告出现乱码的问题 Test Group/Test case Count Pass Fail Error View baidu.Baidu 1 1 0 0 Detail test_baidu_search: 百度搜索 pass [x] pt1.1: [Error 32] : 'c:\\users\\zhengb~1\\appdata\\local\\temp\\tmpmoado1\\AddonInstalle

Python 同一文件中,有unittest不执行“if __name__ == &#39;__main__”,不生成HTMLTestRunner测试报告的解决方案

1.问题:Python中同一个.py文件中同时用unittest框架和HtmlReport框架后,HtmlReport不被执行. 2.为什么?其实不是HtmlReport不被执行,也不是HtmlReport不生成测试报告,是因为if __name__ == '__main__'中的代码根本没执行好嘛! 3.解决方案的来源:因为最开始我的main代码中没有写print打印语句.没有生成HTML报告,我也在网上找了很久的方法,后来才怀疑是不是没有运行main方法,于是写了个print语句,果然没有运

Python3和HTMLTestRunner生成html测试报告

1.测试环境:Python3.5+unittest+HTMLTestRunner 2.下载HTMLTestRunner.py文件 下载地址 http://tungwaiyip.info/software/HTMLTestRunner.html 右键单击保存至本地,放在Python的安装目录Lib文件夹下(我本地放在本目录下:D:\setup\Python35\Lib) 3.由于下载的HTMLTestRunner.py文件是基于Python2的,若适用于Python3则进行如下修改: 第94行,将