2015-06-20 78 views
0

我在使用HTMLTestRunner生成html報告時遇到了問題。我在一個名爲TestsSuite.py的文件中創建了一個測試套件。下面的代碼片段:Python HTMLTestRunner無法生成報告

import unittest 
from LoginTest import LoginTest 
from EditProfileTest import ProfileEditionTest 
from EditAddressTest import EditAddressTest 
from CreatePostTest import CreatePostTest 
from HTMLTestRunner import HTMLTestRunner 

def suite(): 
    suite = unittest.TestSuite() 
    suite.addTest(LoginTest()) 
    suite.addTest(ProfileEditionTest()) 
    suite().addTest(EditAddressTest) 
    suite().addTest(CreatePostTest) 
    return suite 
if ____name____ == "____main___": 
    unittest.TextTestRunner(verbosity=2).run(suite()) 
    outfile = open('C:/Report.html', 'w') 
    runner = HTMLTestRunner(stream=outfile,verbosity=2,title='LinkedIn Report',description='This is a demo report') 
    runner.run(suite()) 

當我運行這段代碼,它運行到完成,並顯示爲合格的PyUnit所有測試用例,但是當我打開Report.html,則顯示空白文件。

這是什麼,我做錯了?

回答

0

首先,我不認爲HTMLTestRunner與Python 3.x一起工作...所以你運氣不好。但讓我們假設你正在嘗試Python 2.7 ...

你有一堆問題。您應該確實閱讀unittestHTMLTestRunner的文檔。

然而,考慮下面這個例子,它實現了在Python 2類似:

import unittest 
from HTMLTestRunner import HTMLTestRunner 


class TestFoo(unittest.TestCase): 
    def test_foo(self): 
     self.assertTrue(True) 


class TestBar(unittest.TestCase): 
    def test_bar(self): 
     self.assertFalse(False) 


if __name__ == '__main__': 
    loader = unittest.TestLoader() 
    suite = unittest.TestSuite((
     loader.loadTestsFromTestCase(TestFoo), 
     loader.loadTestsFromTestCase(TestBar))) 
    outfile = file('Report.html', 'w') 
    runner = HTMLTestRunner(stream=outfile, 
          verbosity=2, 
          title='LinkedIn Report', 
          description='This is a demo report') 
    runner.run(suite) 
0

HTMLTestRunner似乎沒有了積極支持。我在Github上看到了一篇文章,HTMLTestRunner創建者tungwaiyip提到他沒有時間支持HTMLTestRunner,並且詢問其他人是否會繼續開發它。

在那次對話中,兩個人提到他們對文件所做的更改以使其爲他們工作。該職位是:

https://github.com/tungwaiyip/HTMLTestRunner/issues/1

我做了09月28和locus2k的評論細節變化,幫助我,雖然他的一些行號都處於關閉狀態。我也不得不做出一些編輯的文件我自己把事情對我來說如工作:

  • 線642使用對象的has_key需要加以改變以「如果不CLS在 RMAP:」
  • 線687在stream.write()上編碼了一個錯誤字符串。更改爲 不對字符串進行編碼。
1

您好我已經修改了代碼HTMLTestRunner.py文件中使用Python 3.x的工作,並遵循正常程序導入模塊照常 希望它可以幫助

""" 
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 sys 
import time 
import unittest 
from xml.sax import saxutils 
from io import StringIO 


# ------------------------------------------------------------------------ 
# 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,'&amp;'); 
    s = s.replace(/</g,'&lt;'); 
    s = s.replace(/>/g,'&gt;'); 
    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>&nbsp;</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'>&nbsp;</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() 
     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) 
     # timelapse = self.stopTime-self.startTime 
     # print('Time Elapsed: ' + '%s:' % timelapse) 
     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 cls not in rmap.keys(): 
       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) 


    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 = o.encode('utf-8') 
      uo = uo.decode('utf-8') 
     else: 
      uo = o 
     if isinstance(e, str): 
      # TODO: some problem with 'string_escape': it escape \n and mess up formating 
      ue = e.encode('utf-8') 
      ue = ue.decode('utf-8') 
     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)