Python unittest如何生成HTMLTestRunner模块

生成 HTMLTestRunner 模块

unittest 里面是不能生成 html 格式报告的,需要导入一个第三方的模块:HTMLTestRunner

方法1.这个模块下载不能通过 pip 安装了,只能下载后手动导入,下载地址:

http://tungwaiyip.info/software/HTMLTestRunner.html

方法2.在 python 安装文件的 Lib 目录下新增文件 HTMLTestRunner.py

两种模板如下,建议使用第一种(第一种模板更加美观)

文件内容如下:

(1)第一种模板

# -*- coding: utf-8 -*-
"""
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" rel="external nofollow" rel="external nofollow" 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.9.1"

"""
Change History
Version 0.9.1
* 用Echarts添加执行情况统计图 (灰蓝)
Version 0.9.0
* 改成Python 3.x (灰蓝)
Version 0.8.3
* 使用 Bootstrap稍加美化 (灰蓝)
* 改为中文 (灰蓝)
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 io
import time
import unittest
from xml.sax import saxutils
import getpass

# ------------------------------------------------------------------------
# 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: u'通过',
    1: u'失败',
    2: u'错误',
  }

  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"/>

  <link href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="external nofollow" rel="stylesheet">
  <script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script>
  <!-- <script type="text/javascript" src="js/echarts.common.min.js"></script> -->

  %(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>
  <div id="div_base">
    %(heading)s
    %(report)s
    %(ending)s
    %(chart_script)s
  </div>
</body>
</html>
""" # variables: (title, generator, stylesheet, heading, report, ending, chart_script)

  ECHARTS_SCRIPT = """
  <script type="text/javascript">
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.getElementById('chart'));
    // 指定图表的配置项和数据
    var option = {
      title : {
        text: '测试执行情况',
        x:'center'
      },
      tooltip : {
        trigger: 'item',
        formatter: "{a} <br/>{b} : {c} ({d}%%)"
      },
      color: ['#95b75d', 'grey', '#b64645'],
      legend: {
        orient: 'vertical',
        left: 'left',
        data: ['通过','失败','错误']
      },
      series : [
        {
          name: '测试执行情况',
          type: 'pie',
          radius : '60%%',
          center: ['50%%', '60%%'],
          data:[
            {value:%(Pass)s, name:'通过'},
            {value:%(fail)s, name:'失败'},
            {value:%(error)s, name:'错误'}
          ],
          itemStyle: {
            emphasis: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    };
    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
  </script>
  """ # variables: (Pass, fail, error)

  # ------------------------------------------------------------------------
  # Stylesheet
  #
  # alternatively use a <link> for external style sheet, e.g.
  #  <link rel="stylesheet" href="$url" rel="external nofollow" rel="external nofollow" type="text/css">

  STYLESHEET_TMPL = """
<style type="text/css" media="screen">
  body    { font-family: Microsoft YaHei,Consolas,arial,sans-serif; font-size: 80%; }
  table    { font-size: 100%; }
  pre     { white-space: pre-wrap;word-wrap: break-word; }
  /* -- 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: 2ex;
    margin-bottom: 3ex;
  }
  /* -- 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: 99%;
  }
  #header_row {
    font-weight: bold;
    color: #303641;
    background-color: #ebebeb;
  }
  #total_row { font-weight: bold; }
  .passClass { background-color: #bdedbc; }
  .failClass { background-color: #ffefa4; }
  .errorClass { background-color: #ffc9c9; }
  .passCase  { color: #6c6; }
  .failCase  { color: #FF6600; font-weight: bold; }
  .errorCase { color: #c00; font-weight: bold; }
  .hiddenRow { display: none; }
  .testcase  { margin-left: 2em; }
  /* -- ending ---------------------------------------------------------------------- */
  #ending {
  }
  #div_base {
        position:absolute;
        top:0%;
        left:5%;
        right:5%;
        width: auto;
        height: auto;
        margin: -15px 0 0 0;
  }
</style>
"""

  # ------------------------------------------------------------------------
  # Heading
  #

  HEADING_TMPL = """
  <div class='page-header'>
    <h1>%(title)s</h1>
  %(parameters)s
  </div>
  <div style="float: left;width:50%%;"><p class='description'>%(description)s</p></div>
  <div id="chart" style="width:50%%;height:400px;float:left;"></div>
""" # variables: (title, parameters, description)

  HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
""" # variables: (name, value)

  # ------------------------------------------------------------------------
  # Report
  #

  REPORT_TMPL = u"""
  <div class="btn-group btn-group-sm">
    <button class="btn btn-default" onclick='javascript:showCase(0)'>总结</button>
    <button class="btn btn-default" onclick='javascript:showCase(1)'>失败</button>
    <button class="btn btn-default" onclick='javascript:showCase(2)'>全部</button>
  </div>
  <p></p>
  <table id='result_table' class="table table-bordered">
    <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>测试套件/测试用例</td>
      <td>总数</td>
      <td>通过</td>
      <td>失败</td>
      <td>错误</td>
      <td>查看</td>
    </tr>
    %(test_list)s
    <tr id='total_row'>
      <td>总计</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 = u"""
  <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)" rel="external nofollow" rel="external nofollow" >详情</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')" rel="external nofollow" rel="external nofollow" >
    %(status)s</a>
  <div id='div_%(tid)s' class="popup_window">
    <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 = []
    self.subtestlist = []

  def startTest(self, test):
    TestResult.startTest(self, test)
    # just one buffer for both stdout and stderr
    self.outputBuffer = io.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):
    if test not in self.subtestlist:
      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')

  def addSubTest(self, test, subtest, err):
    if err is not None:
      if getattr(self, 'failfast', False):
        self.stop()
      if issubclass(err[0], test.failureException):
        self.failure_count += 1
        errors = self.failures
        errors.append((subtest, self._exc_info_to_string(err, subtest)))
        output = self.complete_output()
        self.result.append((1, test, output + '\nSubTestCase Failed:\n' + str(subtest),
                  self._exc_info_to_string(err, subtest)))
        if self.verbosity > 1:
          sys.stderr.write('F ')
          sys.stderr.write(str(subtest))
          sys.stderr.write('\n')
        else:
          sys.stderr.write('F')
      else:
        self.error_count += 1
        errors = self.errors
        errors.append((subtest, self._exc_info_to_string(err, subtest)))
        output = self.complete_output()
        self.result.append(
          (2, test, output + '\nSubTestCase Error:\n' + str(subtest), self._exc_info_to_string(err, subtest)))
        if self.verbosity > 1:
          sys.stderr.write('E ')
          sys.stderr.write(str(subtest))
          sys.stderr.write('\n')
        else:
          sys.stderr.write('E')
      self._mirrorOutput = True
    else:
      self.subtestlist.append(subtest)
      self.subtestlist.append(test)
      self.success_count += 1
      output = self.complete_output()
      self.result.append((0, test, output + '\nSubTestCase Pass:\n' + str(subtest), ''))
      if self.verbosity > 1:
        sys.stderr.write('ok ')
        sys.stderr.write(str(subtest))
        sys.stderr.write('\n')
      else:
        sys.stderr.write('.')

class HTMLTestRunner(Template_mixin):

  def __init__(self, stream=sys.stdout, verbosity=1, title="TestReport", tester=getpass.getuser(), description="测试详情如下:"):
    self.stream = stream
    self.verbosity = verbosity
    self.tester = tester
    """
    verbosity:
      =1的时候 默认值为1,不限制完整结果,即单个用例成功输出'.',失败输出'F',错误输出'E'
      =0的时候。不输出信息
      =2的时候,需要打印详细的返回信息
    stream:测试报告写入文件的存储区域
    title:测试报告的主题
    tester:默认获取本机用户名
    description:测试报告的描述
    """
    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('\nTime Elapsed: %s' % (self.stopTime - self.startTime), file=sys.stderr)
    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:
        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(u'通过 %s' % result.success_count)
    if result.failure_count: status.append(u'失败 %s' % result.failure_count)
    if result.error_count:  status.append(u'错误 %s' % result.error_count)
    if status:
      status = ' '.join(status)
    else:
      status = 'none'
    return [
      (u'测试人员', self.tester),
      (u'开始时间', startTime),
      (u'运行时长', duration),
      (u'状态', 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()
    chart = self._generate_chart(result)
    output = self.HTML_TMPL % dict(
      title=saxutils.escape(self.title),
      generator=generator,
      stylesheet=stylesheet,
      heading=heading,
      report=report,
      ending=ending,
      chart_script=chart
    )
    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_chart(self, result):
    chart = self.ECHARTS_SCRIPT % dict(
      Pass=str(result.success_count),
      fail=str(result.failure_count),
      error=str(result.error_count),
    )
    return chart

  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

    script = self.REPORT_TEST_OUTPUT_TMPL % dict(
      id=tid,
      output=saxutils.escape(o + e),
    )

    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)

(2)第二种模板

"""
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" rel="external nofollow" rel="external nofollow" 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 io
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" rel="external nofollow" rel="external nofollow" 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)" rel="external nofollow" rel="external nofollow" >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')" rel="external nofollow" rel="external nofollow" >
    %(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= io.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, "\nTimeElapsed: %s" % (self.stopTime-self.startTime))
    sys.stderr.write('\nTime Elapsed: %s\n' % (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 cls in rmap:
        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
    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
    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)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Python unittest生成测试报告过程解析

    1.先导入HTMLTestRunner模块 见生成HTMLTestRunner模块 2.实例如下 (1)单用例文件执行且生成报告 import unittest import HTMLTestRunner class Study01(unittest.TestCase): def test01(self): print "test01" def test02(self): self.assertEqual(1,2,msg="1 != 2") def test03(s

  • Python单元测试工具doctest和unittest使用解析

    Python标准库包含两个测试工具. doctest:一个简单的模块,为检查文档而设计,但也适合用来编写单元测试. unittest:一个通用的测试框架. 一.使用doctest进行单元测试 创建文件mymath.py,内容 def square(x): ''' 计算平方并返回结果(下面是单元测试的格式) >>> square(2) >>> square(3) ''' return x * x if __name__ == '__main__': import doct

  • Python + Requests + Unittest接口自动化测试实例分析

    本文实例讲述了Python + Requests + Unittest接口自动化测试.分享给大家供大家参考,具体如下: 1. 介绍下python的requests模块 Python Requests快速入门 :http://cn.python-requests.org/zh_CN/latest/ 想必会Python基础的小伙伴们一看就懂了 2. Requests接口自动化测试: 2.1 如何利用这么利器进行接口测试,请看小demo: # -*- coding:utf-8 -* import re

  • 对python的unittest架构公共参数token提取方法详解

    额...每个请求都有token值的传入,但是token非常易变,一旦变化,所有的接口用例都得改一遍token,工作量太大了... 那么有没有一种方法能把token提取出来,作为一个全局变量,作为一个参数,从而牵一发而动全身呢?? 经过探索,具体方案如下 先定义一个全局变量token类型为string 然后把请求链接定义一个变量类型为string 然后定义第三个变量=前两个变量相加 然后requests直接传第三个变量就行了 具体代码如下: class Test(unittest.TestCase

  • Python中Unittest框架的具体使用

    Unittest 1.环境 Unittest为Python内嵌的测试框架,不需要特殊配置,仅需要在File---settings---Tools----Python Intergrated Tools下配置框架为Unittest 2.编写规范 需要导入 import unittest 测试类必须继承unittest.TestCase 测试方法以 test_开头 模块和类名没有要求 3.Unittest介绍 import unittest def add(x, y): return x + y d

  • Python unittest discover批量执行代码实例

    代码如下 import unittest dir = "D:\\work_doc\\pycharm2\\python_Basics" #自动化用例所存放的路径 suit = unittest.defaultTestLoader.discover(dir,pattern="XFS*.py",top_level_dir=None) #匹配出需要执行的py文件 runner = unittest.TextTestRunner() #TextTestRunner类实例化,目

  • python 解决pycharm运行py文件只有unittest选项的问题

    有时候在编完脚本开始运行时,发现某个py脚本右键运行的选项不是run,二是run in unittest,试过很多方法都不能很好的去除,主要是因为脚本中含有test字符串,一种解决方法是将脚本中所有的函数和类的test字符串改为其他的. 但是此方法比较麻烦,需要改函数和类的名字,有一种根本的解决方法: File-> Settings -> Tools -> Python Integrated Tools -> Default test runner 将Unittests改为py.t

  • Python unittest装饰器实现原理及代码

    1.前言 前面讲到unittest里面setUp可以在每次执行用例前执行,这样有效的减少了代码量,但是有个弊端,比如打开浏览器操作,每次执行用例时候都会重新打开,这样就会浪费很多时间. 于是就想是不是可以只打开一次浏览器,执行完用例再关闭呢?这就需要用到装饰器(@classmethod)来解决了. 2.@classmethod 装饰器中的 setUpClass() 和 tearDownClass() 与 setUp() 和 tearDown() 的区别 setUp():每个测试 case 运行之

  • Python unittest如何生成HTMLTestRunner模块

    生成 HTMLTestRunner 模块 unittest 里面是不能生成 html 格式报告的,需要导入一个第三方的模块:HTMLTestRunner 方法1.这个模块下载不能通过 pip 安装了,只能下载后手动导入,下载地址: http://tungwaiyip.info/software/HTMLTestRunner.html 方法2.在 python 安装文件的 Lib 目录下新增文件 HTMLTestRunner.py 两种模板如下,建议使用第一种(第一种模板更加美观) 文件内容如下:

  • Python实现简单生成验证码功能【基于random模块】

    本文实例讲述了Python实现简单生成验证码功能.分享给大家供大家参考,具体如下: 验证码一般用来验证登陆.交易等行为,减少对端为机器操作的概率,python中可以使用random模块,char()内置函数来实现一个简单的验证码功能. import random def veri_code(): li = [] for i in range(6): #循环6次,生成6个字符 r = random.randrange(0, 5) #随机生成0-4之间的数字 if r == 1 or r == 4:

  • Python unittest模块用法实例分析

    本文实例讲述了Python unittest模块用法.分享给大家供大家参考,具体如下: python的unittest模块提供了一个测试框架,只要我们写一个继承unittest.TestCase的类,类中用setUp做初始化,用tearDown做清理. 主要用到的函数有: failedinfo表示不成立打印信息failedinfo,为可选参数 self.fail([msg])会无条件的导致测试失败,不推荐使用. self.assertEqual(value1, value2, failedinf

  • Python unittest单元测试框架的使用

    一.测试模型 下面这部分来自于某书籍资料,拿过来,按需参考一下: 测试模型 (1)线性测试 1.概念: 通过录制或编写对应应用程序的操作步骤产生的线性脚本.单纯的来模拟用户完整的操作场景.(操作,重复操作,数据)都混合在一起. 2.优点:每个脚本相对独立,且不产生其他依赖和调用.任何一个测试用例脚本拿出来都可以单独执行. 3.缺点:开发成本高,用例之间存在重复的操作.比如重复的用户登录和退出. 维护成本高,由于重复的操作,当重复的操作发生改变时,则需要逐一进行脚本的修改. 4.线性测试实例:用户

  • Python  unittest单元测试框架的使用

    一.测试模型 下面这部分来自于某书籍资料,拿过来,按需参考一下: 测试模型 (1)线性测试 1.概念: 通过录制或编写对应应用程序的操作步骤产生的线性脚本.单纯的来模拟用户完整的操作场景.(操作,重复操作,数据)都混合在一起. 2.优点:每个脚本相对独立,且不产生其他依赖和调用.任何一个测试用例脚本拿出来都可以单独执行. 3.缺点:开发成本高,用例之间存在重复的操作.比如重复的用户登录和退出. 维护成本高,由于重复的操作,当重复的操作发生改变时,则需要逐一进行脚本的修改. 4.线性测试实例:用户

  • Python+unittest+requests 接口自动化测试框架搭建教程

    一.Python+unittest+requests+HTMLTestRunner 完整的接口自动化测试框架搭建_00--框架结构简解 首先配置好开发环境,下载安装Python并下载安装pycharm,在pycharm中创建项目功能目录.如果不会的可以百度Google一下,该内容网上的讲解还是比较多比较全的! 大家可以先简单了解下该项目的目录结构介绍,后面会针对每个文件有详细注解和代码. common: --configDb.py:这个文件主要编写数据库连接池的相关内容,本项目暂未考虑使用数据库

  • Python+unittest+requests+excel实现接口自动化测试框架

    环境:python3 + unittest + requests Excel管理测试用例, HTMLTestRunner生成测试报告 测试完成后邮件发送测试报告 jsonpath方式做预期结果数据处理,后期多样化处理 后期扩展,CI持续集成 发送邮件效果: 项目整体结构: common模块代码 class IsInstance: def get_instance(self, value, check): flag = None if isinstance(value, str): if chec

  • python unittest单元测试的步骤分析

    说明 1.导入unittest模块. 2.导入被测对象. 3.创建测试类unittest.TestCase. 4.重写setUp和tearDown(如果有初始化和结束) 5.以函数的形式写测试项目:函数名以test_开头. 6.unittest运行测试用例. 实例 import unittest class LoginCheckTest(unittest.TestCase): def setUp(self) -> None: print("每条用例") def test_logi

  • Python unittest 自动识别并执行测试用例方式

    自动化测试执行的用例有很多,python额测试用例文件,都是以"test"开头的. TestLoader(defaultTestLoader)是unittest的测试用例加载器,它包括多个加载测试用例的方法.它的结果是返回一个测试套件.本文介绍discover()用法与功能 结构: discover(start_dir, pattern='test*.py', top_level_dir=None) 作用:找到指定目录下所有测试用例模块,并递归查询子目录下的测试模块,找到匹配的文件进行

随机推荐