精华内容
下载资源
问答
  • web自动化测试框架

    2021-02-22 11:30:41
    web自动化测试框架 所谓框架,就是一种思想,一种整合的思想,就是将所做的事情的各个部分整合到一起构成一个框架。 使用selenium构建web自动化测试框架的流程 在项目中添加7个包:browserdriver、config、business...

    web自动化测试框架

    所谓框架,就是一种思想,一种整合的思想,就是将所做的事情的各个部分整合到一起构成一个框架。

    使用selenium构建web自动化测试框架的流程

    在项目中添加7个包:browserdriver、config、business、test_data、test_case、test_report、test_run

    browserdriver包

    browserdriver包:专门用来存放游览器驱动
    

    在这里插入图片描述

    config包

    config包:存放配置文件,config.ini、read_ini.py、fw_driver.py、fw_findelement.py
    config.ini:配置url和browser驱动
    read_ini.py:用来读取config.ini的文件
    fw_driver.py:用来调用驱动,进行一些初始化的操作,如打开游览器,使页面最大化等
    fw_findelement.py:封装了各种基本元素定位方式的一个框架
    

    config.ini

    [fwconfig]
    url=http://localhost/upload/forum.php
    browser=chrome
    

    read_ini.py

    import configparser
    import os
    #获取项目路径
    # print(os.path.dirname(os.path.dirname(__file__)))
    class fwReadIni():
    #初始化 路径
        # 参数1 file_name文件名指定文件名:例如config.ini就是文件名
        # 参数2  node 指定所需要节点名:例如config.ini里的jwconfig就是节点名
        #如果我没有传递文件和节点,那么就使用config.ini和jwconfig,如果传递了就使用传递过来的
        def __init__(self,file_name=None,node=None):
            self.file_name=file_name
            self.node=node
            if file_name==None:
                #文件名 加r 防止自动转义
                # os.path.dirname()用来获取文件的当前路径(即config包),套两层用来获取上一层路径,即项目路径
                self.file_name=os.path.dirname(os.path.dirname(__file__))+"\config\config.ini"
            if node==None:
                self.node = "fwconfig"
            self.cf=self.load_ini(self.file_name) #获取文件节点内容做解析
    
    #加载配置文件
        def load_ini(self,file_name):
            #获取解析配置对象
            cf=configparser.ConfigParser()
            cf.read(file_name, encoding='utf-8')
            return  cf
    #获取配置文件中的内容===>指定key,获取值
        def get_value(self,key):
            return  self.cf.get(self.node,key)
    
    #进行单元测试,验证上述代码的作用
    if __name__=='__main__':
        aa=fwReadIni()
        #也可以写成以下这种形式,使用传递过去的文件和节点而不是默认的
        # aa=jwReadIni(r"C:\Users\dinghan\PycharmProjects\dh0212\config\config.ini",'jwconfig')
        print(aa.get_value("browser")) #验证能否读取到配置文件中的内容
    
    
    

    fw_driver.py

    import os
    from time import sleep
    
    from selenium import webdriver
    
    from config.read_ini import fwReadIni
    
    #获取项目/工程路径
    parent_path=os.path.dirname(os.path.dirname(__file__))
    #读取配置文件对象
    fw=fwReadIni()
    #获取被测试网址
    url=fw.get_value("url")
    #获取火狐驱动
    def get_firefox_driver():
        driver_path=parent_path+"/browserdriver/geckodriver"
        driver=webdriver.Firefox(executable_path=driver_path)
        driver.get(url)
        driver.maximize_window()
        driver.implicitly_wait(3)
        sleep(3)
        return  driver
    #获取谷歌驱动
    def get_chrome_driver():
        driver_path = parent_path + "/browserdriver/chromedriver"
        driver=webdriver.Chrome(executable_path=driver_path)
        driver.implicitly_wait(3)
        driver.get(url)
        driver.maximize_window()
        sleep(3)
        return   driver
    #进行单元测试
    if __name__=='__main__':
        # get_chrome_driver()
        get_firefox_driver()
    

    fw_findelement.py

    import os
    from time import sleep
    
    from config.fw_driver import get_chrome_driver
    from config.read_ini import fwReadIni
    
    #封装各种定位方式,精简代码量
    #这个类在构造对象的时候要求有驱动(后面元素定位的时候要用到),
    class fwFindElement():
        def __init__(self,driver):
            self.driver=driver
    #获取元素
        def get_element(self,file=None,node=None,key=None):
            #读LocalElement.ini配置文件
            if file==None:
                file=os.path.dirname(os.path.dirname(__file__))+"/business/LocalElement.ini"
    
            if node==None:
                node="fwlogin_fatie"
            read_ini=fwReadIni(file,node)
            data=read_ini.get_value(key)
            #根据键得到值,分析值:
            #得出定位方式 文件中是以  > 方式切割
            by=data.split('>')[0]
            value=data.split('>')[1]
            try:
                if by=='id':#如果分析给的时id,则做id定位
                    return self.driver.find_element_by_id(value)
                elif  by=='name':
                    return self.driver.find_element_by_name(value)
                elif by=='className':
                    return self.driver.find_element_by_class_name(value)
                elif by=='link_text':
                    return self.driver.find_element_by_link_text(value)
                elif by=='xpath':
                    return self.driver.find_element_by_xpath(value)
            except:
                return None
    
    
    
    if __name__=="__main__":
        # 获取驱动
        driver=get_chrome_driver()
        #将驱动作为参数传给jwFindElement()类做初始化
        aa=fwFindElement(driver)
         #验证是否可以找到默认板块链接
        # print(aa.get_element(parent_path+"/business/LocalElement.ini","fwlogin_fatie","morenbankuai"))
        print(aa.get_element(key= "morenbankuai"))
    

    business包

    business包:业务包,存放业务的配置和流程,LocalElement.ini、fatie_business.py
    LocalElement.ini:配置了业务中使用的定位方式所需要的id或name等属性确切的值
    fatie_business.py:业务流程的代码
    

    LocalElement.ini

    #应用po思想将常用的元素和操作封装起来
    #发帖子的常用功能
    # 页面元素修改后  只要修改配置文件即可
    [fwlogin_fatie]
    user_name=id>ls_username
    user_password=id>ls_password
    login_button=xpath>//*[@id="lsform"]/div/div/table/tbody/tr[2]/td[3]/button/em
    luntan=link_text>论坛
    #注意这里只能用xpath定位别的方式定位不了
    morenbankuai=xpath>//*[@id="category_1"]/table/tbody/tr[1]/td[2]/h2/a
    tiezi_title=id>subject
    tiezi_content=id>fastpostmessage
    tiezi_button=xpath>//*[@id="fastpostsubmit"]/strong
    #找回复按钮用于验证发帖成功的元素
    fatie_success=xpath>//*[@id="post_reply"]/img
    

    fatie_business.py

    
    #前置读取
    from time import sleep
    from config.fw_driver import get_chrome_driver, get_firefox_driver
    from config.fw_findelement import fwFindElement
    from config.read_ini import fwReadIni
    
    fw=fwReadIni()
    def  fw_fatie(username,password,tiezi_title,tiezi_content):
        #1.判断使用chrome还是火狐
        driver=None
        if fw.get_value("browser")=="chrome":
            driver=get_chrome_driver()
        else:
            driver=get_firefox_driver()
        #专门查找元素
        fw2=fwFindElement(driver)
        try:
            #先登录
            #用户名
            fw2.get_element(key="user_name").send_keys(username)
            #密码
            fw2.get_element(key="user_password").send_keys(password)
            #点击登录
            fw2.get_element(key="login_button").click()
            sleep(2)
            #点击论坛
            fw2.get_element(key="luntan").click()
            sleep(2)
            #点击默认板块
            fw2.get_element(key="morenbankuai").click()
            sleep(2)
            #发帖
            fw2.get_element(key="tiezi_title").send_keys(tiezi_title)
            fw2.get_element(key="tiezi_content").send_keys(tiezi_content)
            fw2.get_element(key="tiezi_button").click()
            #查找 回复 图片是否存在:存在,返回True;不存在,返回False
            result=fw2.get_element(key="fatie_success")
            # print("找回复图片的结果是",result)
            if  result==None:
                return False
            else:
                return True
        except Exception as e:
            print("fw say:",e)
            #发帖子过程出异常
            return False
        finally:
            sleep(2)
            driver.quit()
    if __name__=="__main__":
        #预期发帖成功
        fw_fatie("admin","123456","今天真高兴~","aaaa")
        # fw_fatie("admin", "123456", "今天真高兴~", "")
    
    

    test_data包

    test_data包:存放测试数据的包,login_fatie.csv、fwtestdata.py
    login_fatie.csv:存放测试用例数据的csv文件
    fwtestdata.py:用来读取csv文件中数据的文件
    

    login_fatie.csv

    admin,123456,今天真高兴,111
    admin,123456,,content
    admin,123456,title,
    

    fwtestdata.py

    import csv
    
    # 设计函数专门读取 csv文件
    def get_csv_data(csv_file,line):#文件名和行号
            fw_csv_file= open(csv_file, 'r', encoding='utf-8-sig') #打开CSV文件
            reader = csv.reader(fw_csv_file)#读取
            # 参数2 :决定了下标位置的开始计数方式
            for index, row in enumerate(reader, 1):#循环遍历CSV文件里的每一行,index代表行的编号,row代表行的值
                if index == line:
                    print(row)
                    return row
    
    if __name__=="__main__":
        get_csv_data("./login_fatie.csv",1)
        get_csv_data("./login_fatie.csv", 2)
        get_csv_data("./login_fatie.csv", 3)
    

    test_case包

    test_case包:存放测试用例的包,test_fatie.py
    test_fatie.py:使用unitest框架,将测试用例放入其中
    

    test_fatie.py

    import os
    import unittest
    import warnings
    
    from business.fatie_business import fw_fatie
    from test_data.fwtestdata import get_csv_data
    
    class TestLogin(unittest.TestCase):
        #获取项目路径
        csv_file = os.path.abspath(os.path.dirname(os.getcwd())) + "\\test_data\\login_fatie.csv"
        def setUp(self):
            print("开始测试~")
            # 这句话的作用是用来忽略 ResourceWaring 异常警告的
            warnings.simplefilter("ignore", ResourceWarning)
    
    
        def tearDown(self):
            print("结束测试~")
    
    
        def test_fatie_111(self):
            print("第一条用例测试~")
            data = get_csv_data(self.csv_file, 1)#获取csv文件中的第一行数据
            # 预期失败用例(获取第一列和第二列数据)交给 登录
            self.assertTrue(fw_fatie(data[0], data[1],data[2],data[3]))
    
    
        def test_fatie_222(self):#获取csv文件中的第二行数据
            print("第二条用例测试~")
            data = get_csv_data(self.csv_file, 2)
            # 预期失败用例
            self.assertFalse(fw_fatie(data[0], data[1],data[2],data[3]))
    
    
        def test_fatie_333(self):
            print("第三条用例测试~")#获取csv文件中的第三行数据
            data = get_csv_data(self.csv_file, 3)
            # 预期失败用例
            self.assertFalse(fw_fatie(data[0], data[1],data[2],data[3]))
    
    
    if __name__ == '__main__':
        unittest.main()
    

    test_report包

    test_report包:存放测试报告
    

    在这里插入图片描述

    test_run包

    test_run包:BSTestRunner.py、fwrun.py
    BSTestRunner.py:一个第三方包,用来生成html测试报告
    fwrun.py:添加测试用例并执行生成测试报告
    

    BSTestRunner.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 BSTestRunner
    
        ... define your tests ...
    
        if __name__ == '__main__':
            BSTestRunner.main()
    
    
    For more customization options, instantiates a BSTestRunner object.
    BSTestRunner is a counterpart to unittest's TextTestRunner. E.g.
    
        # output to a file
        fp = file('my_report.html', 'wb')
        runner = BSTestRunner.BSTestRunner(
                    stream=fp,
                    title='My unit test',
                    description='This demonstrates the report output by BSTestRunner.'
                    )
    
        # 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
    Copyright (c) 2016, Eason Han
    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.
    """
    
    __author__ = "Wai Yip Tung && Eason Han"
    __version__ = "0.8.4"
    
    
    """
    Change History
    
    Version 0.8.3
    * Modify html style using bootstrap3.
    
    Version 0.8.3
    * Prevent crash on class or module-level exceptions (Darren Wurf).
    
    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 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 BSTestRunner 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=BSTestRunner.stdout_redirector)
    #   >>>
    
    def to_unicode(s):
        try:
            return str(s)
        except UnicodeDecodeError:
            # s is non ascii byte string
            return s.decode('unicode_escape')
    
    class OutputRedirector(object):
        """ Wrapper to redirect stdout or stderr """
        def __init__(self, fp):
            self.fp = fp
    
        def write(self, s):
            self.fp.write(to_unicode(s))
    
        def writelines(self, lines):
            lines = map(to_unicode, 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"""<!DOCTYPE html>
    <html lang="zh-cn">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>%(title)s</title>
        <meta name="generator" content="%(generator)s"/>
        <link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">
        %(stylesheet)s
    
        <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
        <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
        <!--[if lt IE 9]>
          <script src="http://cdn.bootcss.com/html5shiv/3.7.2/html5shiv.min.js"></script>
          <script src="http://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
        <![endif]-->
      </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 class="container">
        %(heading)s
        %(report)s
        %(ending)s
    </div>
    
    </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">
    
    /* -- css div popup ------------------------------------------------------------------------ */
    .popup_window {
        display: none;
        position: relative;
        left: 0px;
        top: 0px;
        /*border: solid #627173 1px; */
        padding: 10px;
        background-color: #99CCFF;
        font-family: "Lucida Console", "Courier New", Courier, monospace;
        text-align: left;
        font-size: 10pt;
        width: 500px;
    }
    
    /* -- report ------------------------------------------------------------------------ */
    
    #show_detail_line .label {
        font-size: 85%;
        cursor: pointer;
    }
    
    #show_detail_line {
        margin: 2em auto 1em auto;
    }
    
    #total_row  { font-weight: bold; }
    .hiddenRow  { display: none; }
    .testcase   { margin-left: 2em; }
    
    </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><strong>%(name)s:</strong> %(value)s</p>
    """ # variables: (name, value)
    
    
    
        # ------------------------------------------------------------------------
        # Report
        #
    
        REPORT_TMPL = """
    <p id='show_detail_line'>
    <span class="label label-primary" οnclick="showCase(0)">Summary</span>
    <span class="label label-danger" οnclick="showCase(1)">Failed</span>
    <span class="label label-default" οnclick="showCase(2)">All</span>
    </p>
    <table id='result_table' class="table">
        <thead>
            <tr id='header_row'>
                <th>Test Group/Test case</td>
                <th>Count</td>
                <th>Pass</td>
                <th>Fail</td>
                <th>Error</td>
                <th>View</td>
            </tr>
        </thead>
        <tbody>
            %(test_list)s
        </tbody>
        <tfoot>
            <tr id='total_row'>
                <td>Total</td>
                <td>%(count)s</td>
                <td class="text text-success">%(Pass)s</td>
                <td class="text text-danger">%(fail)s</td>
                <td class="text text-warning">%(error)s</td>
                <td>&nbsp;</td>
            </tr>
        </tfoot>
    </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 class="btn btn-xs btn-primary"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 btn btn-xs btn-default" ο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;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.outputBuffer = StringIO.StringIO()
            self.outputBuffer = io.StringIO()
            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
            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 BSTestRunner(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, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)
            print(sys.stderr, '\nTime 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 rmap.has_key(cls):
                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('<span class="text text-success">Pass <strong>%s</strong></span>'    % result.success_count)
            if result.failure_count: status.append('<span class="text text-danger">Failure <strong>%s</strong></span>' % result.failure_count)
            if result.error_count:   status.append('<span class="text text-warning">Error <strong>%s</strong></span>'   % 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 = 'BSTestRunner %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),
                        name = name,
                        value = 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 'text text-warning' or nf > 0 and 'text text-danger' or 'text text-success',
                    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.decode('latin-1')
                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.decode('latin-1')
                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'),
                Class = (n == 0 and 'hiddenRow' or 'text text-success'),
                # style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
                style = n == 2 and 'text text-warning' or (n == 1 and 'text text-danger' or 'text text-success'),
                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 BSTestRunner as the default test runner.
            # base class's testRunner parameter is not useful because it means
            # we have to instantiate BSTestRunner before we know self.verbosity.
            if self.testRunner is None:
                self.testRunner = BSTestRunner(verbosity=self.verbosity)
            unittest.TestProgram.runTests(self)
    
    main = TestProgram
    
    ##############################################################################
    # Executing this module from the command line
    ##############################################################################
    
    if __name__ == "__main__":
        main(module=None)
    
    

    fwrun.py

    import sys
    #声明包查找的路径(框架路径)
    # path=r'C:\Users\Administrator\PycharmProjects\p2222'
    # sys.path.append(path)
    
    from test_run.BSTestRunner import BSTestRunner
    import unittest
    import time
    #指定测试用例和测试报告的路径:当前目录的上一层目录下的test_case目录和test_report目录
    test_dir = '../test_case'
    report_dir = '../test_report'
    #匹配测试多条用例
    discover = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
    #定义报告的文件格式
    now = time.strftime("%Y-%m-%d-%H_%M_%S")
    report_name = report_dir + '/' + now + 'test_report.html'
    #运行用例并生成测试报告
    with open(report_name, 'wb') as f:
        runner = BSTestRunner(stream=f, title="fw的发帖子测试报告", description="我的论坛网站测试报告")
        runner.run(discover)
    
    展开全文
  • WebWEB自己动手写Web自动化测试框架6-自动化测试框架的规划软件测试从今天开始,我们正式进入自动化测试框架的编写中。首先我们先进行需求分析:我们到底要什么样的自动化测试框架?正如第一篇《自己动手写Web自动化...
  • Web自动化测试框架

    2013-12-06 17:18:03
    【转】Web自动化测试框架   一、 自动化测试的关键思路介绍  首先,谈谈在测试自动化的情况下,带有图形界面的产品的测试用例的设计问题。因为图形界面的输出显示不是很容易做到测试结果自动化比较,...

    【转】Web自动化测试框架


     

    一、        自动化测试的关键思路介绍

       首先,谈谈在测试自动化的情况下,带有图形界面的产品的测试用例的设计问题。因为图形界面的输出显示不是很容易做到测试结果自动化比较,所以一般的做法是把图形界面输出的部分单独建立测试用例,以手工运行。而所有非图形输出则可进行自动测试。

    1.      测试用例的生成
       用编程语言或更方便的脚本语言(如VBSRuby等)写出短小的程序来产生大量的测试输入(包括输入数据与操作指令)。或同时也按一定的逻辑规律产生标准输出。输入与输出的文件名字按规定进行配对,以便控制自动化测试及结果核对的程序易于操作。
       这里提到的测试用例的命名问题,如果在项目的文档设计中作统一规划的话,软件产品的需求与功能的命名就应该成为后继开发过程的中间产品的命名分类依据。这样,就会为文档管理和配置管理带来很大的方便,使整个产品的开发过程变得更有条理,更符合逻辑。任何新手半途加入到开发工作中也会更容易进入状态。

    2.      测试的执行写控制
       单元测试或集成测试可能多用单机运行。但对于系统测试或回归测试,就极有可能需要多台机器在网络上同时运行。记住一个这样的原则,在开发过程中的任何时候,如果你需要等候测试的运行结果的话,那就是一个缩短开发时间的机会。
       对于单个的测试运行,这样的机会在测试的设置及开始运行和结果的比对及显示。有时候,需要反复修改程序,重新汇编及重新测试。这样,每一个循环的各种手工键入的设置与指令所花费的时间,加起来就非常可观。如果能利用make或类似的软件工具来帮助,就能节省大量的时间。
       对于系统测试或回归测试这类涉及大量测试用例运行的情况,缩短时间的机会除了利用软件工具来实现自动化之外,就是怎样充分利用一切硬件资源。往往,就算是在白天的工作时间内,每台计算机的负荷都没有被充分利用。能够把大量测试用例分配到各台机器上去同时运行,就能节省大量的时间。另外,把大量的系统测试及回归测试安排到夜间及周末运行,更能提高效率。
       如果不购买商品化的工具的话,应当遵从正规的软件开发要求来开发出好的软件测试自动化工具。在实践中,许多企业自行开发的自动化工具都是利用一些现成的软件工具再加上自己写的程序而组成的。这些自己开发的工具完全是为本企业量身定做的,因此可用性非常强。同时,也能根据需要随时进行改进,而不必受制于人。当然,这就要求有一点的人力的投入。
       在设计软件自动测试工具的时候,路径控制是一个非常重要的功能。理想的使用情况是:这个工具可以在任何一个路径位置上运行,可以到任何路径位置去取得测试用例,同时也可以把测试的结果输出放到任何的路径位置上去。这样的设计,可以使不同的测试运行能够使用同一组测试用例而不至于互相干扰,也可以灵活使用硬盘的空间,并且使备份保存工作易于控制。
       同时,软件自动测试工具必须能够有办法方便地选择测试用例库中的全部或部分来运行,也必须能够自由地选择被测试的产品或中间产品作为测试对象。

    3.      测试结果与标准输出的比对
       在设计测试用例的时候,必须考虑到怎样才能够易于对比测试结果和标准输出。输出数据量的多少及数据格式对比较的速度有直接影响。而另一方面,也必须考虑到输出数据与测试用例的测试目标的逻辑对应性及易读性,这将会大大有利于分析测试所发现的不吻合,也有利于测试用例的维护。
       许多时候,要写一些特殊的软件来执行测试结果与标准输出的对比工作,因为可能有部分的输出内容是不能直接对比的(比如,对运行的日期时间的记录,对运行的路径的记录,以及对测试对象的版本数据等),就要用程序进行处理。

    4.      不吻合的测试结果的分析、分类、记录和通报
       上一点所谈到的,用于对测试结果与标准输出进行比对的特殊软件,往往也同时担任对不吻合的测试结果进行分析、分类、记录和通报的任务。
       “分析”是找出不吻合的地方并指出错误的可能起因。“分类”包括各种统计上的分项,例如,对应的源程序的位置,错误的严重级别(提示、警告、非失效性错误、失效性错误;或别的分类方法),新发现的还是已有记录的错误,等等。“记录”,是按分类存档。“通报”,是主动对测试的运行着及测试用例的“负责人”通报出错的信息。
       这里提到测试用例的“负责人” 的概念。是用以指定一个测试用例运行时发现的缺陷,由哪一个开发人员负责分析及修复。在设立测试用例库时,各用例均应有指定的负责人。
       最直接的通报方法是由自动测试软件发出电子邮件给测试运行者及测试用例负责人。邮件内容的详细程度可跟进需要灵活决定。

    5.      总测试状况的统计,报表的产生
       这些都是自动测试工具所应有的功能。目的是提高过程管理的质量,同时节省用于产生统计数据的时间。
       产生出来的统计报表,最好是存放到一个约定的路径位置,以便任何有关人员都知道怎样查阅。同时,可按需要用电子邮件向适当的对象(如项目经理,测试经理和质量保证经理)寄出统计报表。

    6.      自动测试与开发中产品每日构建的配合
       自动测试应该是整个开发过程中的一个有机部分。自动测试要依靠配置管理来提供良好的运行的环境,同时它必须要与开发中的软件的构建紧密配合。
       在开发中的产品达到一定程度的时候,就应该开始进行每日构建和测试。这种做法能使软件的开发状态得到频繁的更新,以及及早发现设计和集成的缺陷。
       为了充分利用时间与设备资源,下班之后进行自动的软件构建,紧接着进行自动测试,是一个非常行之有效的方法。如果安排的好,到第二天上班时,测试结果就已经在个人的电子邮箱里面了,等待着新的一天的开发工作。

     

    二、        编码集成介绍

    1.      Ruby的由来
       松本行弘(Matz)是日本一家开源软件公司的程序员,有15年的编程经验。在工作中,他希望有一种比Perl强大,比Python更面向对象的语言。从19932月,他开始设计一个全新的自己的语言,199412月发布了第一个alpha版本,并且将这种新语言定名为Ruby(红宝石)。发展到现在,最新稳定的版本是Ruby1.9.2.

    2.      Ruby的特性
       计算机编程语言的发展总是与飞速发展的世界息息相关的,Ruby是为了适应变化、提高和完善编程艺术而出现的。
    1】完全开源。
    2】多平台:Ruby可以运行在Linux, UNIX, Windows, MS-DOS,BeOS, OS/2..
    3】多线程:线程就是指在一个程序中处理若干控制流的功能。与OS提供的进程不同的是,线程可以共享内存空间。
    4】完全面向对象。
    5】不需要内存管理:具有垃圾回收(Garbage Collect,GC)功能,能自动回收不再使用的对象。
    6】解释执行:其程序无需编译即可轻松执行。
    7】功能强大的字符串操作/正则表达式。
    8】具有异常处理功能。
    9】可以直接访问OSRuby可以使用UNIX的绝大部分的系统调用。单独使用Ruby也可以进行系统编程。
    10】动态类型语言:Ruby的变量没有类型,因此不必为类型匹配而烦恼。
    11】动态语言:程序运行中,可以加入熟悉,行为,也可以重写方法。
    12】支持操作符重写。
    13】支持无限精度的数字。
    14】丰富的库函数。
    15】用模块进行混合插入(Max-in)Ruby舍弃了多重继承,但拥有混合插入功能。使用模块来超越类的界限来共享数据和方法等。
    16】语法简单:它是脚本语言,没有指针,学习曲线比较低。
       总结来说,Ruby吸取了perl的正则表达式,python的简单性可读性,smalltalk的纯面向对象语法和单继承,LISP的无穷嵌套的语法,java的线程….

    3.      Watir介绍
       Watir封装了对web页面元素识别的机制,测试者可以利用Ruby在这个框架下根据对象的属性识别,而后写Ruby代码控制测试逻辑,加入检查点等等以达到自动化测试目的,并且有可以随意自由定制框架和测试逻辑的优点。

    4.      下载和安装ruby + water
    1】首先访问ruby官方网站:http://www.ruby-lang.org/zh_CN/downloads/
    2】访问Watir官方网站:http://wtr.rubyforge.org/install.html
    3】按照网站上的描述进行如下操作:
      1)必须先完成Ruby的安装;
      2)打开“运行”,输入cmd进入控制台;
      3)输入下面代码:
         gem update –system
         gem install water
      4
    )等待安装完成,如果安装失败,需要手工添加类库文件:
         http://rubyforge.org/frs/?group_id=126
         下载最新的rubygems-xxx.zip版本。
      注: 为了让Watir认识中文并操作文件上传,需要做下面的修改:
         首先修改C:\ruby\lib\ruby\gems\1.9\gems\watir-xxx\watir\input_elements.rb文件,找到FileField类下的set方法,把原来的替换成
         system(“rubyw –e \”require ‘win32ole’;@autoit=WIN32OLE.new(‘AutoItX3.Control’);waitresult=@autoit.WinWait’选择文件’,”,15;sleep 1;ifwaitresult==1\”–e\”@autoit.ControlSetText’选择文件’,”,’Edit1’,’#’;@autoit.ControlSend’选择文件’,”,’Button2’,”;\” –e\”end\””)

    5.      开始第一个小程序
    1】安装开发工具,此次选择eclipse,加上RDT插件org.rubypeople.rdt-xxxx.zip
    2eclipse下载地址:http://www.eclipse.org/downloads/
    3RDT下载地址:http://update1.aptana.org/rdt/3.2/index.html
    4】安装好eclipse之后,打开eclipse面板,点Help ->Software Updates -> Available Software-> Add Site -> Archive-> 选中下载好的org.rubypeople.rdt-xxx.zip->OK按钮。
    准备好开发工具后,让我们进入例子:
       模拟打开Google的主页,然后在Google唯一的那个文本框内输入”pickaxe”这个字符串,然后按下“Google搜索”按钮,之后验证搜索结果的页面中是否包含了“ProgrammingRuby”这个字符串,并跟进结果使用puts函数在屏幕上打印不同的信息。
       代码如下:
    require 'watir'  # the watircontroller
    # open the IEbrowser
    ie = Watir::IE.new
    # Step 1: go to the test site:http://www.google.com
    ie.goto(http://www.google.com)
    # Step 2: enter 'pickaxe' inthe search text field
    ie.text_field(:name,"q").set("pickaxe")  # q is the name of thesearch
    # Step 3: click the'Google Search'button
    ie.button(:name,"btnG").click  #"btnG" is the name of the Searchbutton
    # Actual Result: Checkthat the 'Programming Ruby' link appears on the resultspage
    if ie.contains_text("Programming Ruby")
     puts
    "Test Passed. Found the test string:'Programming Ruby'. Actual result match ExpectedResults."
    else
     
    puts "Test Failed! Could not find:'Programming Ruby'"
    end
    # End of test: Googlesearch

    6.      Ruby语法快览
    1】注释与分行:
        # #开头到行尾是单行注释;
        =begin
        xxxx
        =end 
    是多行注释,这也是Ruby的内嵌文档(Rdoc)注释。Rdoc是内嵌在ruby代码之中的,可以转换为html文档说明。
        Ruby中用分号“;”来表示一个语句的结束。一行如果有多个语句,每个语句用分号隔开,而最后一个语句可以省略分号。换行符表示一行结束。如果语句太长,可以用“\”连接下一行。
    2】分隔符:
       ;分号:用来分隔一行中的多个语句。
       () 圆括号:提高优先级;定义方法时容纳参数列表;
         空格:分隔字符:在可省略()的地方,代替()
       
    ,逗号:隔开多个参数;
       .  点:将对象与它的方法隔开;
       ::紧连的两个冒号:域作用符,将模块(类)与它的常量隔开。
    3】关键字:
       模块定义:module
       
    类定义:class
       
    方法定义:defundef
       
    检查类型:defined
       条件语句:ifthenelseelsifcasewhenunless
       
    循环语句:forinwhileuntilnextbreakdoredoretryyield
       
    逻辑判断:notandor
       
    逻辑值和空值:truefalsenil
       
    异常处理:rescueensure
       
    对象引用:superself
       
    块的起始:begin/end
       
    嵌入模块:BEGINEND
       
    文件相关:__FILE__ , __LINE__
       
    方法返回:return
       
    别名:alias
    4】运算符:优先级由高到低
       [][]=      数组下标数组元素赋值
       **       乘幂
       ! ~ + -    非 位非 一元加 负号
       * / %     乘除 模
       + -        加减
       >><<     右移左移
       &        位与
       ^ |       位异或 位或
       <= < >>=  小于等于 小于 大于 大于等于
       <=> == === =~ != !~ 各种相等判断
       &&       短路与
       ||        短路或
       .. …       区间的开始点到结束点
       ?:        三元条件运算符
       = %= ~= /= -= += |= &=>>= <<=*= &&= ||= **=  各种赋值
       defined?   检查类型
       not       逻辑非
       or and     逻辑或 逻辑与
    5】标识名和变量的作用域
       Ruby使用一个约定来帮助它区别一个名字的用法:名字前面的第一个字符表明这个名字的用法。局部变量、方法参数和方法名称应该用一个小写字母开头或者用一个下划线开头;全局变量用美元符作为前缀$;而实例变量用@开头;类变量用@@开头;类名、模块名和常量应该用大写字母开头。
       词首字母后面可以是字母、数字和下划线的任意组合;@后面不可以直接跟数字。
    6】数据类型:
       Ruby的数据类型有数字,字符串,数组,哈希表,区间,正则表达式。
       数字分为整数型和浮点型。浮点型数据小数点后必须跟数字。
       字符串是在单引号、双引号之间的代码;
       数组的小标从0开始。数组的每个元素可以是不同的类型。
       区间 .. …
    7】赋值和条件运算符:
       Ruby基本的赋值用“=”来完成。
       <=> 比较两个对象的大小,大于、等于、小于分别返回1,0-1
       ===
    右边的对象是否在左边区间之内,返回true, false
       =~
    匹配:用例比较是否符合一个正则表达式,返回模式在字符串中被匹配到的位置,否则返回nil
       !~
    不匹配:断言不符合一个正则表达式,返回truefalse
       <= < >>= 小于等于 小于 大于 大于等于
       == != 等于 不等于:比较两个对象的值是否相等,返回true, false
       eql?
    比较两个对象的值、类型是否相等,返回true,false
       equal? 比较两个对象的内存中地址是否相同,返回true,false
    8】条件判断语句:
       判断条件是否相等用“==
       Ruby里,nilfalse为假,其他都为真。
    9】循环语句
       For .. in , while, until
       
    迭代器:times, upto, downto, each,step
    10】异常与线程:
       Ruby中用begin/end…rescue…ensure…raise来处理异常rescue必须在ensure前。

    7.      Watir语法快览:
    1】文本框<INPUT id=”email” name=”_fmu.u._0.e”value=””/>
       
    方法1ie.text_field(:id,’email’).set(“文本内容”)
       
    方法2ie.text_field(:name,‘email’).set(“文本内容”)
       
    方法3ie.text_field(:name,“email”).clear
    2】下拉框
       <SELECT name=”cert_no”>
       <OPTION value=”
    身份证”>身份证</OPTION>
       </SELECT>
       
    方法1ie.select_list(:name,”cert_no”).select(“身份证”)
       
    方法2ie.select_list(:name,”cert_no”).clearSelection
    3】超链接<a href = http://www.google.cn/>google</a>
       
    方法1ie.link(:text,”google”).click
       
    方法2ie.link(:url,http://www.google.cn/).click
    4】复选框<input type = “checkbox” name = “checkme”value = “1”>
       
    方法1ie.checkbox(:name,”checkme”).set
       
    方法2ie.checkbox(:name,”checkme”).clear
       
    方法3values =ie.checkbox(:name,”checkme”).value
       
    多个同名复选框的处理:
       <input type = “checkbox” name = “checkme” value =“2”>
       
    方法1ie.checkbox(:name,”checkme”,”2”).set
       
    方法2ie.checkbox(:name,”checkme”,”2”).clear
    5】单选框<input type = “radio” name = “clickme” id= “1”>
       
    方法1ie.radio(:name,”clickme”).set
       
    方法2ie.radio(:name,”clickme”).clear
    6】一般按钮<input type = “button” name = “clickme”value = “Click Me”>
       
    方法1ie.button(:value, “ClickMe”).click
       
    方法2ie.button(:name,”clickme”).click
    7submit按钮
       <form action = “submit” name = “submitform” method =“post”>
       <input type = “submit” value =“Submit”></input>
       </form>
       
    方法:ie.button(:value,”Submit”).click
    8】图片按钮
       <form action =”submit” name = “doitform” method =“post”>
       <input type = “image” src = “images/doit.gif” name =“doit”>
       </form>
       
    方法:ie.button(:name,”doit”).click
    9Form中无按钮
       <form action = “login” name = “loginform” method =“get:>
       <input name = “username” type=”text”></input>
       </form>
       
    方法1ie.form(:name,”loginform”).submit
       
    方法2ie.form(:action,”login”).submit
    10】获取隐含对象值<INPUT type=hiddenvalue=”您的Email”name=”field1”>
       
    方法:values =ie.hidden(:name,’field1’).value
    11】获取窗口对象
       方法1ie2 =Watir::IE.attach(:url,’http://www.google.cn/’)#根据url获取
       方法2ie3 =Watir::IE.attach(:title,’Google’) #根据窗口标题获取
       方法3ie4 =Watir::IE.attach(:title, /google.cn/) #正则表达式匹配获取
    12URL编码
       requ
    展开全文
  • 自己写Web自动化测试框架!得几年前一本《自己动手写操作系统》在全国的技术范围内引起了学习操作系统的热潮。我不才在这里使用这本书的大名,来分享一下我在写Web自动化测试框架上面的一些经验。  记得几年前一本...
  • 原标题:Pyhton3+Selenium的Web自动化测试框架环境部署Python3+Selenium+unittest+HTMLTestRunner+pageObject Web自动化测试框架。(Page Object设计模式)环境部署:python3、selenium3开发工具:Pycharm集成工具:...

    原标题:Pyhton3+Selenium的Web自动化测试框架

    环境部署

    Python3+Selenium+unittest+HTMLTestRunner+pageObject Web自动化测试框架。

    (Page Object设计模式)

    环境部署:python3、selenium3

    开发工具:Pycharm

    集成工具:Jenkins

    测试代码托管平台:GitHub

    源码:https://github.com/linyuli861/Automated-Test.git

    通过主从服务器执行测试。

    环境部署

    d18c62b94a05e6927ba8278ef0418333.png

    common文件夹存放公有元素,如url,测试报告发送邮件地址,使用信息等;

    file文件夹存放测试过程中需要使用的文件,如图片,txt,zip文件等

    page文件夹用于存放测试过程中需要使用的页面元素

    report文件夹用于存放测试生成的测试报告

    testcase文件夹中存放测试用例

    HTMLTestRunner.py是将测试结果生成为html版的测试报告的文件

    run.py 执行run.py文件可以执行全部测试用例

    PageObject 设计模式

    由于被测页面会更新,为保持测试脚本的健壮性和可修改性,我们使用了Page Object设计模式,将被测页面的测试脚本与脚本中所使用到的页面元素解耦, 将被测页面的测试脚本文件放在testcase文件夹中,将测试脚本使用到的被测页面元素放置在page文件夹中。

    为了测试用例写起来更加方便,我们在page文件夹中放置了BasePage.py文件,BasePage.py文件将查找元素进一步简化,page文件夹中的其他Page文件可以调用BasePage.py中的方法将代码进一步简化。

    下面以百度首页作为示例,演示本框架中Page Object模式的使用。

    test_baiduSearch.py

    # coding=utf-8from HTMLTestRunner import HTMLTestRunnerfrom selenium import webdriverfrom page.searchPage import SearchPageimport timeimport unittestfrom page.searchPage import *classTestLogin( unittest. TestCase):defsetUp( self):self.driver = webdriver.Chromeself.url = SearchPage.urlself.driver.maximize_windowself.page = SearchPage( self.driver)self.page.get( self.url)deftearDown( self):self.driver.closedeftest_search( self):# 使用pageObject模式时的web页面自动化测试代码self.page.search = self.page.search_contentself.page.search_btn.clicktime.sleep( 2)# 断言self.assertIn( self.page.search_content_assert, self.driver.page_source)# # 未使用pageObject模式时的web页面自动化测试代码# self.driver.find_element_by_id("kw").send_keys("hello")# self.driver.find_element_by_id("su").click# time.sleep(2)# self.assertIn("hello", self.driver.page_source)deftest_search1( self):# 错误的断言导致测试用例failedself.page.search = self.page.search_contentself.page.search_btn.clicktime.sleep( 2)self.assertIn( self.page.search_content_assert_wrong, self.driver.page_source)# # 未使用pageObject模式的web页面自动化测试代码# self.driver.find_element_by_id("kw").send_keys("hello")# self.driver.find_element_by_id("su").click# time.sleep(2)# self.assertIn("hello1232323", self.driver.page_source)deftest_search2( self):# 元素值错误,导致的自动化测试用例errorself.page.wrong_search = self.page.search_contentself.page.search_btn.clicktime.sleep( 2)self.assertIn( self.page.search_content_assert, self.driver.page_source)# # 未使用pageObject模式的web页面自动化测试代码# self.driver.find_element_by_id("k").send_keys("hello")# self.driver.find_element_by_id("su").click# time.sleep(2)# self.assertIn("hello", self.driver.page_source)if__name_ _== '__main__':# 使用以下语句生成本页面的测试报告# now = time.strftime("%Y-%m-%d-%H-%M-%S")# suite = unittest.TestSuite# suite.addTest(TestLogin("test_search"))# suite.addTest(TestLogin("test_search1"))# suite.addTest(TestLogin("test_search2"))# path = "../report/" + now + "result.html"# fp = open(path, 'wb')## runner = HTMLTestRunner(stream=fp, title=u"Web页面自动化测试", deion=u"测试查询功能")# runner.run(suite)# fp.closeunittest.mainsearchPage.pyfrom common.pageObject import PageObject, PageElementfrom common.url import *classSearchPage( PageObject):# 当前测试页面的测试网址urlbase_url = Url.base_urlurl = base_url+ '/'# 查询元素search = PageElement(id= "kw")wrong_search = PageElement(id= "k")search_btn = PageElement(id= 'su')# 查询内容search_content = "hello"# 断言search_content_assert = "hello"search_content_assert_wrong = "hello12232423423"

    运行用例

    运行所有测试用例:

    运行run.py文件即可运行所有测试用例,将所有的测试脚本文件统一使用test开头的文件命进行命名。

    run.py

    importunittestimportHTMLTestRunnerimporttimefromcommon.sendEmail importSendEmaildefget_test_cases(dirpath):# dirpath是存放测试用例的文件路径test_cases = unittest.TestSuite# 测试用例均使用"test_"开头命名suites = unittest.defaultTestLoader.discover(dirpath, 'test_*.py', top_level_dir=dirpath)forsuite insuites:test_cases.addTests(suite)returntest_casesif__name__ == '__main__':cases = get_test_cases( '../testcase')now = time.strftime( "%Y-%m-%d %H_%M_%S") # 报告生成时间test_reports_address = '../report'# 测试报告存放位置filename = '../report/'+ now + 'report.html'# 设置报告文件名fp = open(filename, 'wb')runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title= u'Web自动化测试', deion= u'详细测试结果如下:')runner.run(cases)fp.close# 向指定邮箱发送测试报告的html文件time.sleep( 6)# 查找最新生成的测试报告地址new_report_addr = SendEmail.acquire_report_address(test_reports_address)# 自动发送邮件SendEmail.send_email(new_report_addr)

    生成报告

    运行完成后会在report文件夹中生成html版本的测试报告,如下所示:

    这里能够生成HTML版本的测试报告主要是引用了HTMLTestRunner.py文件,由于官网给出的HTMLTestRunner.py文件是python2版本的,所以需要进行改动,以下是改动过的HTMLTestRunner.py文件生成的测试报告。

    81a04340b08a13f72b254ef92f8952a7.png

    发送邮件

    向指定邮箱发送邮件:

    生成测试报告后可以向指定邮箱发送测试报告的html文件,注意向指定邮箱发送邮件是,需要对发邮件的邮箱进行设置,获取发件箱的授权码,具体操作请百度。

    sendEmail.py

    # email:***@163.com password:***"""使用一个邮箱向另一个邮箱发送测试报告的html文件,这里需要对发送邮件的邮箱进行设置,获取邮箱授权码。username=“发送邮件的邮箱”, password=“邮箱授权码”这里要特别注意password不是邮箱密码而是邮箱授权码。mail_server = "发送邮箱的服务器地址"这里常用的有 qq邮箱——"stmp.qq.com", 163邮箱——"stmp.163.com"其他邮箱服务器地址可自行百度"""importosimportsmtplibfromemail.mime.text importMIMETextfromemail.header importHeaderimporttime# 自动发送邮件classSendEmail:defsend_email(self, new_report):# 读取测试报告中的内容作为邮件的内容withopen(new_report, 'r', encoding= 'utf8') asf:mail_body = f.read# 发件人地址send_addr = '***@163.com'# 收件人地址reciver_addr = '***@163.com'# 发送邮箱的服务器地址 qq邮箱是'smtp.qq.com', 163邮箱是'smtp.163.com'mail_server = 'smtp.163.com'now = time.strftime( "%Y-%m-%d %H_%M_%S")# 邮件标题subject = 'web自动化测试报告测试报告'+ now# 发件人的邮箱及邮箱授权码username = '***@163.com'password = '***'# 注意这里是邮箱的授权码而不是邮箱密码# 邮箱的内容和标题message = MIMEText(mail_body, 'html', 'utf8')message[ 'Subject'] = Header(subject, charset= 'utf8')# 发送邮件,使用的使smtp协议smtp = smtplib.SMTPsmtp.connect(mail_server)smtp.login(username, password)smtp.sendmail(send_addr, reciver_addr.split( ','), message.as_string)smtp.quit# 获取最新的测试报告地址defacquire_report_address(self, reports_address):# 测试报告文件夹中的所有文件加入到列表test_reports_list = os.listdir(reports_address)# 按照升序排序生成新的列表new_test_reports_list = sorted(test_reports_list)# 获取最新的测试报告the_last_report = new_test_reports_list[ -1]# 最新的测试报告地址the_last_report_address = os.path.join(reports_address, the_last_report)returnthe_last_report_address

    发送邮件如下所示:

    53417ec58db08d458938d208153e843a.png

    Jenkins部署

    测试脚本编写完成之后可以将自动化测试脚本托管到git上,然后集成到主站上的Jenkins,配置主从服务器,设置构建时间,让Jenkins可以自动去git下载测试代码,在从服务器中运行,并将测试结果反馈给主站。返回搜狐,查看更多

    责任编辑:

    展开全文
  • 首先定义一下Web自动化测试框架Web自动化测试框架是一个类库,他可以帮助测试人员快速写出Web自动化测试代码,并帮助测试人员在自动化报错的时候快速找到Bug。目前市场上成熟的Web自动化测试框架有不少,很出名的...
  • PatatiumWebUi 简介 这是一个WebUI自动化测试框架,由 webdriver中文社区 创办人土豆(本人技术笔名)所创建,该web自动化测试框架是用java语言编写的,基于seleniumwebdriver的开源自动化测试框架,该框架结合了...
  • TestCafe 介绍:TestCafe 是一款基于 Node.js 的端到端 Web 自动化测试框架,支持 TypeScript 或 JavaScript 来编写测试用例,运行用例,并生成自动化测试报告。TestCafe 兼容 Windows,MacOS 和 Linux 系统,同时也...

    a8cc8aefaf5f81be5b7a72fa4d33da65.gif

    TestCafe 介绍:

    4061caebc8e89d1933a0b6576d396140.gif

    TestCafe 是一款基于 Node.js 的端到端 Web 自动化测试框架,支持 TypeScript 或 JavaScript 来编写测试用例,运行用例,并生成自动化测试报告。 

    TestCafe 兼容 Windows,MacOS 和 Linux 系统,同时也支持桌面、移动端所有的浏览器,并且无需安装浏览器对应的 WebDriver。

    • TestCafe 的特性
      • 支持并发启动多个浏览器同时测试,无需修改任何代码
      • 支持 TypeScript 和最新的 JavaScript 语言来编写用例
      • 同一套代码,无需进行任何配置环境支持在所有的浏览器运行
      • 会自动检测页面中错误的 js 代码
      • 支持在 TeamCity,Jenkins,Travis 中持续集成
      • TestCafe 中提供了高级的选择器库,断言方式等,可以很方便的基于 PageObject 模式去进行测试

    环境安装

    TestCafe 环境安装非常简单,安装好 node.js 之后,通过一条 npm 命令就可以搞定。

    • node.js 的安装

      关于 node.js 安装非常简单,先去 node.js 官网(https://nodejs.org/en/download/)根据操作系统下载对应的安装包,进行安装即可。

      ed1cba6224fd4635e8c40f2eb342d9fd.png

      安装好了之后,打开命令终端:输入下述命令 Node --version 能看到对应的版本就说明安装好了!

      23607f8f66972a8f28da859e53522a38.png

    • TestCafe 安装

      安装好 node.js 之后直接使用 mpn 就可以安装 TestCafe,命令如下

      npm install testcafe

      安装好之后输入 testcafe --verison 就说明安装成功了

      f3aed0fd9328d19d06256a849a3585b9.png

    入门使用

    1案例代码讲解

    接下来通过一个简单的案例来给大家介绍一下 TestCafe 如何使用,要使用 testcafe 进行 Web 自动化测试,首先需要创建一个.js 或。ts 的测试用例文件,每个测试用例文件中 fixture 和 test 这两个部分组成,下面是一个打开百度首页并进行搜索的小案例。

    • 第一步、导入 testcafe 模块中的 Selector 这个选择器,
      import { Selector } from 'testcafe';
      • 可以使用 Selector 来定位元素

    • 第二步:创建一个 fixture 对象,并打开百度首页(测试文件中必须要有 fixture)
      fixture(`百度案例`).page(`https://www.baidu.com`);
    • 第三步:定义一个测试用例,用例中定位搜索输出框输入百度,点击搜索(每一个 test 就是一条测试用例)
      test('百度搜索功能', async t => {
         await t.typeText('#kw', "hao123")
             .click('#su')
      });
    2案例运行
    • 第一步:创建 test_baidu.js 文件,写入如下代码

    import { Selector } from 'testcafe';

    fixture(`百度案例`).page(`https://www.baidu.com`);

    test('百度搜索功能', async t => {

     await t.typeText('#kw', "柠檬班")

            .click('#su')

    });

    • 第二步:cmd 中切换到上述文件所在路径,通过 testcafe 执行测试代码
      • 通过 Chrome 浏览器运行,cmd 命令如下:

      • 通过火狐浏览器运行,cmd 命令如下

      • 启动命名规范:testcafe 运行浏览器名 运行文件
        testcafe chrome test_baidu.js
        testcafe firefox test_baidu.js

        关于使用其他浏览器执行,就不逐一介绍了

      • 执行之后就会自动打开对应的浏览器,访问百度首页,并搜索柠檬班

    • 运行完毕后,cmd 中即可查看测试结果,如下图

      a154b4c5106f4b08ebea7f8e757e6fbb.png

    testcafe 的安装和入门使用就介绍到这里,持续关注小编,后续还会更新详细的使用教程文章。

    2abdc9bec1503ef29282f12262c6e63c.png7f17d7f0b7dc17c3cc8fc57080d84d96.png2abdc9bec1503ef29282f12262c6e63c.png

    a8d44de4e0658475986fbe2bf734717a.png

    cbb6bd38dd4ee26e230e558e9c006bbd.png

    176f6e0add7f365d3917b5e38febd10f.png

    421c1a552aeea318f2f1a21e7ecb0bb6.pnga6e77f4dd23a02967e5c2cc7fa7b6492.gif

    展开全文
  • Web自动化测试框架搭建:内含自动化测试理念介绍及分层的自动化测试框架搭建,整个自动化测试采用CI管理
  • WebWEB开源Web自动化测试框架——Watir试用手记软件测试Watir是一个使用Ruby实现的开源Web自动化测试框架,相对于那些庞大的商业工具来说,它很小巧,也很灵活,提供的功能也足够用。最近抽时间试用了一下,感觉还...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,848
精华内容 1,939
关键字:

web自动化测试框架