leave island

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

简单目录层级分4层(效果见下图)

driver层:       驱动层,放置各个浏览器驱动版本,做ui自动化需要考虑兼容性(类型是否支持谷歌,火狐,ie等,支持哪几个谷歌版本等等)

testcases层:   用例层,放置UI自动化脚本,脚本命名一般以test_开头

report层:        报告层,放置UI自动化运行结果报告,一般以html格式生成

utils层:        工具层,放置工具类,类似下图中的HTMLTestRunner文件(生成结果报告类),还可以放数据库操作、时间操作、字符串处理、文件处理等等类

run_all_case.py:  主入口,执行UI自动化,只需要执行这个类,就会去获取所有testcases层的用例,然后运行结果保存在report层

 

 

 

run_all_case.py:具体代码如下


# -*- coding:utf-8 -*-
import unittest
import os
from utils.HTMLTestRunnerForPy3 import HTMLTestRunner
from datetime import datetime



if __name__ == "__main__":
#挑选用例,pattern='test_*.py'表示添加test_开头的py文件
casePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'testcases')
discover = unittest.defaultTestLoader.discover(
start_dir=casePath,
pattern='test_*.py'
)

#指定生成报告地址
reportPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'reports')
reportName = datetime.now().strftime("%Y%m%d%H%M%S") + '.html'
reportFile = os.path.join(reportPath, reportName)
fp = open(reportFile, 'wb')


# 运行用例
runner = HTMLTestRunner(
stream=fp,
# 生成的html报告标题
title='银行UI自动化测试报告',
# 1是粗略的报告,2是详细的报告
verbosity=2,
# 生成的html描述
description='银行UI自动化测试报告'
)
runner.run(discover)
# 创建的文件都需要关闭
fp.close()
 

 

 test_aaa.py:具体代码如下

# -*- coding:utf-8 -*-
import unittest
from selenium import webdriver
import time

#QingQing类的名字任意命名,但命名()里的unittest.TestCase就是去继承这个类,类的作用就是可以使runner.run识别
class QingQing(unittest.TestCase):
    #unittest.TestCase类定义的setUpClass和tearDownClass方法前一定要加@classmethod,
    #setUpClass在这个类里面是第一个执行的方法
    #tearDownClass在这个类里面是最后一个执行的方法
    #中间的执行顺序是通过字符的大小进行顺序执行,命名必须test_开头

    #打开浏览器,获取配置
    @classmethod
    def setUpClass(self):
        # 实例化ChromeOptions
        options = webdriver.ChromeOptions()
        # 关闭浏览器提示信息
        options.add_argument('disable-infobars')
        # 浏览器全屏
        options.add_argument('start-fullscreen')
        driverpath = r'D:\angel\angelauto\littlebee1\driver\chromedriver.exe'
        #driver驱动获取后可以被其他方法调用
        self.driver = webdriver.Chrome(driverpath, options=options)

    def test_01_search_baidu(self):
        # 访问百度首页
        self.driver.get(r"http://www.baidu.com")
        # 百度输入框输入
        self.driver.find_element_by_id("kw").send_keys("懒勺")
        # 点百度一下
        self.driver.find_element_by_id("su").click()
        #等待时间只是为了让你可以看到目前效果,可以省略
        time.sleep(2)


    #执行商品收费功能
    def test_02_search_qq_news(self):
        # 访问qq首页
        self.driver.get(r"http://www.qq.com")
        # 点新闻链接
        self.driver.find_element_by_xpath("//a[text()='新闻']").click()
        # 等待时间只是为了让你可以看到目前效果,可以省略
        time.sleep(3)

    #退出浏览器
    @classmethod
    def tearDownClass(self):
        self.driver.quit()

if __name__ ==  "__main__":
    unittest.main()

HTMLTestRunnerForPy3.py:具体代码如下(第三方工具类,直接使用即可)

  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 logs (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 import io
 95 import sys
 96 import time
 97 import unittest
 98 from xml.sax import saxutils
 99 
100 
101 # ------------------------------------------------------------------------
102 # The redirectors below are used to capture output during testing. Output
103 # sent to sys.stdout and sys.stderr are automatically captured. However
104 # in some cases sys.stdout is already cached before HTMLTestRunner is
105 # invoked (e.g. calling logging.basicConfig). In order to capture those
106 # output, use the redirectors for the cached stream.
107 #
108 # e.g.
109 #   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
110 #   >>>
111 
112 class OutputRedirector(object):
113     """ Wrapper to redirect stdout or stderr """
114     def __init__(self, fp):
115         self.fp = fp
116 
117     def write(self, s):
118         # self.fp.write(s)
119         self.fp.write(bytes(s, 'UTF-8'))
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,'&amp;');
271     s = s.replace(/</g,'&lt;');
272     s = s.replace(/>/g,'&gt;');
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>&nbsp;</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'>&nbsp;</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         self.outputBuffer = io.StringIO()
541         stdout_redirector.fp = self.outputBuffer
542         stderr_redirector.fp = self.outputBuffer
543         self.stdout0 = sys.stdout
544         self.stderr0 = sys.stderr
545         sys.stdout = stdout_redirector
546         sys.stderr = stderr_redirector
547 
548 
549     def complete_output(self):
550         """
551         Disconnect output redirection and return buffer.
552         Safe to call multiple times.
553         """
554         if self.stdout0:
555             sys.stdout = self.stdout0
556             sys.stderr = self.stderr0
557             self.stdout0 = None
558             self.stderr0 = None
559         return self.outputBuffer.getvalue()
560 
561 
562     def stopTest(self, test):
563         # Usually one of addSuccess, addError or addFailure would have been called.
564         # But there are some path in unittest that would bypass this.
565         # We must disconnect stdout in stopTest(), which is guaranteed to be called.
566         self.complete_output()
567 
568 
569     def addSuccess(self, test):
570         self.success_count += 1
571         TestResult.addSuccess(self, test)
572         output = self.complete_output()
573         self.result.append((0, test, output, ''))
574         if self.verbosity > 1:
575             sys.stderr.write('ok ')
576             sys.stderr.write(str(test))
577             sys.stderr.write('\n')
578         else:
579             sys.stderr.write('.')
580 
581     def addError(self, test, err):
582         self.error_count += 1
583         TestResult.addError(self, test, err)
584         _, _exc_str = self.errors[-1]
585         output = self.complete_output()
586         self.result.append((2, test, output, _exc_str))
587         if self.verbosity > 1:
588             sys.stderr.write('E  ')
589             sys.stderr.write(str(test))
590             sys.stderr.write('\n')
591         else:
592             sys.stderr.write('E')
593 
594     def addFailure(self, test, err):
595         self.failure_count += 1
596         TestResult.addFailure(self, test, err)
597         _, _exc_str = self.failures[-1]
598         output = self.complete_output()
599         self.result.append((1, test, output, _exc_str))
600         if self.verbosity > 1:
601             sys.stderr.write('F  ')
602             sys.stderr.write(str(test))
603             sys.stderr.write('\n')
604         else:
605             sys.stderr.write('F')
606 
607 
608 class HTMLTestRunner(Template_mixin):
609     """
610     """
611     def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
612         self.stream = stream
613         self.verbosity = verbosity
614         if title is None:
615             self.title = self.DEFAULT_TITLE
616         else:
617             self.title = title
618         if description is None:
619             self.description = self.DEFAULT_DESCRIPTION
620         else:
621             self.description = description
622 
623         self.startTime = datetime.datetime.now()
624 
625 
626     def run(self, test):
627         "Run the given test case or test suite."
628         result = _TestResult(self.verbosity)
629         test(result)
630         self.stopTime = datetime.datetime.now()
631         self.generateReport(test, result)
632         # print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)
633         print('\nTime Elapsed: %s' % (self.stopTime - self.startTime), file=sys.stderr)
634         return result
635 
636 
637     def sortResult(self, result_list):
638         # unittest does not seems to run in any particular order.
639         # Here at least we want to group them together by class.
640         rmap = {}
641         classes = []
642         for n,t,o,e in result_list:
643             cls = t.__class__
644             if not cls in rmap:
645                 rmap[cls] = []
646                 classes.append(cls)
647             rmap[cls].append((n,t,o,e))
648         r = [(cls, rmap[cls]) for cls in classes]
649         return r
650 
651 
652     def getReportAttributes(self, result):
653         """
654         Return report attributes as a list of (name, value).
655         Override this to add custom attributes.
656         """
657         startTime = str(self.startTime)[:19]
658         duration = str(self.stopTime - self.startTime)
659         status = []
660         if result.success_count: status.append('Pass %s'    % result.success_count)
661         if result.failure_count: status.append('Failure %s' % result.failure_count)
662         if result.error_count:   status.append('Error %s'   % result.error_count  )
663         if status:
664             status = ' '.join(status)
665         else:
666             status = 'none'
667         return [
668             ('Start Time', startTime),
669             ('Duration', duration),
670             ('Status', status),
671         ]
672 
673 
674     def generateReport(self, test, result):
675         report_attrs = self.getReportAttributes(result)
676         generator = 'HTMLTestRunner %s' % __version__
677         stylesheet = self._generate_stylesheet()
678         heading = self._generate_heading(report_attrs)
679         report = self._generate_report(result)
680         ending = self._generate_ending()
681         output = self.HTML_TMPL % dict(
682             title = saxutils.escape(self.title),
683             generator = generator,
684             stylesheet = stylesheet,
685             heading = heading,
686             report = report,
687             ending = ending,
688         )
689         self.stream.write(output.encode('utf8'))
690 
691 
692     def _generate_stylesheet(self):
693         return self.STYLESHEET_TMPL
694 
695 
696     def _generate_heading(self, report_attrs):
697         a_lines = []
698         for name, value in report_attrs:
699             line = self.HEADING_ATTRIBUTE_TMPL % dict(
700                     name = saxutils.escape(name),
701                     value = saxutils.escape(value),
702                 )
703             a_lines.append(line)
704         heading = self.HEADING_TMPL % dict(
705             title = saxutils.escape(self.title),
706             parameters = ''.join(a_lines),
707             description = saxutils.escape(self.description),
708         )
709         return heading
710 
711 
712     def _generate_report(self, result):
713         rows = []
714         sortedResult = self.sortResult(result.result)
715         for cid, (cls, cls_results) in enumerate(sortedResult):
716             # subtotal for a class
717             np = nf = ne = 0
718             for n,t,o,e in cls_results:
719                 if n == 0: np += 1
720                 elif n == 1: nf += 1
721                 else: ne += 1
722 
723             # format class description
724             if cls.__module__ == "__main__":
725                 name = cls.__name__
726             else:
727                 name = "%s.%s" % (cls.__module__, cls.__name__)
728             doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
729             desc = doc and '%s: %s' % (name, doc) or name
730 
731             row = self.REPORT_CLASS_TMPL % dict(
732                 style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
733                 desc = desc,
734                 count = np+nf+ne,
735                 Pass = np,
736                 fail = nf,
737                 error = ne,
738                 cid = 'c%s' % (cid+1),
739             )
740             rows.append(row)
741 
742             for tid, (n,t,o,e) in enumerate(cls_results):
743                 self._generate_report_test(rows, cid, tid, n, t, o, e)
744 
745         report = self.REPORT_TMPL % dict(
746             test_list = ''.join(rows),
747             count = str(result.success_count+result.failure_count+result.error_count),
748             Pass = str(result.success_count),
749             fail = str(result.failure_count),
750             error = str(result.error_count),
751         )
752         return report
753 
754 
755     def _generate_report_test(self, rows, cid, tid, n, t, o, e):
756         # e.g. 'pt1.1', 'ft1.1', etc
757         has_output = bool(o or e)
758         tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
759         name = t.id().split('.')[-1]
760         doc = t.shortDescription() or ""
761         desc = doc and ('%s: %s' % (name, doc)) or name
762         tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
763 
764         # o and e should be byte string because they are collected from stdout and stderr?
765         if isinstance(o,str):
766             # TODO: some problem with 'string_escape': it escape \n and mess up formating
767             # uo = unicode(o.encode('string_escape'))
768             uo = o
769         else:
770             uo =  o.decode('utf-8')
771         if isinstance(e,str):
772             # TODO: some problem with 'string_escape': it escape \n and mess up formating
773             # ue = unicode(e.encode('string_escape'))
774             ue = e
775         else:
776             ue = e.decode('utf-8')
777 
778         script = self.REPORT_TEST_OUTPUT_TMPL % dict(
779             id = tid,
780             output = saxutils.escape(uo+ue),
781         )
782 
783         row = tmpl % dict(
784             tid = tid,
785             Class = (n == 0 and 'hiddenRow' or 'none'),
786             style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
787             desc = desc,
788             script = script,
789             status = self.STATUS[n],
790         )
791         rows.append(row)
792         if not has_output:
793             return
794 
795     def _generate_ending(self):
796         return self.ENDING_TMPL
797 
798 
799 ##############################################################################
800 # Facilities for running tests from the command line
801 ##############################################################################
802 
803 # Note: Reuse unittest.TestProgram to launch test. In the future we may
804 # build our own launcher to support more specific command line
805 # parameters like test title, CSS, etc.
806 class TestProgram(unittest.TestProgram):
807     """
808     A variation of the unittest.TestProgram. Please refer to the base
809     class for command line parameters.
810     """
811     def runTests(self):
812         # Pick HTMLTestRunner as the default test runner.
813         # base class's testRunner parameter is not useful because it means
814         # we have to instantiate HTMLTestRunner before we know self.verbosity.
815         if self.testRunner is None:
816             self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
817         unittest.TestProgram.runTests(self)
818 
819 main = TestProgram
820 
821 ##############################################################################
822 # Executing this module from the command line
823 ##############################################################################
824 
825 if __name__ == "__main__":
826     main(module=None)
View Code

 

生成报告效果

 

 test_aaa.py:具体代码如下

posted on 2020-12-11 15:07  恒-星  阅读(1181)  评论(0编辑  收藏  举报