unittest入门并引入多线程

unittest入门并引入多线程和HTMLTestRunner

郑重声明: 文案全是抄的,因为这篇文章是我见过这一块写的最清晰明了层次分明的。只有练习代码是自己写的,相当于灵魂是人家的。

传送门

为什么要使用unittest?

在编写接口自动化用例时,我们一般针对一个接口建立一个.py文件,一条测试用例封装为一个函数(方法),但是在批量执行的过程中,如果其中一条出错,后面的用例就无法执行。使用测试框架可以互不影响的用例执行及更灵活的执行控制

unittest特点

python自带的单元测试框架,无需安装

用例执行互不干扰

提供不同范围的setUp(测试准备)和tearDown(测试清理)方法

提供丰富的断言方法

可以通过discover批量执行所有模块的用例

可以通过TestSuite(测试集)灵活的组织用例

unittest几大组成部分

TestCase: 用例对象,编写测试用例时要继承该类,以具有TestCase的属性和方法

TestSuite: 测试集或测试套件,测试用例的集合,用来组织用例,支持嵌套

TestLoader: 用例加载器,用于向TestSuite中添加用例

TextTestRunner: 用例执行器(输出文本结果),一般以TestSuite为单位执行用例

TestResult: 测试结果

用例编写

新建一个test_开头(必须)的.py文件,如test_user_login.py

导入unittest

编写一个Test开头(必须)的类,并继承unittest.TestCase,做为测试类

在类中编写一个test_开头(必须)的方法,作为用例

用例断言

unittest提供了丰富的断言方法,常用为以下几种:

注:既然是unittest提供的方法,我们就不能直接调用,而是在前端加上self或者cls,但是没人会在setUp或者tearDown里加判断吧,不会吧不会吧。

判断相等

assertEqual(a,b)/assertNotEqual(a,b): 断言值是否相等

assertIs(a,b)/assertIsNot(a,b): 断言是否同一对象(内存地址一样)

assertListEqual(list1, list2)/assertItemNotEqual(list1, list2): 断言列表是否相等

assertDictEqual(dict1, dict2)/assertDictNotEqual(dict1, dict2): 断言字典是否相等

是否为空

assertIsNone(a)/assertIsNotNone(a)

判断真假

assertTrue(a)/assertFalse(a)

是否包含

assertIn(a,b)/assertNotIn(a,b) # b中是否包含a

大小判断

assertGreater(a,b)/assertLess(a,b) : 断言a>b / 断言a<b
assertGreaterEqual(a,b)/assertLessEqual: 断言a>=b / 断言a<=b

类型判断

assertIsInstance(a,dict)/assertNotIsInstance(a,list) # 断言a为字典 / 断言a非列表

Test Fixtures(用例包裹方法)

setUp()/tearDown(): 每个用例执行前/后执行一次

setUpClass()/tearDownClass(): 每个测试类加载时/结束时执行一次

setUpMoudle()/tearDownMoudle(): 每个测试模块(一个py文件为一个模块)加载/结束时执行一次(这个不常用,至少我不常用,也可能是我用的比较少)

基于上述的文案,我们来写一下测试用例

用例组织及运行

"""
@File    :   test_unittest_czb.py    
@Contact :   xxx@xxx.com
@Modify Time      @Author    @Version
------------      -------    --------    
2021/7/26 2:07 下午   xxx        1.0
@Desciption: 演示unittest基本用法
"""
import unittest


class Czb_Test_One(unittest.TestCase):
    @classmethod  # 声明为类方法(必须)
    def setUpClass(cls):  # 类方法,注意后面是cls,整个类只执行一次
        print("在类执行前调用,一般用来初始化环境的,比如说可以用来初始化driver")

    @classmethod
    def tearDownClass(cls):
        print("在类执行完成后调用,一般用来清理环境,不过我一般都是用来打印一句话,提示一下用例执行完了就可以了")

    def setUp(self):  # 每个用例执行前先执行setUp里的内容
        print("setUp(self)")

    def tearDown(self):
        print("tearDown(self)")

    def test_one(self):
        print("test_one")

    def test_two(self):
        print("test_two")


class Czb_Test_Two(unittest.TestCase):
    def test_three(self):
        print("test_three")


if __name__ == "__main__":
    unittest.main()

结果

在类执行前调用,一般用来初始化环境的,比如说可以用来初始化driver
setUp(self)
test_one
tearDown(self)
setUp(self)
test_two
tearDown(self)
在类执行完成后调用,一般用来清理环境,不过我一般都是用来打印一句话,提示一下用例执行完了就可以了
test_three

如何组织测试用例

除了使用unittest.main()运行整个测试类之外,我们还可以通过TestSuite来灵活的组织要运行的测试集

新建TestSuite并添加测试用例

"""
@File    :   test_unittest_czb.py    
@Contact :   xxx@xxx.com
@Modify Time      @Author    @Version
------------      -------    --------    
2021/7/26 2:07 下午   xxx        1.0
@Desciption: 演示unittest基本用法
"""
import HTMLTestReportCN
import os
import unittest


class Czb_Test_One(unittest.TestCase):
    @classmethod  # 声明为类方法(必须)
    def setUpClass(cls):  # 类方法,注意后面是cls,整个类只执行一次
        print("在类执行前调用,一般用来初始化环境的,比如说可以用来初始化driver")
        html_file = os.path.join(os.path.join(os.path.dirname(__file__), "report"), "report.html")
        print(html_file)

    @classmethod
    def tearDownClass(cls):
        print("在类执行完成后调用,一般用来清理环境,不过我一般都是用来打印一句话,提示一下用例执行完了就可以了")

    def setUp(self):  # 每个用例执行前先执行setUp里的内容
        print("setUp(self)")

    def tearDown(self):
        print("tearDown(self)")

    def test_one(self):
        print("test_one")

    def test_two(self):
        print("test_two")


class Czb_Test_Two(unittest.TestCase):
    def test_three(self):
        print("test_three")


if __name__ == "__main__":
    suite = unittest.TestSuite()
    suite.addTest(Czb_Test_One("test_one"))
    suite.addTest(Czb_Test_One("test_two"))
    print("执行了没")
    unittest.TextTestRunner().run(suite)

使用discover(用例发现)遍历所有的用例

"""
@File    :   test_unittest_czb.py    
@Contact :   xxx@xxx.com
@Modify Time      @Author    @Version
------------      -------    --------    
2021/7/26 2:07 下午   xxx        1.0
@Desciption: 演示unittest基本用法
"""
import os
import threading
import unittest

from util import HTMLTestRunner


class Czb_Test_One(unittest.TestCase):
    @classmethod  # 声明为类方法(必须)
    def setUpClass(cls):  # 类方法,注意后面是cls,整个类只执行一次
        print("在类执行前调用,一般用来初始化环境的,比如说可以用来初始化driver")

    @classmethod
    def tearDownClass(cls):
        print("在类执行完成后调用,一般用来清理环境,不过我一般都是用来打印一句话,提示一下用例执行完了就可以了")

    def setUp(self):  # 每个用例执行前先执行setUp里的内容
        print("setUp(self)")

    def tearDown(self):
        print("tearDown(self)")

    def test_one(self):
        print("test_one")

    def test_two(self):
        print("test_two")
        self.assertEqual(1, 1, "数据错误")


class Czb_Test_Two(unittest.TestCase):
    def test_three(self):
        print("test_three")


def get_suite():
    # 遍历当前目录及子包中所有test_*.py中所有unittest用例
    suite = unittest.defaultTestLoader.discover("./")
    print("执行了没")
    unittest.TextTestRunner().run(suite)


if __name__ == "__main__":
    get_suite()

结果

在类执行前调用,一般用来初始化环境的,比如说可以用来初始化driver
setUp(self)
test_one
tearDown(self)
setUp(self)
test_two
tearDown(self)
在类执行完成后调用,一般用来清理环境,不过我一般都是用来打印一句话,提示一下用例执行完了就可以了
test_three


Ran 3 tests in 0.009s

OK

使用TestLoader(用例加载器)生成测试集

"""
@File    :   test_unittest_czb.py    
@Contact :   xxx@xxx.com
@Modify Time      @Author    @Version
------------      -------    --------    
2021/7/26 2:07 下午   xxx        1.0
@Desciption: 演示unittest基本用法
"""
import os
import threading
import unittest

from util import HTMLTestRunner


class Czb_Test_One(unittest.TestCase):
    @classmethod  # 声明为类方法(必须)
    def setUpClass(cls):  # 类方法,注意后面是cls,整个类只执行一次
        print("在类执行前调用,一般用来初始化环境的,比如说可以用来初始化driver")

    @classmethod
    def tearDownClass(cls):
        print("在类执行完成后调用,一般用来清理环境,不过我一般都是用来打印一句话,提示一下用例执行完了就可以了")

    def setUp(self):  # 每个用例执行前先执行setUp里的内容
        print("setUp(self)")

    def tearDown(self):
        print("tearDown(self)")

    def test_one(self):
        print("test_one")

    def test_two(self):
        print("test_two")
        self.assertEqual(1, 1, "数据错误")


class Czb_Test_Two(unittest.TestCase):
    def test_three(self):
        print("test_three")


def get_suite():
    # 加载该测试类所有用例并生成测试集
    suite = unittest.TestLoader.loadTestsFromTestCase(Czb_Test_One)
    print("执行了没")
    unittest.TextTestRunner().run(suite)


if __name__ == "__main__":
    get_suite()

如何使用HTMLTestRunner

HTMLTestRunner的导入

直接将文件复制到项目的某一个用来做工具包的路径下即可

HTMLTestRunner的使用

"""
@File    :   test_unittest_czb.py    
@Contact :   xxx@xxx.com
@Modify Time      @Author    @Version
------------      -------    --------    
2021/7/26 2:07 下午   xxx        1.0
@Desciption: 演示unittest基本用法
"""
import HTMLTestReportCN
import os
import unittest


class Czb_Test_One(unittest.TestCase):
    @classmethod  # 声明为类方法(必须)
    def setUpClass(cls):  # 类方法,注意后面是cls,整个类只执行一次
        print("在类执行前调用,一般用来初始化环境的,比如说可以用来初始化driver")
        html_file = os.path.join(os.path.join(os.path.dirname(__file__), "report"), "report.html")
        print(html_file)

    @classmethod
    def tearDownClass(cls):
        print("在类执行完成后调用,一般用来清理环境,不过我一般都是用来打印一句话,提示一下用例执行完了就可以了")

    def setUp(self):  # 每个用例执行前先执行setUp里的内容
        print("setUp(self)")

    def tearDown(self):
        print("tearDown(self)")

    def test_one(self):
        print("test_one")

    def test_two(self):
        print("test_two")


class Czb_Test_Two(unittest.TestCase):
    def test_three(self):
        print("test_three")


if __name__ == "__main__":
    suite = unittest.TestSuite()
    suite.addTest(Czb_Test_One("test_one"))
    suite.addTest(Czb_Test_One("test_two"))
    print("执行了没")
    # unittest.TextTestRunner().run(suite)
    # HTMLTestRunner后面是html文件的流
    html_file = os.path.join(os.path.join(os.path.dirname(__file__), "report"), "report.html")
    # html_file = os.path.join(os.path.dirname(__file__), "report.html")
    with open(html_file, 'wb') as f:
        HTMLTestReportCN.HTMLTestRunner(f).run(suite)

结果

在类执行前调用,一般用来初始化环境的,比如说可以用来初始化driver
/Users/zc/PycharmProjects/jiaoshan/report/report.html
setUp(self)
test_one
tearDown(self)
setUp(self)
test_two
tearDown(self)
在类执行完成后调用,一般用来清理环境,不过我一般都是用来打印一句话,提示一下用例执行完了就可以了


Ran 2 tests in 0.002s

OK

Process finished with exit code 0

没有生成日志文件怎么办

执行完了我们发现没有生成日志文件,仔细看在main里的“执行了没”也没打印,说明根本就是只执行了测试用例没有执行生成文件的操作.

路灯

引入多线程

我们知道了多线程的target是一个方法名,那么我们就可以把我们unittest组织测试用例的步骤封装成一个方法,然后放到多线程里

"""
@File    :   test_unittest_czb.py    
@Contact :   xxx@xxx.com
@Modify Time      @Author    @Version
------------      -------    --------    
2021/7/26 2:07 下午   xxx        1.0
@Desciption: 演示unittest基本用法
"""
import os
import threading
import unittest

from util import HTMLTestRunner


class Czb_Test_One(unittest.TestCase):
    @classmethod  # 声明为类方法(必须)
    def setUpClass(cls):  # 类方法,注意后面是cls,整个类只执行一次
        print("在类执行前调用,一般用来初始化环境的,比如说可以用来初始化driver")
        html_file = os.path.join(os.path.join(os.path.dirname(__file__), "report"), "report.html")
        print(html_file)

    @classmethod
    def tearDownClass(cls):
        print("在类执行完成后调用,一般用来清理环境,不过我一般都是用来打印一句话,提示一下用例执行完了就可以了")

    def setUp(self):  # 每个用例执行前先执行setUp里的内容
        print("setUp(self)")

    def tearDown(self):
        print("tearDown(self)")

    def test_one(self):
        print("test_one")

    def test_two(self):
        print("test_two")
        self.assertEqual(1, 2, "数据错误")


class Czb_Test_Two(unittest.TestCase):
    def test_three(self):
        print("test_three")


def get_suite(z):
    suite = unittest.TestSuite()
    suite.addTest(Czb_Test_One("test_one"))
    suite.addTest(Czb_Test_One("test_two"))
    print("执行了没")
    # unittest.TextTestRunner().run(suite)
    # HTMLTestRunner后面是html文件的流
    # 由于z是一个数字,所以要转化成string
    html_file = os.path.join(os.path.join(os.path.dirname(__file__), "report"), "report"+str(z)+".html")
    with open(html_file, 'wb') as f:
        HTMLTestRunner.HTMLTestRunner(f).run(suite)


if __name__ == "__main__":
    threads = []
    for i in range(4):
        # 为什么要加一个逗号,这个args不是必传的,如果没有参数的话也可以不传
        # https://blog.csdn.net/yasyal515/article/details/86014915
        # 这里记得不要写成threading.Thread(target=get_suite())
        t = threading.Thread(target=get_suite, args=(i, ))
        threads.append(t)
    for j in threads:
        j.start()

这样就可以生成三个文件了

附件:实测可用的HTMLTestRunner文件

"""
A TestRunner for use with the Python unit testing framework. It
generates a HTML report to show the result at a glance.

The simplest way to use this is to invoke its main method. E.g.

    import unittest
    import HTMLTestRunner

    ... define your tests ...

    if __name__ == '__main__':
        HTMLTestRunner.main()


For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.

    # output to a file
    fp = file('my_report.html', 'wb')
    runner = HTMLTestRunner.HTMLTestRunner(
                stream=fp,
                title='My unit test',
                description='This demonstrates the report output by HTMLTestRunner.'
                )

    # Use an external stylesheet.
    # See the Template_mixin class for more customizable options
    runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'

    # run the test
    runner.run(my_test_suite)


------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may be
  used to endorse or promote products derived from this software without
  specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

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

__author__ = "Wai Yip Tung"
__version__ = "0.8.2"


"""
Change History

Version 0.8.2
* Show output inline instead of popup window (Viorel Lupu).

Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.

Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.

Version in 0.7.1
* Back port to Python 2.3 (Frank Horowitz).
* Fix missing scroll bars in detail log (Podi).
"""

# TODO: color stderr
# TODO: simplify javascript using ,ore than 1 class in the class attribute?

import datetime
import 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,'&amp;');
    s = s.replace(/</g,'&lt;');
    s = s.replace(/>/g,'&gt;');
    return s;
}

/* obsoleted by detail in <div>
function showOutput(id, name) {
    var w = window.open("", //url
                    name,
                    "resizable,scrollbars,status,width=800,height=450");
    d = w.document;
    d.write("<pre>");
    d.write(html_escape(output_list[id]));
    d.write("\n");
    d.write("<a href='javascript:window.close()'>close</a>\n");
    d.write("</pre>\n");
    d.close();
}
*/
--></script>

%(heading)s
%(report)s
%(ending)s

</body>
</html>
"""
    # variables: (title, generator, stylesheet, heading, report, ending)


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

    STYLESHEET_TMPL = """
<style type="text/css" media="screen">
body        { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
table       { font-size: 100%; }
pre         { }

/* -- heading ---------------------------------------------------------------------- */
h1 {
	font-size: 16pt;
	color: gray;
}
.heading {
    margin-top: 0ex;
    margin-bottom: 1ex;
}

.heading .attribute {
    margin-top: 1ex;
    margin-bottom: 0;
}

.heading .description {
    margin-top: 4ex;
    margin-bottom: 6ex;
}

/* -- css div popup ------------------------------------------------------------------------ */
a.popup_link {
}

a.popup_link:hover {
    color: red;
}

.popup_window {
    display: none;
    position: relative;
    left: 0px;
    top: 0px;
    /*border: solid #627173 1px; */
    padding: 10px;
    background-color: #E6E6D6;
    font-family: "Lucida Console", "Courier New", Courier, monospace;
    text-align: left;
    font-size: 8pt;
    width: 500px;
}

}
/* -- report ------------------------------------------------------------------------ */
#show_detail_line {
    margin-top: 3ex;
    margin-bottom: 1ex;
}
#result_table {
    width: 80%;
    border-collapse: collapse;
    border: 1px solid #777;
}
#header_row {
    font-weight: bold;
    color: white;
    background-color: #777;
}
#result_table td {
    border: 1px solid #777;
    padding: 2px;
}
#total_row  { font-weight: bold; }
.passClass  { background-color: #6c6; }
.failClass  { background-color: #c60; }
.errorClass { background-color: #c00; }
.passCase   { color: #6c6; }
.failCase   { color: #c60; font-weight: bold; }
.errorCase  { color: #c00; font-weight: bold; }
.hiddenRow  { display: none; }
.testcase   { margin-left: 2em; }


/* -- ending ---------------------------------------------------------------------- */
#ending {
}

</style>
"""



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

    HEADING_TMPL = """<div class='heading'>
<h1>%(title)s</h1>
%(parameters)s
<p class='description'>%(description)s</p>
</div>

""" # variables: (title, parameters, description)

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



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

    REPORT_TMPL = """
<p id='show_detail_line'>Show
<a href='javascript:showCase(0)'>Summary</a>
<a href='javascript:showCase(1)'>Failed</a>
<a href='javascript:showCase(2)'>All</a>
</p>
<table id='result_table'>
<colgroup>
<col align='left' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
</colgroup>
<tr id='header_row'>
    <td>Test Group/Test case</td>
    <td>Count</td>
    <td>Pass</td>
    <td>Fail</td>
    <td>Error</td>
    <td>View</td>
</tr>
%(test_list)s
<tr id='total_row'>
    <td>Total</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td>&nbsp;</td>
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)

    REPORT_CLASS_TMPL = r"""
<tr class='%(style)s'>
    <td>%(desc)s</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)


    REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>

    <!--css div popup start-->
    <a class="popup_link" οnfοcus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
        %(status)s</a>

    <div id='div_%(tid)s' class="popup_window">
        <div style='text-align: right; color:red;cursor:pointer'>
        <a οnfοcus='this.blur();' οnclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
           [x]</a>
        </div>
        <pre>
        %(script)s
        </pre>
    </div>
    <!--css div popup end-->

    </td>
</tr>
""" # variables: (tid, Class, style, desc, status)


    REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
    <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
    <td colspan='5' align='center'>%(status)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)


    REPORT_TEST_OUTPUT_TMPL = r"""
%(id)s: %(output)s
""" # variables: (id, output)



    # ------------------------------------------------------------------------
    # ENDING
    #

    ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""

# -------------------- The end of the Template class -------------------


TestResult = unittest.TestResult

class _TestResult(TestResult):
    # note: _TestResult is a pure representation of results.
    # It lacks the output and reporting ability compares to unittest._TextTestResult.

    def __init__(self, verbosity=1):
        TestResult.__init__(self)
        self.stdout0 = None
        self.stderr0 = None
        self.success_count = 0
        self.failure_count = 0
        self.error_count = 0
        self.verbosity = verbosity

        # result is a list of result in 4 tuple
        # (
        #   result code (0: success; 1: fail; 2: error),
        #   TestCase object,
        #   Test output (byte string),
        #   stack trace,
        # )
        self.result = []


    def startTest(self, test):
        TestResult.startTest(self, test)
        # just one buffer for both stdout and stderr
        self.outputBuffer = 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)
        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(mdule=None)

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值