Python3之HTMLTestRunner测试报告美化
  前⾯我们讲到过在做⾃动化测试或单元测试的时候使⽤HTMLTestRunner来⽣成测试报告,并且由于Python2 和 Python3 对于HTMLTestRunner的⽀持稍微有点差异,所以我们将HTMLTestRunner进⾏了改造,从⽽适配Python3,详细改造步骤可以参考:
  但是改造后的HTMLTestRunner⽣成的测试报告不是特别的美观,所以我⼜对HTMLTestRunner进⾏了进⼀步的改造,主要是做⼀些美化。
  美化之前的测试报告如下:
  美化之后的测试报告如下:
  从上⾯两个报告的对⽐来看,第⼆个测试报告是不是更为美观呢?
下⾯是我改造后的HTMLTestRunner源码:
1# -*- coding: utf-8 -*-
2
3"""
4A TestRunner for use with the Python unit testing framework. It
5generates a HTML report to show the result at a glance.
6
7The 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
18For more customization options, instantiates a HTMLTestRunner object.
19HTMLTestRunner 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------------------------------------------------------------------------
38Copyright (c) 2004-2007, Wai Yip Tung
39All rights reserved.
40
41Redistribution and use in source and binary forms, with or without
42modification, are permitted provided that the following conditions are
43met:
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
54THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
55IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
56TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
57PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
58OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
59EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
60PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
61PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
62LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
63NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65"""
66
67# URL: tungwaiyip.info/software/HTMLTestRunner.html
68
69__author__ = "Wai Yip Tung"
70__version__ = "0.8.2.3"
71
72
73"""
74Change History
75Version 0.8.2.1 -Findyou
76* 改为⽀持python3
77
78Version 0.8.2.1 -Findyou
79* ⽀持中⽂,
80* 调整样式,美化(需要连⼊⽹络,使⽤的百度的Bootstrap.js)
81* 增加通过分类显⽰、测试⼈员、通过率的展⽰
82* 优化“详细”与“收起”状态的变换
83* 增加返回顶部的锚点
84
85Version 0.8.2
86* Show output inline instead of popup window (Viorel Lupu).
87
88Version in 0.8.1
89* Validated XHTML (Wolfgang Borgert).
90* Added description of test classes and test cases.
91
92Version 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
96Version 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
104import datetime
105import io
106import sys
107import time
108import unittest
109from xml.sax import saxutils
110import 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
123class OutputRedirector(object):
124""" Wrapper to redirect stdout or stderr """
125def__init__(self, fp):
126        self.fp = fp
127
128def write(self, s):
129        self.fp.write(s)
130
131def writelines(self, lines):
132        self.fp.writelines(lines)
133
134def flush(self):
135        self.fp.flush()
136
137 stdout_redirector = OutputRedirector(sys.stdout)
138 stderr_redirector = OutputRedirector(sys.stderr)
139
140# ----------------------------------------------------------------------
141# Template
142
143class 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" "/TR/xhtml1/DTD/xhtml1-strict.dtd"> 199<html xmlns="/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="libs.baidu/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
205    <script src="libs.baidu/jquery/2.0.0/jquery.min.js"></script>
206    <script src="libs.baidu/bootstrap/3.0.3/js/bootstrap.min.js"></script>
207    %(stylesheet)s
208</head>
209<body >
210<script language="javascript" type="text/javascript">
211output_list = Array();
212
213/*level 调整增加只显⽰通过⽤例的分类 --Adil
2140:Summary //all hiddenRow
2151:Failed  //pt hiddenRow, ft none
2162:Pass    //pt none, ft hiddenRow
2173:Error  // pt hiddenRow, ft none
2184:All    //pt none, ft none
219下⾯设置按钮展开逻辑  --Yang Yao Jun
220*/
221function showCase(level) {
222    trs = ElementsByTagName("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_ElementsByClassName('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
259function 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);
266        tr = ElementById(tid);
267        if (!tr) {
268            tid = 'p' + tid0;
269            tr = ElementById(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            ElementById(tid).className = 'hiddenRow';
281            ElementById(cid).innerText = "详细"
282        }
283        else {
284            ElementById(tid).className = '';
285            ElementById(cid).innerText = "收起"
286        }
287    }
288}
289
290function 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">
315body        { font-family: Microsoft YaHei,Tahoma,arial,helvetica,sans-serif;padding: 20px; font-size: 80%; }
316table      { 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; }
334.hiddenRow  { display: none; }
336</style>
337"""
338
339# ------------------------------------------------------------------------
340# Heading
341#
342
343    HEADING_TMPL = """<div class='heading'>
344<h1 >%(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 按钮资料介绍详见:www.runoob/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" >
381    <td>⽤例集/测试⽤例</td>
382    <td>总计</td>
383    <td>通过</td>
384    <td>错误</td>
385    <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 >
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
458class _TestResult(TestResult):
459# note: _TestResult is a pure representation of results.
460# It lacks the output and reporting ability compares to unittest._TextTestResult.
461
462def__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        _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        sult = []
479#增加⼀个测试通过率 --Findyou
480        self.passrate=float(0)
481
482
483def 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
writelines使用方法python495def complete_output(self):
496"""
497        Disconnect output redirection and return buffer.
498        Safe to call multiple times.
499"""
500if self.stdout0:
501            sys.stdout = self.stdout0
502            sys.stderr = self.stderr0
503            self.stdout0 = None
504            self.stderr0 = None
505return value()
506

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。