1. 程式人生 > >Appium python自動化測試系列之使用HTMLTestRunner生成測試報告(十三)

Appium python自動化測試系列之使用HTMLTestRunner生成測試報告(十三)

like resizable mes 500px string padding code tip hid

?13.1 測試報告概述

13.1.1 測試報告的定義

在前面章節我們已經講了自動化基礎的很多東西,如果說掌握了,而且自己動手去練習了,我相信在一些初級的面試中是沒任何問題的,今天我們接觸的應該算是一個比較新的東西,也算是開啟另一層知識的大門。

在手工測試過程中如果發現了bug我們需要提交測試報告,自動化中雖然當程序出錯的時候我們不可能讓程序自動去提交bug(其實也可以,只是需要自己去開發模塊,思路:當程序監聽到錯誤的時候就觸發一個提交bug的程序),但是為了體現工作的價值當自動化跑完之後是不是就需要生成一份測試報告呢,讓我們知道跑了哪些case,哪些通過哪些沒通過,通過率是多少。所以在這個情況下我們就有了測試報告

13.1.2 控制臺消息和HTML報告的區別

其實很多人不明白為什麽有控制臺的消息不看,非得看html的報告,那不是抽風麽?其實我剛開始做的時候也是一樣,覺得控制臺的消息一經提示很明確了,而且排錯什麽的也很好,結合TestNG的看就更方便,其實很長一段時間我確實是這麽做的,但是後來有一次領導讓我分享一下,我按照平時的工作流程做了,後來的結果是這個需要提高,說:你這個還不能完全叫做自動化。其實當時我想為什麽不能呢?後來通過各方面的請教以及收集資料才知道,自己那個真的不叫。就這個很好的例子:每次跑腳本還需要打開IDE然後運行去看報告,不low嗎?如果說你的自動化是自動運行,或者執行一個bat,或者是打開一個網址點一個按鈕,然後收到的就是一份完整的HTML報告,這樣是不是頓時感覺檔次上去了呢?可能有人會疑惑,說你報告裏面會有運行錯誤時的一些信息嗎?答案肯定是有的。哪個case錯誤、哪裏錯誤、錯誤的截圖、錯誤的日誌統統的有啦,還有case的通過率,難道這樣的報告不比控制臺好麽?

13.2 如何擁有HTML測試報告

13.2.1 HTMLTestRunner

HTMLTestRunner是python標準庫中單元測試模塊的一個擴展,在使用python的情況下我們基本都是使用,還有一個事情需要說的是,他是一個擴展,那麽就需要自己去安裝,當然如果你在python的命令模式下直接引入“import HTMLTestRunner”一下,如果你那個不報錯那麽代表你有這個環境,不需要另外配置,如果沒配置接著下看。

13.2.2 HTMLTestRunner 環境配置

首先我們來看一下我電腦中的配置圖片:

技術分享

從上面的圖片我們可以在出在“/Library/Python/2.7/site-packages” 這個目錄下面就是我們python的一些擴展包,然後在這個裏面我有兩個文件,“HTMLTestRunner.py”,“test_HTMLTestRunner.py”,這兩個文件就是我們生成html測試報告的時候需要的東西,當然實際上只需要第一個,這個就當作官網的教程吧。大家也可以去下載HTMLTestRunner.py這個文件然後去放到這個目錄下面,如果沒找到也可以對下面的代碼進行復制:

"""
A TestRunner for use with the Python unit testing framework. It
generates a HTML report to show the result at a glance.
   
The simplest way to use this is to invoke its main method. E.g.
   
    import unittest
    import HTMLTestRunner
   
    ... define your tests ...
   
    if __name__ == ‘__main__‘:
        HTMLTestRunner.main()
   
   
For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest‘s TextTestRunner. E.g.
   
    # output to a file
    fp = file(‘my_report.html‘, ‘wb‘)
    runner = HTMLTestRunner.HTMLTestRunner(
                stream=fp,
                title=‘My unit test‘,
                description=‘This demonstrates the report output by HTMLTestRunner.‘
                )
   
    # Use an external stylesheet.
    # See the Template_mixin class for more customizable options
    runner.STYLESHEET_TMPL = ‘<link rel="stylesheet" href="my_stylesheet.css" type="text/css">‘
   
    # run the test
    runner.run(my_test_suite)
   
   
------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.
   
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
   
* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may be
  used to endorse or promote products derived from this software without
  specific prior written permission.
   
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
   
# URL: http://tungwaiyip.info/software/HTMLTestRunner.html
   
__author__ = "Wai Yip Tung"
__version__ = "0.8.2"
   
   
"""
Change History
   
Version 0.8.2
* Show output inline instead of popup window (Viorel Lupu).
   
Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.
   
Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
   
Version in 0.7.1
* Back port to Python 2.3 (Frank Horowitz).
* Fix missing scroll bars in detail log (Podi).
"""
   
# TODO: color stderr
# TODO: simplify javascript using ,ore than 1 class in the class attribute?
   
import datetime
import StringIO
import sys
import time
import unittest
from xml.sax import saxutils
   
   
# ------------------------------------------------------------------------
# The redirectors below are used to capture output during testing. Output
# sent to sys.stdout and sys.stderr are automatically captured. However
# in some cases sys.stdout is already cached before HTMLTestRunner is
# invoked (e.g. calling logging.basicConfig). In order to capture those
# output, use the redirectors for the cached stream.
#
# e.g.
#   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
#   >>>
   
class OutputRedirector(object):
    """ Wrapper to redirect stdout or stderr """
    def __init__(self, fp):
        self.fp = fp
   
    def write(self, s):
        self.fp.write(s)
   
    def writelines(self, lines):
        self.fp.writelines(lines)
   
    def flush(self):
        self.fp.flush()
   
stdout_redirector = OutputRedirector(sys.stdout)
stderr_redirector = OutputRedirector(sys.stderr)
   
   
   
# ----------------------------------------------------------------------
# Template
   
class Template_mixin(object):
    """
    Define a HTML template for report customerization and generation.
   
    Overall structure of an HTML report
   
    HTML
    +------------------------+
    |<html>                  |
    |  <head>                |
    |                        |
    |   STYLESHEET           |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |  </head>               |
    |                        |
    |  <body>                |
    |                        |
    |   HEADING              |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |   REPORT               |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |   ENDING               |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |  </body>               |
    |</html>                 |
    +------------------------+
    """
   
    STATUS = {
    0: ‘pass‘,
    1: ‘fail‘,
    2: ‘error‘,
    }
   
    DEFAULT_TITLE = ‘Unit Test Report‘
    DEFAULT_DESCRIPTION = ‘‘
   
    # ------------------------------------------------------------------------
    # HTML Template
   
    HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>%(title)s</title>
    <meta name="generator" content="%(generator)s"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    %(stylesheet)s
</head>
<body>
<script language="javascript" type="text/javascript"><!--
output_list = Array();
   
/* level - 0:Summary; 1:Failed; 2:All */
function showCase(level) {
    trs = document.getElementsByTagName("tr");
    for (var i = 0; i < trs.length; i++) {
        tr = trs[i];
        id = tr.id;
        if (id.substr(0,2) == ‘ft‘) {
            if (level < 1) {
                tr.className = ‘hiddenRow‘;
            }
            else {
                tr.className = ‘‘;
            }
        }
        if (id.substr(0,2) == ‘pt‘) {
            if (level > 1) {
                tr.className = ‘‘;
            }
            else {
                tr.className = ‘hiddenRow‘;
            }
        }
    }
}
   
   
function showClassDetail(cid, count) {
    var id_list = Array(count);
    var toHide = 1;
    for (var i = 0; i < count; i++) {
        tid0 = ‘t‘ + cid.substr(1) + ‘.‘ + (i+1);
        tid = ‘f‘ + tid0;
        tr = document.getElementById(tid);
        if (!tr) {
            tid = ‘p‘ + tid0;
            tr = document.getElementById(tid);
        }
        id_list[i] = tid;
        if (tr.className) {
            toHide = 0;
        }
    }
    for (var i = 0; i < count; i++) {
        tid = id_list[i];
        if (toHide) {
            document.getElementById(‘div_‘+tid).style.display = ‘none‘
            document.getElementById(tid).className = ‘hiddenRow‘;
        }
        else {
            document.getElementById(tid).className = ‘‘;
        }
    }
}
   
   
function showTestDetail(div_id){
    var details_div = document.getElementById(div_id)
    var displayState = details_div.style.display
    // alert(displayState)
    if (displayState != ‘block‘ ) {
        displayState = ‘block‘
        details_div.style.display = ‘block‘
    }
    else {
        details_div.style.display = ‘none‘
    }
}
   
   
function html_escape(s) {
    s = s.replace(/&/g,‘&‘);
    s = s.replace(/</g,‘<‘);
    s = s.replace(/>/g,‘>‘);
    return s;
}
   
/* obsoleted by detail in <div>
function showOutput(id, name) {
    var w = window.open("", //url
                    name,
                    "resizable,scrollbars,status,width=800,height=450");
    d = w.document;
    d.write("<pre>");
    d.write(html_escape(output_list[id]));
    d.write("\n");
    d.write("<a href=‘javascript:window.close()‘>close</a>\n");
    d.write("</pre>\n");
    d.close();
}
*/
--></script>
   
%(heading)s
%(report)s
%(ending)s
   
</body>
</html>
"""
    # variables: (title, generator, stylesheet, heading, report, ending)
   
   
    # ------------------------------------------------------------------------
    # Stylesheet
    #
    # alternatively use a <link> for external style sheet, e.g.
    #   <link rel="stylesheet" href="$url" type="text/css">
   
    STYLESHEET_TMPL = """
<style type="text/css" media="screen">
body        { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
table       { font-size: 100%; }
pre         { }
   
/* -- heading ---------------------------------------------------------------------- */
h1 {
  font-size: 16pt;
  color: gray;
}
.heading {
    margin-top: 0ex;
    margin-bottom: 1ex;
}
   
.heading .attribute {
    margin-top: 1ex;
    margin-bottom: 0;
}
   
.heading .description {
    margin-top: 4ex;
    margin-bottom: 6ex;
}
   
/* -- css div popup ------------------------------------------------------------------------ */
a.popup_link {
}
   
a.popup_link:hover {
    color: red;
}
   
.popup_window {
    display: none;
    position: relative;
    left: 0px;
    top: 0px;
    /*border: solid #627173 1px; */
    padding: 10px;
    background-color: #E6E6D6;
    font-family: "Lucida Console", "Courier New", Courier, monospace;
    text-align: left;
    font-size: 8pt;
    width: 500px;
}
   
}
/* -- report ------------------------------------------------------------------------ */
#show_detail_line {
    margin-top: 3ex;
    margin-bottom: 1ex;
}
#result_table {
    width: 80%;
    border-collapse: collapse;
    border: 1px solid #777;
}
#header_row {
    font-weight: bold;
    color: white;
    background-color: #777;
}
#result_table td {
    border: 1px solid #777;
    padding: 2px;
}
#total_row  { font-weight: bold; }
.passClass  { background-color: #6c6; }
.failClass  { background-color: #c60; }
.errorClass { background-color: #c00; }
.passCase   { color: #6c6; }
.failCase   { color: #c60; font-weight: bold; }
.errorCase  { color: #c00; font-weight: bold; }
.hiddenRow  { display: none; }
.testcase   { margin-left: 2em; }
   
   
/* -- ending ---------------------------------------------------------------------- */
#ending {
}
   
</style>
"""
   
   
   
    # ------------------------------------------------------------------------
    # Heading
    #
   
    HEADING_TMPL = """<div class=‘heading‘>
<h1>%(title)s</h1>
%(parameters)s
<p class=‘description‘>%(description)s</p>
</div>
   
""" # variables: (title, parameters, description)
   
    HEADING_ATTRIBUTE_TMPL = """<p class=‘attribute‘><strong>%(name)s:</strong> %(value)s</p>
""" # variables: (name, value)
   
   
   
    # ------------------------------------------------------------------------
    # Report
    #
   
    REPORT_TMPL = """
<p id=‘show_detail_line‘>Show
<a href=‘javascript:showCase(0)‘>Summary</a>
<a href=‘javascript:showCase(1)‘>Failed</a>
<a href=‘javascript:showCase(2)‘>All</a>
</p>
<table id=‘result_table‘>
<colgroup>
<col align=‘left‘ />
<col align=‘right‘ />
<col align=‘right‘ />
<col align=‘right‘ />
<col align=‘right‘ />
<col align=‘right‘ />
</colgroup>
<tr id=‘header_row‘>
    <td>Test Group/Test case</td>
    <td>Count</td>
    <td>Pass</td>
    <td>Fail</td>
    <td>Error</td>
    <td>View</td>
</tr>
%(test_list)s
<tr id=‘total_row‘>
    <td>Total</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td> </td>
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)
   
    REPORT_CLASS_TMPL = r"""
<tr class=‘%(style)s‘>
    <td>%(desc)s</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td><a href="javascript:showClassDetail(‘%(cid)s‘,%(count)s)">Detail</a></td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)
   
   
    REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id=‘%(tid)s‘ class=‘%(Class)s‘>
    <td class=‘%(style)s‘><div class=‘testcase‘>%(desc)s</div></td>
    <td colspan=‘5‘ align=‘center‘>
   
    <!--css div popup start-->
    <a class="popup_link" onfocus=‘this.blur();‘ href="javascript:showTestDetail(‘div_%(tid)s‘)" >
        %(status)s</a>
   
    <div id=‘div_%(tid)s‘ class="popup_window">
        <div style=‘text-align: right; color:red;cursor:pointer‘>
        <a onfocus=‘this.blur();‘ onclick="document.getElementById(‘div_%(tid)s‘).style.display = ‘none‘ " >
           [x]</a>
        </div>
        <pre>
        %(script)s
        </pre>
    </div>
    <!--css div popup end-->
   
    </td>
</tr>
""" # variables: (tid, Class, style, desc, status)
   
   
    REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id=‘%(tid)s‘ class=‘%(Class)s‘>
    <td class=‘%(style)s‘><div class=‘testcase‘>%(desc)s</div></td>
    <td colspan=‘5‘ align=‘center‘>%(status)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)
   
   
    REPORT_TEST_OUTPUT_TMPL = r"""
%(id)s: %(output)s
""" # variables: (id, output)
   
   
   
    # ------------------------------------------------------------------------
    # ENDING
    #
   
    ENDING_TMPL = """<div id=‘ending‘> </div>"""
   
# -------------------- The end of the Template class -------------------
   
   
TestResult = unittest.TestResult
   
class _TestResult(TestResult):
    # note: _TestResult is a pure representation of results.
    # It lacks the output and reporting ability compares to unittest._TextTestResult.
   
    def __init__(self, verbosity=1):
        TestResult.__init__(self)
        self.stdout0 = None
        self.stderr0 = None
        self.success_count = 0
        self.failure_count = 0
        self.error_count = 0
        self.verbosity = verbosity
   
        # result is a list of result in 4 tuple
        # (
        #   result code (0: success; 1: fail; 2: error),
        #   TestCase object,
        #   Test output (byte string),
        #   stack trace,
        # )
        self.result = []
   
   
    def startTest(self, test):
        TestResult.startTest(self, test)
        # just one buffer for both stdout and stderr
        self.outputBuffer = StringIO.StringIO()
        stdout_redirector.fp = self.outputBuffer
        stderr_redirector.fp = self.outputBuffer
        self.stdout0 = sys.stdout
        self.stderr0 = sys.stderr
        sys.stdout = stdout_redirector
        sys.stderr = stderr_redirector
   
   
    def complete_output(self):
        """
        Disconnect output redirection and return buffer.
        Safe to call multiple times.
        """
        if self.stdout0:
            sys.stdout = self.stdout0
            sys.stderr = self.stderr0
            self.stdout0 = None
            self.stderr0 = None
        return self.outputBuffer.getvalue()
   
   
    def stopTest(self, test):
        # Usually one of addSuccess, addError or addFailure would have been called.
        # But there are some path in unittest that would bypass this.
        # We must disconnect stdout in stopTest(), which is guaranteed to be called.
        self.complete_output()
   
   
    def addSuccess(self, test):
        self.success_count += 1
        TestResult.addSuccess(self, test)
        output = self.complete_output()
        self.result.append((0, test, output, ‘‘))
        if self.verbosity > 1:
            sys.stderr.write(‘ok ‘)
            sys.stderr.write(str(test))
            sys.stderr.write(‘\n‘)
        else:
            sys.stderr.write(‘.‘)
   
    def addError(self, test, err):
        self.error_count += 1
        TestResult.addError(self, test, err)
        _, _exc_str = self.errors[-1]
        output = self.complete_output()
        self.result.append((2, test, output, _exc_str))
        if self.verbosity > 1:
            sys.stderr.write(‘E  ‘)
            sys.stderr.write(str(test))
            sys.stderr.write(‘\n‘)
        else:
            sys.stderr.write(‘E‘)
   
    def addFailure(self, test, err):
        self.failure_count += 1
        TestResult.addFailure(self, test, err)
        _, _exc_str = self.failures[-1]
        output = self.complete_output()
        self.result.append((1, test, output, _exc_str))
        if self.verbosity > 1:
            sys.stderr.write(‘F  ‘)
            sys.stderr.write(str(test))
            sys.stderr.write(‘\n‘)
        else:
            sys.stderr.write(‘F‘)
   
   
class HTMLTestRunner(Template_mixin):
    """
    """
    def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
        self.stream = stream
        self.verbosity = verbosity
        if title is None:
            self.title = self.DEFAULT_TITLE
        else:
            self.title = title
        if description is None:
            self.description = self.DEFAULT_DESCRIPTION
        else:
            self.description = description
   
        self.startTime = datetime.datetime.now()
   
   
    def run(self, test):
        "Run the given test case or test suite."
        result = _TestResult(self.verbosity)
        test(result)
        self.stopTime = datetime.datetime.now()
        self.generateReport(test, result)
        print >>sys.stderr, ‘\nTime Elapsed: %s‘ % (self.stopTime-self.startTime)
        return result
   
   
    def sortResult(self, result_list):
        # unittest does not seems to run in any particular order.
        # Here at least we want to group them together by class.
        rmap = {}
        classes = []
        for n,t,o,e in result_list:
            cls = t.__class__
            if not rmap.has_key(cls):
                rmap[cls] = []
                classes.append(cls)
            rmap[cls].append((n,t,o,e))
        r = [(cls, rmap[cls]) for cls in classes]
        return r
   
   
    def getReportAttributes(self, result):
        """
        Return report attributes as a list of (name, value).
        Override this to add custom attributes.
        """
        startTime = str(self.startTime)[:19]
        duration = str(self.stopTime - self.startTime)
        status = []
        if result.success_count: status.append(‘Pass %s‘    % result.success_count)
        if result.failure_count: status.append(‘Failure %s‘ % result.failure_count)
        if result.error_count:   status.append(‘Error %s‘   % result.error_count  )
        if status:
            status = ‘ ‘.join(status)
        else:
            status = ‘none‘
        return [
            (‘Start Time‘, startTime),
            (‘Duration‘, duration),
            (‘Status‘, status),
        ]
   
   
    def generateReport(self, test, result):
        report_attrs = self.getReportAttributes(result)
        generator = ‘HTMLTestRunner %s‘ % __version__
        stylesheet = self._generate_stylesheet()
        heading = self._generate_heading(report_attrs)
        report = self._generate_report(result)
        ending = self._generate_ending()
        output = self.HTML_TMPL % dict(
            title = saxutils.escape(self.title),
            generator = generator,
            stylesheet = stylesheet,
            heading = heading,
            report = report,
            ending = ending,
        )
        self.stream.write(output.encode(‘utf8‘))
   
   
    def _generate_stylesheet(self):
        return self.STYLESHEET_TMPL
   
   
    def _generate_heading(self, report_attrs):
        a_lines = []
        for name, value in report_attrs:
            line = self.HEADING_ATTRIBUTE_TMPL % dict(
                    name = saxutils.escape(name),
                    value = saxutils.escape(value),
                )
            a_lines.append(line)
        heading = self.HEADING_TMPL % dict(
            title = saxutils.escape(self.title),
            parameters = ‘‘.join(a_lines),
            description = saxutils.escape(self.description),
        )
        return heading
   
   
    def _generate_report(self, result):
        rows = []
        sortedResult = self.sortResult(result.result)
        for cid, (cls, cls_results) in enumerate(sortedResult):
            # subtotal for a class
            np = nf = ne = 0
            for n,t,o,e in cls_results:
                if n == 0: np += 1
                elif n == 1: nf += 1
                else: ne += 1
   
            # format class description
            if cls.__module__ == "__main__":
                name = cls.__name__
            else:
                name = "%s.%s" % (cls.__module__, cls.__name__)
            doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
            desc = doc and ‘%s: %s‘ % (name, doc) or name
   
            row = self.REPORT_CLASS_TMPL % dict(
                style = ne > 0 and ‘errorClass‘ or nf > 0 and ‘failClass‘ or ‘passClass‘,
                desc = desc,
                count = np+nf+ne,
                Pass = np,
                fail = nf,
                error = ne,
                cid = ‘c%s‘ % (cid+1),
            )
            rows.append(row)
   
            for tid, (n,t,o,e) in enumerate(cls_results):
                self._generate_report_test(rows, cid, tid, n, t, o, e)
   
        report = self.REPORT_TMPL % dict(
            test_list = ‘‘.join(rows),
            count = str(result.success_count+result.failure_count+result.error_count),
            Pass = str(result.success_count),
            fail = str(result.failure_count),
            error = str(result.error_count),
        )
        return report
   
   
    def _generate_report_test(self, rows, cid, tid, n, t, o, e):
        # e.g. ‘pt1.1‘, ‘ft1.1‘, etc
        has_output = bool(o or e)
        tid = (n == 0 and ‘p‘ or ‘f‘) + ‘t%s.%s‘ % (cid+1,tid+1)
        name = t.id().split(‘.‘)[-1]
        doc = t.shortDescription() or ""
        desc = doc and (‘%s: %s‘ % (name, doc)) or name
        tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
   
        # o and e should be byte string because they are collected from stdout and stderr?
        if isinstance(o,str):
            # TODO: some problem with ‘string_escape‘: it escape \n and mess up formating
            # uo = unicode(o.encode(‘string_escape‘))
            uo = o.decode(‘latin-1‘)
        else:
            uo = o
        if isinstance(e,str):
            # TODO: some problem with ‘string_escape‘: it escape \n and mess up formating
            # ue = unicode(e.encode(‘string_escape‘))
            ue = e.decode(‘latin-1‘)
        else:
            ue = e
   
        script = self.REPORT_TEST_OUTPUT_TMPL % dict(
            id = tid,
            output = saxutils.escape(uo+ue),
        )
   
        row = tmpl % dict(
            tid = tid,
            Class = (n == 0 and ‘hiddenRow‘ or ‘none‘),
            style = n == 2 and ‘errorCase‘ or (n == 1 and ‘failCase‘ or ‘none‘),
            desc = desc,
            script = script,
            status = self.STATUS[n],
        )
        rows.append(row)
        if not has_output:
            return
   
    def _generate_ending(self):
        return self.ENDING_TMPL
   
   
##############################################################################
# Facilities for running tests from the command line
##############################################################################
   
# Note: Reuse unittest.TestProgram to launch test. In the future we may
# build our own launcher to support more specific command line
# parameters like test title, CSS, etc.
class TestProgram(unittest.TestProgram):
    """
    A variation of the unittest.TestProgram. Please refer to the base
    class for command line parameters.
    """
    def runTests(self):
        # Pick HTMLTestRunner as the default test runner.
        # base class‘s testRunner parameter is not useful because it means
        # we have to instantiate HTMLTestRunner before we know self.verbosity.
        if self.testRunner is None:
            self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
        unittest.TestProgram.runTests(self)
   
main = TestProgram
   
##############################################################################
# Executing this module from the command line
##############################################################################
   
if __name__ == "__main__":
    main(module=None)

  記住,這是一個.py文件。配置完畢之後再次去試一下“import HTMLTestRunner”,如果不報錯,恭喜你,成功配置文件成功。

13.2.3 HTMLTestRunner的使用

首先我們要知道我們定要運行一個case,那麽我們是不是首先要將我們的case都封裝起來,然後才能去執行呢?

so,這裏我們需要講到開發語言中經常用到的一個關鍵字 class,就是我們經常說的類,python裏面類的定義很簡單,class className():這就是一個類的定義。

下面我們來看一個最最最簡單的HTMLTestRunner的測試模版是怎麽樣的

# -*- coding: utf-8 -*-
import unittest
#引入要測試的文件,就是引入我們測試類所屬文件
import testMycase
import HTMLTestRunner   
suite = unittest.TestSuite()
#引入測試的類,因為我們的類在testMycase這個文件下,所以就直接文件名.類名
suite.addTest(unittest.makeSuite(testMycase.appiumTest))
#定義報告路徑
filename = ‘test.html‘
#定義報告文件權限,wb,表示有讀寫權限
fp = file(filename,‘wb‘)
runner = HTMLTestRunner.HTMLTestRunner(
        stream = fp,
        title =‘appiumTest‘,
        description = ‘測試報告‘)
#執行測試
runner.run(suite)
#關閉文件,否則會無法生成文件
fp.close()

  

上面的代碼就是我們測試報告生成的一個簡要模版,大家只需要將自己需要測試的類引入到這個裏面就可以了,當然也是可以直接在方法中去運行的,但是為了方便,這裏給大家說了另外一個知識。趕快下去動手吧

Appium python自動化測試系列之使用HTMLTestRunner生成測試報告(十三)