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小时内删除。
发表评论