unittest 生成测试报告

前置:下载HTMLTestRunner.py文件,放在D:\Programs\Python\Python37\Lib\site-packages目录下

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

生成测试报告:

复制代码
 1 import unittest
 2 import time
 3 from selenium import webdriver
 4 import HTMLTestRunner
 5 
 6 
 7 class UILoginTest(unittest.TestCase):
 8 
 9     def setUp(self) -> None:
10         self.driver = webdriver.Chrome()
11         self.imgs = []  # 初始化存放截图的列表
12         self.url = 'http://xxx/login'
13 
14     def tearDown(self) -> None:
15         self.driver.quit()
16 
17     def test_login_success(self):
18         self.driver.get(self.url)
19         self.driver.maximize_window()
20         self.driver.find_element_by_xpath('//*[@type="button"]/span[text()="登录"]').click()
21         time.sleep(0.5)
22         self.driver.find_element('xpath', '//*[@placeholder="用户名"]').send_keys('xxx')
23         self.driver.find_element('xpath', '//*[@placeholder="密码"]').send_keys('xxxxxx')
24         self.driver.find_element('xpath','//*[@type="button"]/span[text()="登\xa0\xa0录"]').click()
25         time.sleep(2)
26         # 执行截图操作,将当前截图加入测试报告中
27         self.imgs.append(self.driver.get_screenshot_as_base64())
28         loginname = self.driver.find_element('xpath', '//*[@title="XX"]').text
29         # print(loginname)
30         self.assertEqual('XX', loginname)
31 
32     def test_login_failed_without_username(self):
33         self.driver.get(self.url)
34         self.driver.maximize_window()
35         self.driver.find_element_by_xpath('//*[@type="button"]/span[text()="登录"]').click()
36         time.sleep(0.5)
37         self.driver.find_element('xpath', '//*[@placeholder="密码"]').send_keys('xxxxxx')
38         self.driver.find_element('xpath', '//*[@type="button"]/span[text()="登\xa0\xa0录"]').click()
39         time.sleep(0.5)
40         self.imgs.append(self.driver.get_screenshot_as_base64())
41         errmsg = self.driver.find_element('xpath', '//div[contains(text(),"请输入用户名")]').text
42         # print(errmsg)
43         self.assertEqual(errmsg, '请输入用户名')
44 
45     def test_login_failed_with_incorrect_username(self):
46         self.driver.get(self.url)
47         self.driver.maximize_window()
48         self.driver.find_element_by_xpath('//*[@type="button"]/span[text()="登录"]').click()
49         time.sleep(0.5)
50         self.driver.find_element('xpath', '//*[@placeholder="用户名"]').send_keys('xxx1')
51         self.driver.find_element('xpath', '//*[@placeholder="密码"]').send_keys('xxxxxx')
52         self.driver.find_element('xpath', '//*[@type="button"]/span[text()="登\xa0\xa0录"]').click()
53         time.sleep(0.5)
54         self.imgs.append(self.driver.get_screenshot_as_base64())
55         errmsg = self.driver.find_element('xpath', '//p[contains(text(),"验证失败!")]').text
56         # print(errmsg)
57         self.assertEqual(errmsg, '验证失败!')
58 
59     def test_login_failed_without_password(self):
60         self.driver.get(self.url)
61         self.driver.maximize_window()
62         self.driver.find_element_by_xpath('//*[@type="button"]/span[text()="登录"]').click()
63         time.sleep(0.5)
64         self.driver.find_element('xpath', '//*[@placeholder="用户名"]').send_keys('xxx')
65         self.driver.find_element('xpath', '//*[@type="button"]/span[text()="登\xa0\xa0录"]').click()
66         time.sleep(0.5)
67         self.imgs.append(self.driver.get_screenshot_as_base64())
68         errmsg = self.driver.find_element('xpath', '//div[contains(text(),"请输入密码")]').text
69         # print(errmsg)
70         self.assertEqual(errmsg, '请输入密码')
71 
72 
73 if __name__ == '__main__':
74     test1 = unittest.defaultTestLoader.loadTestsFromTestCase(UILoginTest)
75     suite = unittest.TestSuite(test1)
76 
77     runner = HTMLTestRunner.HTMLTestRunner(
78         title='xx系统自动化测试报告',
79         description='xxx测试报告',
80         stream=open('sample_test_report.html', 'wb'),
81         verbosity=2
82     )
83 
84     runner.run(suite)
复制代码

 

posted @   wujin啊~  阅读(92)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示