unittest 报告——HTMLTestRunner+代码覆盖率

1. HTMLTestRunner.py 代码(python3)如下:

python2:  https://github.com/tungwaiyip/HTMLTestRunner

  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 The simplest way to use this is to invoke its main method. E.g.
  6 
  7     import unittest
  8     import HTMLTestRunner
  9 
 10     ... define your tests ...
 11 
 12     if __name__ == '__main__':
 13         HTMLTestRunner.main()
 14 
 15 
 16 For more customization options, instantiates a HTMLTestRunner object.
 17 HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
 18 
 19     # output to a file
 20     fp = file('my_report.html', 'wb')
 21     runner = HTMLTestRunner.HTMLTestRunner(
 22                 stream=fp,
 23                 title='My unit test',
 24                 description='This demonstrates the report output by HTMLTestRunner.'
 25                 )
 26 
 27     # Use an external stylesheet.
 28     # See the Template_mixin class for more customizable options
 29     runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
 30 
 31     # run the test
 32     runner.run(my_test_suite)
 33 
 34 
 35 ------------------------------------------------------------------------
 36 Copyright (c) 2004-2007, Wai Yip Tung
 37 All rights reserved.
 38 
 39 Redistribution and use in source and binary forms, with or without
 40 modification, are permitted provided that the following conditions are
 41 met:
 42 
 43 * Redistributions of source code must retain the above copyright notice,
 44   this list of conditions and the following disclaimer.
 45 * Redistributions in binary form must reproduce the above copyright
 46   notice, this list of conditions and the following disclaimer in the
 47   documentation and/or other materials provided with the distribution.
 48 * Neither the name Wai Yip Tung nor the names of its contributors may be
 49   used to endorse or promote products derived from this software without
 50   specific prior written permission.
 51 
 52 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 53 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 54 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 55 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 56 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 57 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 58 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 59 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 60 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 61 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 62 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 63 """
 64 
 65 # URL: http://tungwaiyip.info/software/HTMLTestRunner.html
 66 
 67 __author__ = "Wai Yip Tung"
 68 __version__ = "0.8.3"
 69 
 70 
 71 """
 72 Change History
 73 
 74 Version 0.8.3
 75 * Prevent crash on class or module-level exceptions (Darren Wurf).
 76 
 77 Version 0.8.2
 78 * Show output inline instead of popup window (Viorel Lupu).
 79 
 80 Version in 0.8.1
 81 * Validated XHTML (Wolfgang Borgert).
 82 * Added description of test classes and test cases.
 83 
 84 Version in 0.8.0
 85 * Define Template_mixin class for customization.
 86 * Workaround a IE 6 bug that it does not treat <script> block as CDATA.
 87 
 88 Version in 0.7.1
 89 * Back port to Python 2.3 (Frank Horowitz).
 90 * Fix missing scroll bars in detail log (Podi).
 91 """
 92 
 93 # TODO: color stderr
 94 # TODO: simplify javascript using ,ore than 1 class in the class attribute?
 95 
 96 import datetime
 97 import io
 98 import sys
 99 import time
100 import unittest
101 from xml.sax import saxutils
102 
103 
104 # ------------------------------------------------------------------------
105 # The redirectors below are used to capture output during testing. Output
106 # sent to sys.stdout and sys.stderr are automatically captured. However
107 # in some cases sys.stdout is already cached before HTMLTestRunner is
108 # invoked (e.g. calling logging.basicConfig). In order to capture those
109 # output, use the redirectors for the cached stream.
110 #
111 # e.g.
112 #   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
113 #   >>>
114 
115 def to_unicode(s):
116     try:
117         return str(s)
118     except UnicodeDecodeError:
119         # s is non ascii byte string
120         return s.decode('unicode_escape')
121 
122 class OutputRedirector(object):
123     """ Wrapper to redirect stdout or stderr """
124     def __init__(self, fp):
125         self.fp = fp
126 
127     def write(self, s):
128         self.fp.write(to_unicode(s))
129 
130     def writelines(self, lines):
131         lines = map(to_unicode, 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 
142 # ----------------------------------------------------------------------
143 # Template
144 
145 class Template_mixin(object):
146     """
147     Define a HTML template for report customerization and generation.
148 
149     Overall structure of an HTML report
150 
151     HTML
152     +------------------------+
153     |<html>                  |
154     |  <head>                |
155     |                        |
156     |   STYLESHEET           |
157     |   +----------------+   |
158     |   |                |   |
159     |   +----------------+   |
160     |                        |
161     |  </head>               |
162     |                        |
163     |  <body>                |
164     |                        |
165     |   HEADING              |
166     |   +----------------+   |
167     |   |                |   |
168     |   +----------------+   |
169     |                        |
170     |   REPORT               |
171     |   +----------------+   |
172     |   |                |   |
173     |   +----------------+   |
174     |                        |
175     |   ENDING               |
176     |   +----------------+   |
177     |   |                |   |
178     |   +----------------+   |
179     |                        |
180     |  </body>               |
181     |</html>                 |
182     +------------------------+
183     """
184 
185     STATUS = {
186     0: 'pass',
187     1: 'fail',
188     2: 'error',
189     }
190 
191     DEFAULT_TITLE = 'Unit Test Report'
192     DEFAULT_DESCRIPTION = ''
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     %(stylesheet)s
205 </head>
206 <body>
207 <script language="javascript" type="text/javascript"><!--
208 output_list = Array();
209 
210 /* level - 0:Summary; 1:Failed; 2:All */
211 function showCase(level) {
212     trs = document.getElementsByTagName("tr");
213     for (var i = 0; i < trs.length; i++) {
214         tr = trs[i];
215         id = tr.id;
216         if (id.substr(0,2) == 'ft') {
217             if (level < 1) {
218                 tr.className = 'hiddenRow';
219             }
220             else {
221                 tr.className = '';
222             }
223         }
224         if (id.substr(0,2) == 'pt') {
225             if (level > 1) {
226                 tr.className = '';
227             }
228             else {
229                 tr.className = 'hiddenRow';
230             }
231         }
232     }
233 }
234 
235 
236 function showClassDetail(cid, count) {
237     var id_list = Array(count);
238     var toHide = 1;
239     for (var i = 0; i < count; i++) {
240         tid0 = 't' + cid.substr(1) + '.' + (i+1);
241         tid = 'f' + tid0;
242         tr = document.getElementById(tid);
243         if (!tr) {
244             tid = 'p' + tid0;
245             tr = document.getElementById(tid);
246         }
247         id_list[i] = tid;
248         if (tr.className) {
249             toHide = 0;
250         }
251     }
252     for (var i = 0; i < count; i++) {
253         tid = id_list[i];
254         if (toHide) {
255             document.getElementById('div_'+tid).style.display = 'none'
256             document.getElementById(tid).className = 'hiddenRow';
257         }
258         else {
259             document.getElementById(tid).className = '';
260         }
261     }
262 }
263 
264 
265 function showTestDetail(div_id){
266     var details_div = document.getElementById(div_id)
267     var displayState = details_div.style.display
268     // alert(displayState)
269     if (displayState != 'block' ) {
270         displayState = 'block'
271         details_div.style.display = 'block'
272     }
273     else {
274         details_div.style.display = 'none'
275     }
276 }
277 
278 
279 function html_escape(s) {
280     s = s.replace(/&/g,'&amp;');
281     s = s.replace(/</g,'&lt;');
282     s = s.replace(/>/g,'&gt;');
283     return s;
284 }
285 
286 /* obsoleted by detail in <div>
287 function showOutput(id, name) {
288     var w = window.open("", //url
289                     name,
290                     "resizable,scrollbars,status,width=800,height=450");
291     d = w.document;
292     d.write("<pre>");
293     d.write(html_escape(output_list[id]));
294     d.write("\n");
295     d.write("<a href='javascript:window.close()'>close</a>\n");
296     d.write("</pre>\n");
297     d.close();
298 }
299 */
300 --></script>
301 
302 %(heading)s
303 %(report)s
304 %(ending)s
305 
306 </body>
307 </html>
308 """
309     # variables: (title, generator, stylesheet, heading, report, ending)
310 
311 
312     # ------------------------------------------------------------------------
313     # Stylesheet
314     #
315     # alternatively use a <link> for external style sheet, e.g.
316     #   <link rel="stylesheet" href="$url" type="text/css">
317 
318     STYLESHEET_TMPL = """
319 <style type="text/css" media="screen">
320 body        { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
321 table       { font-size: 100%; }
322 pre         { }
323 
324 /* -- heading ---------------------------------------------------------------------- */
325 h1 {
326     font-size: 16pt;
327     color: gray;
328 }
329 .heading {
330     margin-top: 0ex;
331     margin-bottom: 1ex;
332 }
333 
334 .heading .attribute {
335     margin-top: 1ex;
336     margin-bottom: 0;
337 }
338 
339 .heading .description {
340     margin-top: 4ex;
341     margin-bottom: 6ex;
342 }
343 
344 /* -- css div popup ------------------------------------------------------------------------ */
345 a.popup_link {
346 }
347 
348 a.popup_link:hover {
349     color: red;
350 }
351 
352 .popup_window {
353     display: none;
354     position: relative;
355     left: 0px;
356     top: 0px;
357     /*border: solid #627173 1px; */
358     padding: 10px;
359     background-color: #E6E6D6;
360     font-family: "Lucida Console", "Courier New", Courier, monospace;
361     text-align: left;
362     font-size: 8pt;
363     width: 500px;
364 }
365 
366 }
367 /* -- report ------------------------------------------------------------------------ */
368 #show_detail_line {
369     margin-top: 3ex;
370     margin-bottom: 1ex;
371 }
372 #result_table {
373     width: 80%;
374     border-collapse: collapse;
375     border: 1px solid #777;
376 }
377 #header_row {
378     font-weight: bold;
379     color: white;
380     background-color: #777;
381 }
382 #result_table td {
383     border: 1px solid #777;
384     padding: 2px;
385 }
386 #total_row  { font-weight: bold; }
387 .passClass  { background-color: #6c6; }
388 .failClass  { background-color: #c60; }
389 .errorClass { background-color: #c00; }
390 .passCase   { color: #6c6; }
391 .failCase   { color: #c60; font-weight: bold; }
392 .errorCase  { color: #c00; font-weight: bold; }
393 .hiddenRow  { display: none; }
394 .testcase   { margin-left: 2em; }
395 
396 
397 /* -- ending ---------------------------------------------------------------------- */
398 #ending {
399 }
400 
401 </style>
402 """
403 
404 
405 
406     # ------------------------------------------------------------------------
407     # Heading
408     #
409 
410     HEADING_TMPL = """<div class='heading'>
411 <h1>%(title)s</h1>
412 %(parameters)s
413 <p class='description'>%(description)s</p>
414 </div>
415 
416 """ # variables: (title, parameters, description)
417 
418     HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
419 """ # variables: (name, value)
420 
421 
422 
423     # ------------------------------------------------------------------------
424     # Report
425     #
426 
427     REPORT_TMPL = """
428 <p id='show_detail_line'>Show
429 <a href='javascript:showCase(0)'>Summary</a>
430 <a href='javascript:showCase(1)'>Failed</a>
431 <a href='javascript:showCase(2)'>All</a>
432 </p>
433 <table id='result_table'>
434 <colgroup>
435 <col align='left' />
436 <col align='right' />
437 <col align='right' />
438 <col align='right' />
439 <col align='right' />
440 <col align='right' />
441 </colgroup>
442 <tr id='header_row'>
443     <td>Test Group/Test case</td>
444     <td>Count</td>
445     <td>Pass</td>
446     <td>Fail</td>
447     <td>Error</td>
448     <td>View</td>
449 </tr>
450 %(test_list)s
451 <tr id='total_row'>
452     <td>Total</td>
453     <td>%(count)s</td>
454     <td>%(Pass)s</td>
455     <td>%(fail)s</td>
456     <td>%(error)s</td>
457     <td>&nbsp;</td>
458 </tr>
459 </table>
460 """ # variables: (test_list, count, Pass, fail, error)
461 
462     REPORT_CLASS_TMPL = r"""
463 <tr class='%(style)s'>
464     <td>%(desc)s</td>
465     <td>%(count)s</td>
466     <td>%(Pass)s</td>
467     <td>%(fail)s</td>
468     <td>%(error)s</td>
469     <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
470 </tr>
471 """ # variables: (style, desc, count, Pass, fail, error, cid)
472 
473 
474     REPORT_TEST_WITH_OUTPUT_TMPL = r"""
475 <tr id='%(tid)s' class='%(Class)s'>
476     <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
477     <td colspan='5' align='center'>
478 
479     <!--css div popup start-->
480     <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
481         %(status)s</a>
482 
483     <div id='div_%(tid)s' class="popup_window">
484         <div style='text-align: right; color:red;cursor:pointer'>
485         <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
486            [x]</a>
487         </div>
488         <pre>
489         %(script)s
490         </pre>
491     </div>
492     <!--css div popup end-->
493 
494     </td>
495 </tr>
496 """ # variables: (tid, Class, style, desc, status)
497 
498 
499     REPORT_TEST_NO_OUTPUT_TMPL = r"""
500 <tr id='%(tid)s' class='%(Class)s'>
501     <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
502     <td colspan='5' align='center'>%(status)s</td>
503 </tr>
504 """ # variables: (tid, Class, style, desc, status)
505 
506 
507     REPORT_TEST_OUTPUT_TMPL = r"""
508 %(id)s: %(output)s
509 """ # variables: (id, output)
510 
511 
512 
513     # ------------------------------------------------------------------------
514     # ENDING
515     #
516 
517     ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""
518 
519 # -------------------- The end of the Template class -------------------
520 
521 
522 TestResult = unittest.TestResult
523 
524 class _TestResult(TestResult):
525     # note: _TestResult is a pure representation of results.
526     # It lacks the output and reporting ability compares to unittest._TextTestResult.
527 
528     def __init__(self, verbosity=1):
529         TestResult.__init__(self)
530         self.outputBuffer = io.StringIO()
531         self.stdout0 = None
532         self.stderr0 = None
533         self.success_count = 0
534         self.failure_count = 0
535         self.error_count = 0
536         self.verbosity = verbosity
537 
538         # result is a list of result in 4 tuple
539         # (
540         #   result code (0: success; 1: fail; 2: error),
541         #   TestCase object,
542         #   Test output (byte string),
543         #   stack trace,
544         # )
545         self.result = []
546 
547 
548     def startTest(self, test):
549         TestResult.startTest(self, test)
550         # just one buffer for both stdout and stderr
551         stdout_redirector.fp = self.outputBuffer
552         stderr_redirector.fp = self.outputBuffer
553         self.stdout0 = sys.stdout
554         self.stderr0 = sys.stderr
555         sys.stdout = stdout_redirector
556         sys.stderr = stderr_redirector
557 
558 
559     def complete_output(self):
560         """
561         Disconnect output redirection and return buffer.
562         Safe to call multiple times.
563         """
564         if self.stdout0:
565             sys.stdout = self.stdout0
566             sys.stderr = self.stderr0
567             self.stdout0 = None
568             self.stderr0 = None
569         return self.outputBuffer.getvalue()
570 
571 
572     def stopTest(self, test):
573         # Usually one of addSuccess, addError or addFailure would have been called.
574         # But there are some path in unittest that would bypass this.
575         # We must disconnect stdout in stopTest(), which is guaranteed to be called.
576         self.complete_output()
577 
578 
579     def addSuccess(self, test):
580         self.success_count += 1
581         TestResult.addSuccess(self, test)
582         output = self.complete_output()
583         self.result.append((0, test, output, ''))
584         if self.verbosity > 1:
585             sys.stderr.write('ok ')
586             sys.stderr.write(str(test))
587             sys.stderr.write('\n')
588         else:
589             sys.stderr.write('.')
590 
591     def addError(self, test, err):
592         self.error_count += 1
593         TestResult.addError(self, test, err)
594         _, _exc_str = self.errors[-1]
595         output = self.complete_output()
596         self.result.append((2, test, output, _exc_str))
597         if self.verbosity > 1:
598             sys.stderr.write('E  ')
599             sys.stderr.write(str(test))
600             sys.stderr.write('\n')
601         else:
602             sys.stderr.write('E')
603 
604     def addFailure(self, test, err):
605         self.failure_count += 1
606         TestResult.addFailure(self, test, err)
607         _, _exc_str = self.failures[-1]
608         output = self.complete_output()
609         self.result.append((1, test, output, _exc_str))
610         if self.verbosity > 1:
611             sys.stderr.write('F  ')
612             sys.stderr.write(str(test))
613             sys.stderr.write('\n')
614         else:
615             sys.stderr.write('F')
616 
617 
618 class HTMLTestRunner(Template_mixin):
619     """
620     """
621     def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
622         self.stream = stream
623         self.verbosity = verbosity
624         if title is None:
625             self.title = self.DEFAULT_TITLE
626         else:
627             self.title = title
628         if description is None:
629             self.description = self.DEFAULT_DESCRIPTION
630         else:
631             self.description = description
632 
633         self.startTime = datetime.datetime.now()
634 
635 
636     def run(self, test):
637         "Run the given test case or test suite."
638         result = _TestResult(self.verbosity)
639         test(result)
640         self.stopTime = datetime.datetime.now()
641         self.generateReport(test, result)
642         print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime - self.startTime))
643         # print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)
644         return result
645 
646 
647     def sortResult(self, result_list):
648         # unittest does not seems to run in any particular order.
649         # Here at least we want to group them together by class.
650         rmap = {}
651         classes = []
652         for n,t,o,e in result_list:
653             cls = t.__class__
654             if not cls in rmap:
655                 rmap[cls] = []
656                 classes.append(cls)
657             rmap[cls].append((n,t,o,e))
658         r = [(cls, rmap[cls]) for cls in classes]
659         return r
660 
661 
662     def getReportAttributes(self, result):
663         """
664         Return report attributes as a list of (name, value).
665         Override this to add custom attributes.
666         """
667         startTime = str(self.startTime)[:19]
668         duration = str(self.stopTime - self.startTime)
669         status = []
670         if result.success_count: status.append('Pass %s'    % result.success_count)
671         if result.failure_count: status.append('Failure %s' % result.failure_count)
672         if result.error_count:   status.append('Error %s'   % result.error_count  )
673         if status:
674             status = ' '.join(status)
675         else:
676             status = 'none'
677         return [
678             ('Start Time', startTime),
679             ('Duration', duration),
680             ('Status', status),
681         ]
682 
683 
684     def generateReport(self, test, result):
685         report_attrs = self.getReportAttributes(result)
686         generator = 'HTMLTestRunner %s' % __version__
687         stylesheet = self._generate_stylesheet()
688         heading = self._generate_heading(report_attrs)
689         report = self._generate_report(result)
690         ending = self._generate_ending()
691         output = self.HTML_TMPL % dict(
692             title = saxutils.escape(self.title),
693             generator = generator,
694             stylesheet = stylesheet,
695             heading = heading,
696             report = report,
697             ending = ending,
698         )
699         self.stream.write(output.encode('utf8'))
700 
701 
702     def _generate_stylesheet(self):
703         return self.STYLESHEET_TMPL
704 
705 
706     def _generate_heading(self, report_attrs):
707         a_lines = []
708         for name, value in report_attrs:
709             line = self.HEADING_ATTRIBUTE_TMPL % dict(
710                     name = saxutils.escape(name),
711                     value = saxutils.escape(value),
712                 )
713             a_lines.append(line)
714         heading = self.HEADING_TMPL % dict(
715             title = saxutils.escape(self.title),
716             parameters = ''.join(a_lines),
717             description = saxutils.escape(self.description),
718         )
719         return heading
720 
721 
722     def _generate_report(self, result):
723         rows = []
724         sortedResult = self.sortResult(result.result)
725         for cid, (cls, cls_results) in enumerate(sortedResult):
726             # subtotal for a class
727             np = nf = ne = 0
728             for n,t,o,e in cls_results:
729                 if n == 0: np += 1
730                 elif n == 1: nf += 1
731                 else: ne += 1
732 
733             # format class description
734             if cls.__module__ == "__main__":
735                 name = cls.__name__
736             else:
737                 name = "%s.%s" % (cls.__module__, cls.__name__)
738             doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
739             desc = doc and '%s: %s' % (name, doc) or name
740 
741             row = self.REPORT_CLASS_TMPL % dict(
742                 style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
743                 desc = desc,
744                 count = np+nf+ne,
745                 Pass = np,
746                 fail = nf,
747                 error = ne,
748                 cid = 'c%s' % (cid+1),
749             )
750             rows.append(row)
751 
752             for tid, (n,t,o,e) in enumerate(cls_results):
753                 self._generate_report_test(rows, cid, tid, n, t, o, e)
754 
755         report = self.REPORT_TMPL % dict(
756             test_list = ''.join(rows),
757             count = str(result.success_count+result.failure_count+result.error_count),
758             Pass = str(result.success_count),
759             fail = str(result.failure_count),
760             error = str(result.error_count),
761         )
762         return report
763 
764 
765     def _generate_report_test(self, rows, cid, tid, n, t, o, e):
766         # e.g. 'pt1.1', 'ft1.1', etc
767         has_output = bool(o or e)
768         tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
769         name = t.id().split('.')[-1]
770         doc = t.shortDescription() or ""
771         desc = doc and ('%s: %s' % (name, doc)) or name
772         tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
773 
774         # o and e should be byte string because they are collected from stdout and stderr?
775         if isinstance(o,str):
776             # TODO: some problem with 'string_escape': it escape \n and mess up formating
777             # uo = unicode(o.encode('string_escape'))
778             uo = e
779         else:
780             uo = o
781         if isinstance(e,str):
782             # TODO: some problem with 'string_escape': it escape \n and mess up formating
783             # ue = unicode(e.encode('string_escape'))
784             ue = e
785         else:
786             ue = e
787 
788         script = self.REPORT_TEST_OUTPUT_TMPL % dict(
789             id = tid,
790             output = saxutils.escape(uo+ue),
791         )
792 
793         row = tmpl % dict(
794             tid = tid,
795             Class = (n == 0 and 'hiddenRow' or 'none'),
796             style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
797             desc = desc,
798             script = script,
799             status = self.STATUS[n],
800         )
801         rows.append(row)
802         if not has_output:
803             return
804 
805     def _generate_ending(self):
806         return self.ENDING_TMPL
807 
808 
809 ##############################################################################
810 # Facilities for running tests from the command line
811 ##############################################################################
812 
813 # Note: Reuse unittest.TestProgram to launch test. In the future we may
814 # build our own launcher to support more specific command line
815 # parameters like test title, CSS, etc.
816 class TestProgram(unittest.TestProgram):
817     """
818     A variation of the unittest.TestProgram. Please refer to the base
819     class for command line parameters.
820     """
821     def runTests(self):
822         # Pick HTMLTestRunner as the default test runner.
823         # base class's testRunner parameter is not useful because it means
824         # we have to instantiate HTMLTestRunner before we know self.verbosity.
825         if self.testRunner is None:
826             self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
827         unittest.TestProgram.runTests(self)
828 
829 main = TestProgram
830 
831 ##############################################################################
832 # Executing this module from the command line
833 ##############################################################################
834 
835 if __name__ == "__main__":
836     main(module=None)
View Code

2. 将HTMLTestRunner.py 放到 "

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5

"(以mac为例)

3.主运行文件

 1 # -*- coding:utf-8 -*-
 2 import unittest
 3 import os
 4 import time
 5 import HTMLTestRunner
 6 
 7 
 8 def allTests():
 9     path = os.path.dirname(__file__)
10     print(path)
11     suit = unittest.defaultTestLoader.discover(path,pattern='test2.py')
12     return suit
13 
14 def getNowTime():
15     return time.strftime('%Y-%m-%d %H_%M_%S')
16 
17 def run():
18     fileName = os.path.join(os.path.dirname(__file__),getNowTime()+'report.html')
19     fp = open(fileName,'wb')
20     runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title='UI 自动化测试报告',description="详情")
21     runner.run(allTests())
22 
23 if __name__ == '__main__':
24     run()

4. report 

5. 代码覆盖率

pip3 install coverage

1 coverage3 run ttst.py #上面代码的主运行文件
2 coverage3 html

 会生成htmlcov 的文件夹,打开index.html

 

猜你喜欢

转载自www.cnblogs.com/zhang-dan/p/11443544.html