master c72fde29c2d5 cached
6 files
50.2 KB
13.5k tokens
46 symbols
1 requests
Download .txt
Repository: huilansame/HTMLTestRunner_PY3
Branch: master
Commit: c72fde29c2d5
Files: 6
Total size: 50.2 KB

Directory structure:
gitextract_v0a07hph/

├── .gitignore
├── ExampleReport.html
├── HTMLTestRunner_PY3.py
├── README.md
├── __init__.py
└── test.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================

.idea/encodings.xml
.idea/HTMLTestRunner_PY3.iml
.idea/modules.xml
.idea/modules.xml
.idea/misc.xml
.idea/misc.xml
.idea/workspace.xml
.idea/vcs.xml


================================================
FILE: ExampleReport.html
================================================
<?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>Example用例执行报告</title>
    <meta name="generator" content="HTMLTestRunner 0.9.1"/>
    <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="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> -->
    
    
<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>

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

    <div id="div_base">
        
    <div class='page-header'>
        <h1>Example用例执行报告</h1>
    <p class='attribute'><strong>开始时间:</strong> 2017-12-28 14:58:27</p>
<p class='attribute'><strong>运行时长:</strong> 0:00:00.003002</p>
<p class='attribute'><strong>状态:</strong> 通过 5 失败 4 错误 2</p>

    </div>
    <div style="float: left;width:50%;"><p class='description'>用于展示修改样式后的HTMLTestRunner</p></div>
    <div id="chart" style="width:50%;height:400px;float:left;"></div>

        
    <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>
        
    <tr class='errorClass'>
        <td>TestTest:  测试HTMLTestRunner </td>
        <td>7</td>
        <td>3</td>
        <td>3</td>
        <td>1</td>
        <td><a href="javascript:showClassDetail('c1',7)">详情</a></td>
    </tr>

<tr id='pt1.1' class='hiddenRow'>
    <td class='none'><div class='testcase'>test_a_divide_c: a / c = 1 这是个有subTest的用例</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_pt1.1')" >
        通过</a>

    <div id='div_pt1.1' class="popup_window">
        <pre>pt1.1: a / c = 1
a / c = 1
a / c = 1

SubTestCase Pass:
test_a_divide_c (__main__.TestTest) (i=1)</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

<tr id='ft1.2' class='none'>
    <td class='failCase'><div class='testcase'>test_a_divide_c: a / c = 1 这是个有subTest的用例</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_ft1.2')" >
        失败</a>

    <div id='div_ft1.2' class="popup_window">
        <pre>ft1.2: a / c = 1
a / c = 1
a / c = 1

SubTestCase Failed:
test_a_divide_c (__main__.TestTest) (i=2)Traceback (most recent call last):
  File "E:/PY/HTMLTestRunner_PY3/test.py", line 32, in test_a_divide_c
    self.assertEqual(self.a / i, 1)
AssertionError: 0.5 != 1
</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

<tr id='ft1.3' class='none'>
    <td class='failCase'><div class='testcase'>test_a_divide_c: a / c = 1 这是个有subTest的用例</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_ft1.3')" >
        失败</a>

    <div id='div_ft1.3' class="popup_window">
        <pre>ft1.3: a / c = 1
a / c = 1
a / c = 1

SubTestCase Failed:
test_a_divide_c (__main__.TestTest) (i=3)Traceback (most recent call last):
  File "E:/PY/HTMLTestRunner_PY3/test.py", line 32, in test_a_divide_c
    self.assertEqual(self.a / i, 1)
AssertionError: 0.3333333333333333 != 1
</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

<tr id='ft1.4' class='none'>
    <td class='errorCase'><div class='testcase'>test_a_error_case: 除零异常</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_ft1.4')" >
        错误</a>

    <div id='div_ft1.4' class="popup_window">
        <pre>ft1.4: 1/0
Traceback (most recent call last):
  File "E:/PY/HTMLTestRunner_PY3/test.py", line 37, in test_a_error_case
    self.assertEqual(self.a/0, 1)
ZeroDivisionError: division by zero
</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

<tr id='ft1.5' class='none'>
    <td class='failCase'><div class='testcase'>test_a_minus_b: a - b = 3 这个用例应该失败</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_ft1.5')" >
        失败</a>

    <div id='div_ft1.5' class="popup_window">
        <pre>ft1.5: a - b = 3
Traceback (most recent call last):
  File "E:/PY/HTMLTestRunner_PY3/test.py", line 20, in test_a_minus_b
    self.assertEqual(self.a-self.b, 3)
AssertionError: -1 != 3
</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

<tr id='pt1.6' class='hiddenRow'>
    <td class='none'><div class='testcase'>test_a_multi_b: a * b = 2 这个用例应该成功</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_pt1.6')" >
        通过</a>

    <div id='div_pt1.6' class="popup_window">
        <pre>pt1.6: a * b = 2
</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

<tr id='pt1.7' class='hiddenRow'>
    <td class='none'><div class='testcase'>test_a_plus_b: a + b = 3 这个用例应该通过</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_pt1.7')" >
        通过</a>

    <div id='div_pt1.7' class="popup_window">
        <pre>pt1.7: a + b = 3
</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

    <tr class='failClass'>
        <td>ExampleCase1: 此class包含两个用例:add - ok, minus - FAIL</td>
        <td>2</td>
        <td>1</td>
        <td>1</td>
        <td>0</td>
        <td><a href="javascript:showClassDetail('c2',2)">详情</a></td>
    </tr>

<tr id='pt2.1' class='hiddenRow'>
    <td class='none'><div class='testcase'>test_add: 用例1,add,此用例成功通过</div></td>
    <td colspan='5' align='center'>通过</td>
</tr>

<tr id='ft2.2' class='none'>
    <td class='failCase'><div class='testcase'>test_minus: 用例2,minus,此用例执行失败,4-3!=2</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_ft2.2')" >
        失败</a>

    <div id='div_ft2.2' class="popup_window">
        <pre>ft2.2: 中文方法反反复复凤飞飞反复
Traceback (most recent call last):
  File "E:/PY/HTMLTestRunner_PY3/test.py", line 53, in test_minus
    self.assertEqual(self.a - self.b, 2)
AssertionError: 1 != 2
</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

    <tr class='errorClass'>
        <td>ExampleCase2: 此class包含一个用例:plus - ERROR</td>
        <td>1</td>
        <td>0</td>
        <td>0</td>
        <td>1</td>
        <td><a href="javascript:showClassDetail('c3',1)">详情</a></td>
    </tr>

<tr id='ft3.1' class='none'>
    <td class='errorCase'><div class='testcase'>test_plus: 用例3,plus,此用例执行出错,因为c未定义</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_ft3.1')" >
        错误</a>

    <div id='div_ft3.1' class="popup_window">
        <pre>ft3.1: Traceback (most recent call last):
  File "E:/PY/HTMLTestRunner_PY3/test.py", line 63, in test_plus
    self.assertEqual(self.a * self.b, c)
NameError: name 'c' is not defined
</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

    <tr class='passClass'>
        <td>ExampleCase3: 此class包含一个用例:divide - ok</td>
        <td>1</td>
        <td>1</td>
        <td>0</td>
        <td>0</td>
        <td><a href="javascript:showClassDetail('c4',1)">详情</a></td>
    </tr>

<tr id='pt4.1' class='hiddenRow'>
    <td class='none'><div class='testcase'>test_devide: 用例4,divide,此用例执行成功</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_pt4.1')" >
        通过</a>

    <div id='div_pt4.1' class="popup_window">
        <pre>pt4.1: 我要打印输出
我要打印输出222
</pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>

        <tr id='total_row'>
            <td>总计</td>
            <td>11</td>
            <td>5</td>
            <td>4</td>
            <td>2</td>
            <td>&nbsp;</td>
        </tr>
    </table>

        <div id='ending'>&nbsp;</div>
        
    <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:5, name:'通过'},
                        {value:4, name:'失败'},
                        {value:2, name:'错误'}
                    ],
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        };

        // 使用刚指定的配置项和数据显示图表。
        myChart.setOption(option);
    </script>
    
    </div>
</body>
</html>


================================================
FILE: HTMLTestRunner_PY3.py
================================================
# -*- 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" 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


# ------------------------------------------------------------------------
# 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="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,'&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>

    <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" 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>&nbsp;</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)">详情</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">
        <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 = []
        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=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('\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'开始时间', 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)


================================================
FILE: README.md
================================================
# HTMLTestRunner_PY3

## 2017.09.08 统计图 0.9.1

用Echarts添加了一个统计图,如下图:

![测试执行情况统计图](./img/echarts.png)

**(2017.12.22) 修改统计饼图颜色**

**(2017.12.05)修改为使用cdn的方式,不用建文件夹导入js。**

~~需要echarts的js文件 `echarts.common.min.js` ,在报告文件夹下创建文件夹 `js` ,将该文件放入,生成报告就能看到该报告中的图表~~

## 2017.08 修改为PY3版本 0.9.0

基于之前对PY2版本HTMLTestRunner的修改(中文以及美化,[链接点我](http://download.csdn.net/download/huilan_same/9598558)),现在针对PY3做了修改,增加对subTest的支持

1. StringIO -> io
2. 去掉decode
3. 增加addSubTest()

```python
# import StringIO
import io
...
  def startTest(self, test):
          TestResult.startTest(self, test)
          # just one buffer for both stdout and stderr
          # self.outputBuffer = StringIO.StringIO()
          self.outputBuffer = io.StringIO()
...
# add
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('.')
...
  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)
          print('\nTime Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)
          return result
...
    # 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')
    #             uo = o.decode('utf-8')
    #         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')
    #             ue = e.decode('utf-8')
    #         else:
    #             ue = e

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

以上代码列出大部分主要修改。

如果有任何问题,可在此基础上再次进行修改。

关于PY3对subTest的支持,可以查看相关资料,在这里我的处理是:如果一个Case有使用subTest,则将此用例拆分成n个同级子用例,在report中是同级别展示的。




================================================
FILE: __init__.py
================================================


================================================
FILE: test.py
================================================
import unittest
from HTMLTestRunner_PY3 import HTMLTestRunner


class TestTest(unittest.TestCase):
    """ 测试HTMLTestRunner """
    def setUp(self):
        self.a = 1
        self.b = 2
        self.c = [1, 2, 3]

    def test_a_plus_b(self):
        """ a + b = 3 这个用例应该通过"""
        print('a + b = 3')
        self.assertEqual(self.a+self.b, 3)

    def test_a_minus_b(self):
        """ a - b = 3 这个用例应该失败 """
        print('a - b = 3')
        self.assertEqual(self.a-self.b, 3)

    def test_a_multi_b(self):
        """ a * b = 2 这个用例应该成功"""
        print('a * b = 2')
        self.assertEqual(self.a*self.b, 2)

    def test_a_divide_c(self):
        """ a / c = 1 这是个有subTest的用例"""
        for i in self.c:
            with self.subTest(i=i):
                print('a / c = 1')
                self.assertEqual(self.a / i, 1)

    def test_a_error_case(self):
        """ 除零异常 """
        print('1/0')
        self.assertEqual(self.a/0, 1)


class ExampleCase1(unittest.TestCase):
    """此class包含两个用例:add - ok, minus - FAIL"""
    def setUp(self):
        self.a = 4
        self.b = 3

    def test_add(self):
        """用例1,add,此用例成功通过"""
        self.assertEqual(self.a + self.b, 7)

    def test_minus(self):
        """用例2,minus,此用例执行失败,4-3!=2"""
        print('中文方法反反复复凤飞飞反复')
        self.assertEqual(self.a - self.b, 2)


class ExampleCase2(unittest.TestCase):
    """此class包含一个用例:plus - ERROR"""
    def setUp(self):
        self.a, self.b = 4, 3

    def test_plus(self):
        """用例3,plus,此用例执行出错,因为c未定义"""
        self.assertEqual(self.a * self.b, c)


class ExampleCase3(unittest.TestCase):
    """此class包含一个用例:divide - ok"""
    def setUp(self):
        self.a, self.b = 4, 2

    def test_devide(self):
        """用例4,divide,此用例执行成功"""
        print('我要打印输出')
        self.assertEqual(self.a / self.b, 2)


if __name__ == '__main__':
    report_title = 'Example用例执行报告'
    desc = '用于展示修改样式后的HTMLTestRunner'
    report_file = 'ExampleReport.html'

    testsuite = unittest.TestSuite()
    testsuite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestTest))
    testsuite.addTest(unittest.TestLoader().loadTestsFromTestCase(ExampleCase1))
    testsuite.addTest(unittest.TestLoader().loadTestsFromTestCase(ExampleCase2))
    testsuite.addTest(unittest.TestLoader().loadTestsFromTestCase(ExampleCase3))

    with open(report_file, 'wb') as report:
        runner = HTMLTestRunner(stream=report, title=report_title, description=desc)
        runner.run(testsuite)
Download .txt
gitextract_v0a07hph/

├── .gitignore
├── ExampleReport.html
├── HTMLTestRunner_PY3.py
├── README.md
├── __init__.py
└── test.py
Download .txt
SYMBOL INDEX (46 symbols across 2 files)

FILE: HTMLTestRunner_PY3.py
  class OutputRedirector (line 121) | class OutputRedirector(object):
    method __init__ (line 123) | def __init__(self, fp):
    method write (line 126) | def write(self, s):
    method writelines (line 129) | def writelines(self, lines):
    method flush (line 132) | def flush(self):
  class Template_mixin (line 143) | class Template_mixin(object):
  class _TestResult (line 565) | class _TestResult(TestResult):
    method __init__ (line 569) | def __init__(self, verbosity=1):
    method startTest (line 588) | def startTest(self, test):
    method complete_output (line 599) | def complete_output(self):
    method stopTest (line 611) | def stopTest(self, test):
    method addSuccess (line 617) | def addSuccess(self, test):
    method addError (line 630) | def addError(self, test, err):
    method addFailure (line 643) | def addFailure(self, test, err):
    method addSubTest (line 656) | def addSubTest(self, test, subtest, err):
  class HTMLTestRunner (line 701) | class HTMLTestRunner(Template_mixin):
    method __init__ (line 703) | def __init__(self, stream=sys.stdout, verbosity=1, title=None, descrip...
    method run (line 717) | def run(self, test):
    method sortResult (line 726) | def sortResult(self, result_list):
    method getReportAttributes (line 740) | def getReportAttributes(self, result):
    method generateReport (line 761) | def generateReport(self, test, result):
    method _generate_stylesheet (line 780) | def _generate_stylesheet(self):
    method _generate_heading (line 783) | def _generate_heading(self, report_attrs):
    method _generate_report (line 798) | def _generate_report(self, result):
    method _generate_chart (line 840) | def _generate_chart(self, result):
    method _generate_report_test (line 848) | def _generate_report_test(self, rows, cid, tid, n, t, o, e):
    method _generate_ending (line 874) | def _generate_ending(self):
  class TestProgram (line 885) | class TestProgram(unittest.TestProgram):
    method runTests (line 890) | def runTests(self):

FILE: test.py
  class TestTest (line 5) | class TestTest(unittest.TestCase):
    method setUp (line 7) | def setUp(self):
    method test_a_plus_b (line 12) | def test_a_plus_b(self):
    method test_a_minus_b (line 17) | def test_a_minus_b(self):
    method test_a_multi_b (line 22) | def test_a_multi_b(self):
    method test_a_divide_c (line 27) | def test_a_divide_c(self):
    method test_a_error_case (line 34) | def test_a_error_case(self):
  class ExampleCase1 (line 40) | class ExampleCase1(unittest.TestCase):
    method setUp (line 42) | def setUp(self):
    method test_add (line 46) | def test_add(self):
    method test_minus (line 50) | def test_minus(self):
  class ExampleCase2 (line 56) | class ExampleCase2(unittest.TestCase):
    method setUp (line 58) | def setUp(self):
    method test_plus (line 61) | def test_plus(self):
  class ExampleCase3 (line 66) | class ExampleCase3(unittest.TestCase):
    method setUp (line 68) | def setUp(self):
    method test_devide (line 71) | def test_devide(self):
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (56K chars).
[
  {
    "path": ".gitignore",
    "chars": 150,
    "preview": "\n.idea/encodings.xml\n.idea/HTMLTestRunner_PY3.iml\n.idea/modules.xml\n.idea/modules.xml\n.idea/misc.xml\n.idea/misc.xml\n.ide"
  },
  {
    "path": "ExampleReport.html",
    "chars": 15463,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xh"
  },
  {
    "path": "HTMLTestRunner_PY3.py",
    "chars": 29222,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"\nA TestRunner for use with the Python unit testing framework. It\ngenerates a HTML report to s"
  },
  {
    "path": "README.md",
    "chars": 4124,
    "preview": "# HTMLTestRunner_PY3\n\n## 2017.09.08 统计图 0.9.1\n\n用Echarts添加了一个统计图,如下图:\n\n![测试执行情况统计图](./img/echarts.png)\n\n**(2017.12.22) 修改"
  },
  {
    "path": "__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test.py",
    "chars": 2490,
    "preview": "import unittest\nfrom HTMLTestRunner_PY3 import HTMLTestRunner\n\n\nclass TestTest(unittest.TestCase):\n    \"\"\" 测试HTMLTestRun"
  }
]

About this extraction

This page contains the full source code of the huilansame/HTMLTestRunner_PY3 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (50.2 KB), approximately 13.5k tokens, and a symbol index with 46 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!