• bbs社区项目实战


    项目目录结构规划:
    bbs:用于存放BBS项目的测试用例、测试报告和测试数据等。
    driver:用于存放浏览器驱动。如selenium-server-standalone-2.47.0.jar、chromedriver.exe、IEDriverServer.exe等。在执行测试前根据执行场景将浏览器驱动复制到系统环境变量path目录下。
    package:用于存放自动化所用到的扩展包。例如,HTMLTestRunner.py属于一个单独模块,并且对其做了修改,所以,在执行测试前需要将它复制到Python的Lib目录下。
    run_bbs_test.py:项目主程序。用来运行社区(BBS)自动化用例。
    startup.bat:用于启动Selenium Server,默认启动driver目录下的selenium-server-standalone-2.47.0.jar。
    自动化测试项目说明文档.docx:介绍当前项目的架构、配置和使用说明。
    data:该目录用来存放测试相关的数据。
    report:用于存放HTML测试报告。其下面创建了image目录用于存放测试过程中的截图。
    test_case:测试用例目录,用于存放测试用例及相关模块。
    models:该目录下存放了一些公共的配置函数及公共类。
    page_obj:该目录用于存放测试用例的页面对象(Page Object)。根据自定义规则,以“*Page.py”命名的文件为封装的页面对象文件。
    *stapy:测试用例文件。根据测试文件匹配规则,以“*_sta.py”命名的文件将被当作自动化测试用例执行。

     1、定义浏览器驱动函数browser(),该函数可以进行配置,根据我们的需求,配置测试用例在不同的主机及浏览器下运行。

    from selenium.webdriver import Remote
    from selenium import webdriver

    #启动浏览器驱动:
    def browser():
    driver = webdriver.Chrome()
    host = "127.0.0.1:4444"
    dc = {"browserName":"chrome"} #指定浏览器(chrome','firefox',)
    driver = Remote(command_executor="http://" + host + "/wd/hub",desired_capabilities=dc)
    return driver

    if __name__ == '__main__':
    dr = browser()
    dr.get("http://www.baidu.com")
    dr.quit()

    2、自定义测试框架类myunit.py,定义MyTest()类用于继承unitest.TestCase类,因为笔者创建的所有测试类中setUp)与tearDown)方法所做的事情相同,所以,将它们抽象为MyTest()类,好处就是在编写测试用例时不再考虑这两个方法的实现。

    from selenium import webdriver
    from .driver import browser
    import unittest
    import os

    class MyTest(unittest.TestCase):
    def setUp(self):
    self.driver = browser()
    self.driver.implicitly_wait(10)
    self.driver.maximize_window()

    def tearDown(self):
    self.driver.quit()

    3、定义截图函数function.py,创建截图函数insert_img(),为了保持自动化项目的移植性,采用相对路径的方式将测试截图保存到.(reportimage目录中。

    from selenium import webdriver
    import os
    #截图函数:
    def insert_img(driver,file_name):
    base_dir = os.path.dirname(os.path.dirname(__file__))
    base_dir = str(base_dir)
    base_dir = base_dir.replace("\","/")
    base = base_dir.split("/test_case")[0]
    file_path = base + "/report/image/" + file_name
    driver.get_screenshot_as_file(file_path)

    if __name__ == '__main__':
    driver = webdriver.Chrome()
    driver.get("https://www.baidu.com")
    insert_img(driver,"baidu.jpg")
    driver.quit()

    4、创建基础Page基础类,创建页面基础类,通过__init__方法初始化参数:浏览器驱动、URL地址、超时时长等。定义基本方法:open()用于打开BBS地址;find element()和find elements()分别用来定位单个与多个元素;创建script)方法可以更简便地调用JavaScript代码。当然我们还可以对更多的WebDriver方法进行重定义。

    5、创建BBS登录对象类loginPage.py,创建登录页面对象,对用户登录面上的用户名/密码输入框、登录按钮和提示信息等元素的定位进行封装。除此之外,还创建user_login()方法作为系统统一登录的入口。关于对操作步骤的封装既可以放在Page Object当中,也可以放在测试用例当中,这个主要根据具体需求来衡量。这里之所以存放在Page Object当中,主要考虑到还有其他用例会调用到该登录方法。为username和password入参设置了默认值是为了方便其他用例在调用user_login)时不用再传递登录用户信息,因为该系统大多用例的执行使用该账号即可,同时也方便了在账号失效时的修改。

    from selenium.webdriver.common.action_chains import ActionChains
    from selenium.webdriver.common.by import By
    from .base import Page
    from time import sleep
    class login(Page):
    """
    用户登录界面
    """
    url = "/"
    bbs_login_user_loc = (By.XPATH,"//div[@id='mzCust']/div/img")
    bbs_login_button_loc = (By.ID,"mzLogin")
    def bbs_login(self):
    self.find_element(*self.bbs_login_user_loc).click()
    sleep(1)
    self.find_element(*self.bbs_login_button_loc).click()
    login_username_loc = (By.ID,"account")
    login_password_loc = (By.ID,"password")
    login_button_loc = (By.ID,"login")
    #登录用户名
    def login_username(self,username):
    self.find_element(*self.login_username_loc).send_keys(username)

    #登录密码:
    def login_password(self,password):
    self.find_element(*self.login_password_loc).send_keys(password)

    #登录按钮
    def login_button(self):
    self.find_element(*self.login_button_loc).click()

    #定义统一登录入口:
    def user_login(self,username = "username",password = "1111"):
    """获取用户名密码登录"""
    self.open()
    self.bbs_login()
    self.login_username(username)
    self.login_password(password)
    self.login_button()
    sleep(1)
    user_error_hint_loc = (By.XPATH,"//span[@for = 'account']")
    pawd_error_hint_loc = (By.XPATH,"//span[@for = 'password']")
    user_login_success_loc = (By.ID,"mzCustName")
    #用户名错误提示
    def user_error_hint(self):
    return self.find_element(*self.user_error_hint_loc).text
    #密码错误提示
    def pawd_error_hint(self):
    return self.find_element(*self.pawd_error_hint_loc).text
    #登录成功用户名
    def user_login_success(self):
    return self.find_element(*self.user_login_success_loc).text

    6、首先创建loginTest()类,继承myunit.MyTest)类,关于MyTest)类的实现,请翻看前面的代码。这样就省去在了在每个测试类中实现一遍setUp()和tearDown0方法。

    创建 user_login_verify)方法,并调用loginPage.py中定义的user_login)方法。为什么不直接调用呢?因为user_login)的入参已经设置了默认值,原因前面已经解释,这里需要重新将其入参的默认值设置为空即可。前三条测试用例很好理解,分别验证:
    ·用户名密码为空,点击登录;
    ·用户名正确,密码为空,点击登录;·用户名为空,密码正确,点击登录。
    第四条用例验证错误的用户名和密码登录。在当前系统中如果反复使用固定且错误的用户名和密码,系统会弹出验证码输入框。为了避免这种情况的发生,就需要用户名进行随机变化,此处的做法用固定的前缀“zhangsan”,末尾字符从a-z中随机一个字符与前缀进行拼接。
    第五条用例验证正确的用户名和密码登录,通过获取用户名作为断言信息。
    在上面的测试用例中,每条测试用例结束时都调用function.py文件中的insertimg函数进行截图。当用例运行完成后,打开.…/report/image/目录将会看到用例执行的截图文件,如图11.3所示。
    为了在测试用例运行过程中不影响做其他事,笔者选择调用远程主机或虚拟机来运行测试用例,那么这里就需要使用Selenium Grid(其包含在Selenium Server)来调用远程节创建../mztestprostartup.bat 文件,用于启动….mztestproldriver目录下的Selenium Server。
    from time import sleep
    import unittest,random,sys
    sys.path.append("./models")
    sys.path.append("./page_obj")
    from models import myunit,function
    from page_obj.loginPage import login
    class loginTest(myunit.MyTest):
    """社区登录测试"""
    #测试用户登录
    def user_login_verify(self,username = "",password = ""):
    login(self.driver).user_login(username,password)

    def test_login1(self):
    """用户名、密码为空"""
    self.user_login_verify()
    po = login(self.driver)
    self.assertEqual(po.user_error_hint(),"账号不能为空")
    self.assertEqual(po.pawd_error_hint(),"密码不能为空")
    function.insert_img(self.driver,"user_pawd_empty.jpg")

    def test_login2(self):
    """用户名正确、密码为空登录"""
    self.user_login_verify(username="pytest")
    po = login(self.driver)
    self.assertEqual(po.pawd_error_hint(),"密码不能为空")
    function.insert_img(self.driver,"pawd_empty.jpg")

    def test_login3(self):
    """用户名为空、密码为正确"""
    self.user_login_verify(password="abc123456")
    po = login(self.driver)
    self.assertEqual(po.user_error_hint(),"账号不能为空")
    function.insert_img(self.driver,"user_empty.jpg")

    def test_login4(self):
    """用户名与密码不匹配"""
    character = random.choice("zyxwvutsrqponmlkjihgfedcba")
    username = "zhangsan" + character
    self.user_login_verify(username = username,password="123456")
    po = login(self.driver)
    self.assertEqual(po.pawd_error_hint(),"账号与密码不匹配")
    function.insert_img(self.driver,"user_pawd_error.jpg")

    def test_login5(self):
    """用户名、密码正确"""
    self.user_login_verify(username = zhangsan,password="123456")
    sleep(2)
    po = login(self.driver)
    self.assertEqual(po.user_login_success(),"张三")
    function.insert_img(self.driver,"user_pawd_ture.jpg")

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

    7、双击strtup.bat文件,启动Selenium Server创建主hub节点。在远程主机或虚拟机中同样需要启动Selenium Server创建node节点,创建方式参考本书第9.3节。

    创建用例执行程序:….mztestpro/run_bbs_test.py
    from HTMLTestRunner import HTMLTestRunner
    from email.mime.text import MIMEText
    from email.header import Header
    import smtplib
    import unittest
    import time
    import os
    #定义发送邮件:
    def send_mail(file_new):
    f = open(file_new,"rb")
    mail_body = f.read()
    f.close()
    msg = MIMEText(mail_body,"html","utf-8")
    msg["Subject"] = Header("自动化测试报告","utf-8")
    smtp = smtplib.SMTP()
    smtp.connect("smtp.126.com")
    smtp.login("username@126.com","123456")
    smtp.sendmail("username@126.com","receive@126.com",msg.as_string())
    smtp.quit()
    print("email has send out !")

    #查找测试报告目录、找到最新生成的测试报告文件
    def new_report(testreport):
    lists = os.listdir(testreport)
    lists.sort(key=lambda fn:os.path.getmtime(testreport + "\" + fn))
    file_new = os.path.join(testreport,lists[-1])
    print(file_new)
    return file_new

    if __name__ == '__main__':
    now = time.strftime("%Y-%m-%d %H_%M_%S")
    filename = "./bbs/report/" + now + "result.html"
    fp = open(filename,"wb")
    runner = HTMLTestRunner(stream=fp,title="魅族社区自动化测试报告",description="环境:windows 7 浏览器:chrome")
    discover = unittest.defaultTestLoader.discover("./bbs/test_case",pattern="*_sta.py")
    runner.run(discover)
    fp.close() #关闭测试报告
    file_path = new_report("./bbs/report/") #查找新生成的报告
    #发送测试报告:
    send_mail(file_path)

    8、执行过程中并没有做任何改动,集成了HTMLTestRunner生成HTML测试报告,以及

    集成自动发邮件功能等。唯一需要注意的是,脚本中的路径建议使用相对路径,以便于项目被移动到任意目录下执行。
    打开.modelsdriver.py文件,修改脚本运行的节点及浏览器。现在可以通过运行run_bbs_test.py来执行测试项目了。
    """
    A TestRunner for use with the Python unit testing framework. It
    generates a HTML report to show the result at a glance.

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

    import unittest
    import HTMLTestRunner

    ... define your tests ...

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


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

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

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

    # run the test
    runner.run(my_test_suite)


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

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

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

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

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

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


    """
    Change History

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

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

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

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

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

    import datetime
    import 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(" ");
    d.write("<a href='javascript:window.close()'>close</a> ");
    d.write("</pre> ");
    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;

    font-family: "Lucida Console", "Courier New", Courier, monospace;
    text-align: left;
    font-size: 8pt;
    500px;
    }

    }
    /* -- report ------------------------------------------------------------------------ */
    #show_detail_line {
    margin-top: 3ex;
    margin-bottom: 1ex;
    }
    #result_table {
    80%;
    border-collapse: collapse;
    border: 1px solid #777;
    }
    #header_row {
    font-weight: bold;
    color: white;

    }
    #result_table td {
    border: 1px solid #777;
    padding: 2px;
    }
    #total_row { font-weight: bold; }
    .passClass { }
    .failClass { background-color: #c60; }
    .errorClass { }
    .passCase { color: #6c6; }
    .failCase { color: #c60; font-weight: bold; }
    .errorCase { color: #c00; font-weight: bold; }
    .hiddenRow { display: none; }
    .testcase { margin-left: 2em; }


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

    </style>
    """



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

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

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

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



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

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

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


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

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

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

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


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


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



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

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

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


    TestResult = unittest.TestResult

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

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

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


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


    class HTMLTestRunner(Template_mixin):
    """
    """
    def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
    self.stream = stream
    self.verbosity = verbosity
    if title is None:
    self.title = self.DEFAULT_TITLE
    else:
    self.title = title
    if description is None:
    self.description = self.DEFAULT_DESCRIPTION
    else:
    self.description = description

    self.startTime = datetime.datetime.now()


    def run(self, test):
    "Run the given test case or test suite."
    result = _TestResult(self.verbosity)
    test(result)
    self.stopTime = datetime.datetime.now()
    self.generateReport(test, result)
    print(sys.stderr, ' Time Elapsed: %s' % (self.stopTime-self.startTime))
    return result


    def sortResult(self, result_list):
    # unittest does not seems to run in any particular order.
    # Here at least we want to group them together by class.
    rmap = {}
    classes = []
    for n,t,o,e in result_list:
    cls = t.__class__
    if not 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(" ")[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 and mess up formating
    # uo = unicode(o.encode('string_escape'))
    uo = e
    else:
    uo = o
    if isinstance(e,str):
    # TODO: some problem with 'string_escape': it escape and mess up formating
    # ue = unicode(e.encode('string_escape'))
    ue = e
    else:
    ue = e

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

    row = tmpl % dict(
    tid = tid,
    Class = (n == 0 and 'hiddenRow' or 'none'),
    style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
    desc = desc,
    script = script,
    status = self.STATUS[n],
    )
    rows.append(row)
    if not has_output:
    return

    def _generate_ending(self):
    return self.ENDING_TMPL


    ##############################################################################
    # Facilities for running tests from the command line
    ##############################################################################

    # Note: Reuse unittest.TestProgram to launch test. In the future we may
    # build our own launcher to support more specific command line
    # parameters like test title, CSS, etc.
    class TestProgram(unittest.TestProgram):
    """
    A variation of the unittest.TestProgram. Please refer to the base
    class for command line parameters.
    """
    def runTests(self):
    # Pick HTMLTestRunner as the default test runner.
    # base class's testRunner parameter is not useful because it means
    # we have to instantiate HTMLTestRunner before we know self.verbosity.
    if self.testRunner is None:
    self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
    unittest.TestProgram.runTests(self)

    main = TestProgram

    ##############################################################################
    # Executing this module from the command line
    ##############################################################################

    if __name__ == "__main__":
    main(module=None)

  • 相关阅读:
    字符串里输出字符c的所有位置
    python时间戳
    python之set()和issubset()方法
    python之判断键是否存在于字典中
    python之方法与函数的区别,及其传参
    接口测试之requests
    python之isinstance()函数
    MySQL创建表时,被``和''坑了很久
    游标位置self.cur.scroll(0, mode='absolute')
    python操作MySQL数据库
  • 原文地址:https://www.cnblogs.com/zhang-da/p/12210468.html
Copyright © 2020-2023  润新知