UI自動化框架搭建(三):簡單目錄層級結構實現(包含ui腳本、工具、報告、驅動)


簡單目錄層級分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:具體代碼如下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM