Selenium3与Python3实战 Web自动化测试框架(二)

Selenium3与Python3实战 Web自动化测试框架


 

一、项目实战中PO模型的设计与封装

 一般将所有的元素、数据都放在代码中,并不利于自动化代码的维护。最好的方式是尽量把数据、页面、操作进行分离开:PO设计模式

 PO设计模式的优势:

  1. PO提供了一种业务流程与页面元素操作分离的模式,这使得测试代码变得更加清晰。
  2. 页面对象与用例分离,使得我们更好的复用对象。
  3. 可复用的页面方法代码会变得更加优化
  4. 更加有效的命名方式使得我们更加清晰的知道方法所操作的UI元素

1、使用PO模式实现注册页面封装

1)关于配置文件

 LocalElement.py:

[RegisterElement]
user_email=id>register_email
user_email_error=id>register_email-error
user_name=id>register_nickname
user_name_error=id>register_nickname-error
password=id>register_password
password_error=id>register_password-error
code_image=id>getcode_num
code_text=id>captcha_code
code_text_error=id>captcha_code-error
register_button=id>register-btn

 setting.py:

import os

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 项目首路径
codeerror_path = os.path.join(base_dir,'Image','codeerror.png')  # 验证码错误图片路径
code_path = os.path.join(base_dir,'Image','code.png')  # 验证码图片保存路径
config_ini_dir = os.path.join(base_dir,'setting','localElement.ini')  # localElement.ini 配置文件路径

 

2)read.ini.py :用于读取LocalElement.py文件中的配置信息

from setting.setting import config_ini_dir
import configparser

class Read_Ini(object): # 初始化
    def __init__(self,node = None):
        if node:
            self.node = node
        else:
            self.node = 'RegisterElement'  # 配置文件中的某个节点
        self.cf = self.load_ini()

    def load_ini(self):  # 加载文件
        cf = configparser.ConfigParser()  # 使用 configparser模块读取配置文件信息
        cf.read(config_ini_dir)  # 配置文件所在路径
        return cf

    def get_value(self,key): # 获取配置文件中key的value值
        data = self.cf.get(self.node,key)
        return data

 

3)base包下新建find_element.py文件:通过read_ini.py文件获取配置文件的信息,用于定位注册页面的目标元素

from util.read_ini import Read_Ini
class FindElement(object):
    """获取元素所在位置"""
    def __init__(self,driver):
        self.driver = driver
    def get_element(self,key):
        read_ini = Read_Ini()
        data = read_ini.get_value(key)
        by,value = data.split('>')

        try:
            if by == '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 == 'xpath':
                return self.driver.find_element_by_xpath(value)
            else:
                return self.driver.find_element_by_css(value)
        except Exception as e:
            # print("find_element错误信息:",e)
            return None

 

4)page包新建register_page.py文件: 通过find_element.py文件,获取目标元素所在的位置

from base.find_element import FindElement
class RegisterPage(object):
    """获取元素所在位置"""
    def __init__(self,driver):
        self.fd = FindElement(driver)
    
    #获取邮箱元素
    def get_email_element(self):
        return self.fd.get_element("user_email")

    #获取用户名元素
    def get_username_element(self):
        return self.fd.get_element("user_name")
    #获取密码元素
    def get_password_element(self):
        return self.fd.get_element("password")
    #获取验证码元素
    def get_code_element(self):
        return self.fd.get_element("code_text")
    #获取注册按钮元素
    def get_button_element(self):
        return self.fd.get_element("register_button")
    #获取邮箱错误元素
    def get_email_error_element(self):
        return self.fd.get_element("user_email_error")
    #获取用户名错误元素
    def get_name_error_element(self):
        return self.fd.get_element("user_name_error")
    #获取密码错误元素
    def get_password_error_element(self):
        return self.fd.get_element("password_error")
    #获取验证码错误元素
    def get_code_error_element(self):
        return self.fd.get_element("code_text_errorr")

 

5)handle包下新建register_handle.py文件:结合register_page.py文件获取目标元素位置,再自动输入对应信息

#coding=utf-8
from page.register_page import RegisterPage
from util.get_code_value import GetCode

class RegisterHandle(object):
    """打开页面后自动输入相应信息"""
    def __init__(self,driver):
        self.driver = driver
        self.register_p = RegisterPage(self.driver)

    #输入邮箱
    def send_user_email(self,email):
        # self.loger.info("输入的邮箱值是:"+email)
        self.register_p.get_email_element().send_keys(email)
    
    #输入用户名
    def send_user_name(self,username):
        # self.loger.info("输入的用户名是:"+username)
        self.register_p.get_username_element().send_keys(username)

    #输入密码
    def send_user_password(self,password):
        # self.loger.info("输入的密码是:"+password)
        self.register_p.get_password_element().send_keys(password)
    
    #输入验证码
    def send_user_code(self,file_name):
        get_code_text = GetCode(self.driver)
        code = get_code_text.code_online(file_name)
        self.register_p.get_code_element().send_keys(code)
    
    #获取文字信息
    def get_user_text(self,info,user_info):
        try:# 容错处理
            if info == 'user_email_error':
                text = self.register_p.get_email_error_element().text  # 获取邮箱错误信息
            elif info == 'user_name_error':
                text = self.register_p.get_name_error_element().text  # 获取用户名错误信息
            elif info == 'password_error':
                text = self.register_p.get_password_error_element().text  # 获取用户密码错误信息
            else:
                text = self.register_p.get_code_error_element().text  # 获取验证码错误信息
        except:
            text = None     
        return text
    #点击注册按钮
    def click_register_button(self):
        self.register_p.get_button_element().click()
    
    #获取注册按钮文字
    def get_register_btn_text(self):
        """如获取不到信息,表明页面已成功跳转"""
        return self.register_p.get_button_element().text

 

 

6)business包新建register_business.py文件:测试注册页面form表单功能逻辑

from handle.register_handle import RegisterHandle
class RegisterBusiness:
    """测试注册页面form表单功能情况"""
    def __init__(self,driver):
        self.register_h = RegisterHandle(driver)

    def user_base(self,email,name,password,file_name):
        self.register_h.send_user_email(email)
        self.register_h.send_user_name(name)
        self.register_h.send_user_password(password)
        self.register_h.send_user_code(file_name)
        self.register_h.click_register_button()
    
    def register_succes(self):
        if self.register_h.get_register_btn_text() == None:
            # 注册成功
            return True
        else:
            return False

    # 邮箱错误
    def login_email_error(self,email,name,password,file_name):
        self.user_base(email,name,password,file_name)   
        if self.register_h.get_user_text('email_error',"请输入有效的电子邮件地址") == None:
            #print("无错误,邮箱检验不成功")
            return True
        else:
            return False
        
   
    def login_name_error(self,email,name,password,file_name):
        self.user_base(email,name,password,file_name)
        if self.register_h.get_user_text('user_name_error',"字符长度必须大于等于4,一个中文字算2个字符") == None:
            #print("用户名检验不成功")
            return True
        else:
            return False
    
    # 密码错误
    def login_password_error(self,email,name,password,file_name):
        self.user_base(email,name,password,file_name)
        if self.register_h.get_user_text('password_error',"最少需要输入 5 个字符") == None:
            #print("密码检验不成功")
            return True
        else:
            return False

    # 验证码错误
    def login_code_error(self,email,name,password,file_name):
        self.user_base(email,name,password,file_name)
        if self.register_h.get_user_text('code_text_error',"验证码错误") == None:
            #print("验证码检验不成功")
            return True
        else:
            return False

 

7)case包新建first_case.py:测试注册页面form表单功能

from selenium import webdriver
from setting import setting
from business.register_business import RegisterBusiness



class FirstCase(object):
    def __init__(self, file_name, url):
        self.driver = webdriver.Chrome()
        self.driver.get(url)
        self.driver.maximize_window()
        self.file_name = file_name
        self.login = RegisterBusiness(self.driver)

    def test_login_success(self):
        login_success = self.login.register_succes()
        if login_success:
            print("注册成功,case调试失败!")

    def test_login_email_error(self):
        email_error = self.login.login_email_error('11111@qq.com','aaaa','111111',self.file_name)
        if email_error == True:
            print("Error: email没有错误提示,此条case执行失败!")

    def test_login_username_error(self):
        self.driver.refresh()
        username_error = self.login.login_name_error('2222222@qq.com','bbbbb','111111',self.file_name)
        if username_error == True:
            print("Error: username没有错误提示,此条case执行失败!")

    def test_login_password_error(self):
        self.driver.refresh()
        password_error = self.login.login_password_error('333333@qq.com','ccccc','111111',self.file_name)
        if password_error == True:
            print("Error: password没有错误提示,此条case执行失败!")

    def test_login_code_error(self):
        self.driver.refresh()
        code_error = self.login.login_code_error('44444444@qq.com','dddddddd','111111',self.file_name)
        if code_error == True:
            print("Error: password没有错误提示,此条case执行失败!")

    def main_run(self):

        self.test_login_email_error()
        self.test_login_username_error()
        self.test_login_password_error()
        self.test_login_code_error()
        self.driver.close()



if __name__ == '__main__':
    file_name = setting.code_path
    url = 'http://www.5itest.cn/register'
    first_case = FirstCase(file_name,url)
    first_case.main_run()

 

代码递进方向:

  read.ini.py → find_element.py → register_page.py → register_handle.py → register_business.py → first_case.py


 

附例:

get_code_value.py:获取注册页面图片,截取验证码图片部分区域,识别验证码图片,获取验证码内容

from PIL import Image
from setting.ShowapiRequest import ShowapiRequest
import time


class GetCode:
    """获取验证码图片,解析验证码图片并返回验证码值"""
    def __init__(self, driver):
        self.driver = driver

    def get_code_image(self, file_name):
        self.driver.save_screenshot(file_name)
        code_element = self.driver.find_element_by_id("getcode_num")
        left = code_element.location['x']
        top = code_element.location['y']
        right = code_element.size['width'] + left
        height = code_element.size['height'] + top
        im = Image.open(file_name)
        img = im.crop((left, top, right, height))
        img.save(file_name)
        time.sleep(1)

    # 解析图片获取验证码
    def code_online(self, file_name):
        self.get_code_image(file_name)
        r = ShowapiRequest("http://route.showapi.com/184-4", "62626", "d61950be50dc4dbd9969f741b8e730f5")
        r.addBodyPara("typeId", "35")
        r.addBodyPara("convert_to_jpg", "0")
        r.addFilePara("image", file_name)  # 文件上传时设置
        res = r.post()
        # print("test:",res.text)
        time.sleep(1)
        text = res.json()['showapi_res_body']
        # print(text)
        try:
            code = text['Result']
            return code
        except Exception as e:
            print('code_error:',e)
            return None
get_code_value.py

 

二、Unittest介绍及项目实战中的运用

1、Unittest简单使用:

import unittest
class FirstCase01(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print("所有case执行之前的前置")

    @classmethod
    def tearDownClass(cls):
        print("所有case执行之后的后置")

    def setUp(self):
        print("这个是case的前置条件")
    
    def tearDown(self):
        print("这个是case的后置调键")

    @unittest.skip("不执行第一条")  # 跳过此条case 不执行
    def testfirst01(self):
        print("这个第一条case")

    def testfirst02(self):
        print("这是第二条case")
    
    def testfirst03(self):
        print("这是第3条case")
    

if __name__ == '__main__':
    #unittest.main()
    suite = unittest.TestSuite()  # suite容器
    suite.addTest(FirstCase01('testfirst02'))
    suite.addTest(FirstCase01('testfirst01'))
    suite.addTest(FirstCase01('testfirst03'))
    unittest.TextTestRunner().run(suite)
# suite :容器 ,结果是集合类型
suite = unittest.defaultTestLoader.discover(case_path,'unittest_*.py')  # 批量选择性运行case
#三个参数:第一个传入路径;第二个匹配文件名,成功则将该文件内指定的case存入suite容器中;第三个参数默认为None

unittest.TextTestRunner().run(suite)    # 执行上述匹配成功的case



assertFalse(code_error, "msg")   # 用于判断结果

 

2、在项目中用HTMLTestRunner输出漂亮的HTML报告 

copy下述HTMLTestRunner代码,在项目中新建 HTMLTestRunner.py文件,将代码copy到里面即可使用

"""
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,'&');
    s = s.replace(/</g,'<');
    s = s.replace(/>/g,'>');
    return s;
}
 
/* obsoleted by detail in <div>
function showOutput(id, name) {
    var w = window.open("", //url
                    name,
                    "resizable,scrollbars,status,width=800,height=450");
    d = w.document;
    d.write("<pre>");
    d.write(html_escape(output_list[id]));
    d.write("\n");
    d.write("<a href='javascript:window.close()'>close</a>\n");
    d.write("</pre>\n");
    d.close();
}
*/
--></script>
 
%(heading)s
%(report)s
%(ending)s
 
</body>
</html>
"""
    # variables: (title, generator, stylesheet, heading, report, ending)


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

    STYLESHEET_TMPL = """
<style type="text/css" media="screen">
body        { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
table       { font-size: 100%; }
pre         { }
 
/* -- heading ---------------------------------------------------------------------- */
h1 {
    font-size: 16pt;
    color: gray;
}
.heading {
    margin-top: 0ex;
    margin-bottom: 1ex;
}
 
.heading .attribute {
    margin-top: 1ex;
    margin-bottom: 0;
}
 
.heading .description {
    margin-top: 4ex;
    margin-bottom: 6ex;
}
 
/* -- css div popup ------------------------------------------------------------------------ */
a.popup_link {
}
 
a.popup_link:hover {
    color: red;
}
 
.popup_window {
    display: none;
    position: relative;
    left: 0px;
    top: 0px;
    /*border: solid #627173 1px; */
    padding: 10px;
    background-color: #E6E6D6;
    font-family: "Lucida Console", "Courier New", Courier, monospace;
    text-align: left;
    font-size: 8pt;
    width: 500px;
}
 
}
/* -- report ------------------------------------------------------------------------ */
#show_detail_line {
    margin-top: 3ex;
    margin-bottom: 1ex;
}
#result_table {
    width: 80%;
    border-collapse: collapse;
    border: 1px solid #777;
}
#header_row {
    font-weight: bold;
    color: white;
    background-color: #777;
}
#result_table td {
    border: 1px solid #777;
    padding: 2px;
}
#total_row  { font-weight: bold; }
.passClass  { background-color: #6c6; }
.failClass  { background-color: #c60; }
.errorClass { background-color: #c00; }
.passCase   { color: #6c6; }
.failCase   { color: #c60; font-weight: bold; }
.errorCase  { color: #c00; font-weight: bold; }
.hiddenRow  { display: none; }
.testcase   { margin-left: 2em; }
 
 
/* -- ending ---------------------------------------------------------------------- */
#ending {
}
 
</style>
"""



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

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

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



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

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

    REPORT_CLASS_TMPL = r"""
<tr class='%(style)s'>
    <td>%(desc)s</td>
    <td>%(count)s</td>
    <td>%(Pass)s</td>
    <td>%(fail)s</td>
    <td>%(error)s</td>
    <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">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'> </div>"""

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


TestResult = unittest.TestResult

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

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

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


    def startTest(self, test):
        TestResult.startTest(self, test)
        # just one buffer for both stdout and stderr
        self.outputBuffer = io.StringIO()
        stdout_redirector.fp = self.outputBuffer
        stderr_redirector.fp = self.outputBuffer
        self.stdout0 = sys.stdout
        self.stderr0 = sys.stderr
        sys.stdout = stdout_redirector
        sys.stderr = stderr_redirector


    def complete_output(self):
        """
        Disconnect output redirection and return buffer.
        Safe to call multiple times.
        """
        if self.stdout0:
            sys.stdout = self.stdout0
            sys.stderr = self.stderr0
            self.stdout0 = None
            self.stderr0 = None
        return self.outputBuffer.getvalue()


    def stopTest(self, test):
        # Usually one of addSuccess, addError or addFailure would have been called.
        # But there are some path in unittest that would bypass this.
        # We must disconnect stdout in stopTest(), which is guaranteed to be called.
        self.complete_output()


    def addSuccess(self, test):
        self.success_count += 1
        TestResult.addSuccess(self, test)
        output = self.complete_output()
        self.result.append((0, test, output, ''))
        if self.verbosity > 1:
            sys.stderr.write('ok ')
            sys.stderr.write(str(test))
            sys.stderr.write('\n')
        else:
            sys.stderr.write('.')

    def addError(self, test, err):
        self.error_count += 1
        TestResult.addError(self, test, err)
        _, _exc_str = self.errors[-1]
        output = self.complete_output()
        self.result.append((2, test, output, _exc_str))
        if self.verbosity > 1:
            sys.stderr.write('E  ')
            sys.stderr.write(str(test))
            sys.stderr.write('\n')
        else:
            sys.stderr.write('E')

    def addFailure(self, test, err):
        self.failure_count += 1
        TestResult.addFailure(self, test, err)
        _, _exc_str = self.failures[-1]
        output = self.complete_output()
        self.result.append((1, test, output, _exc_str))
        if self.verbosity > 1:
            sys.stderr.write('F  ')
            sys.stderr.write(str(test))
            sys.stderr.write('\n')
        else:
            sys.stderr.write('F')


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

        self.startTime = datetime.datetime.now()


    def run(self, test):
        "Run the given test case or test suite."
        result = _TestResult(self.verbosity)
        test(result)
        self.stopTime = datetime.datetime.now()
        self.generateReport(test, result)
        # print >> sys.stderr, '\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 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.decode('latin-1')
            uo = e
        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(str(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)
HTMLTestRunner

 

参考:

import unittest
from test_mathfunc import TestMathFunc
from HTMLTestRunner import HTMLTestRunner

if __name__ == '__main__':
    suite = unittest.TestSuite()

    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
    with open(r'D:\HTMLReport.html', 'wb') as f:
        runner = HTMLTestRunner(stream=f,
                                title='MathFunc Test Report',
                                description='generated by HTMLTestRunner.',
                                verbosity=2
                                 )
        runner.run(suite)

 

suite = unittest.TestSuite()
    suite.addTest(FirstCase('test_login_success'))
    # suite.addTest(FirstCase('test_login_code_error'))
    suite.addTest(FirstCase('test_login_email_error'))
    suite.addTest(FirstCase('test_login_username_error'))
    with open(setting.report_path, 'wb') as f:
        runner = HTMLTestRunner(stream=f,
                                title="This is first123 report",
                                description="这个是我们第一次测试报告",
                                verbosity=2
                                )
        runner.run(suite)
demo

 * unittest中,执行case时,如果执行失败,错误信息会被保存到_outtime中,我们可通过一些技巧对这些错误信息进行操作,比如:case执行完,如果出错,可以通过tearDown方法 将错误信息页面截图,保存到指定路径,方便检查出错原因 ↓

    def tearDown(self):
        for method_name, error in self._outcome.errors:  # case如果执行失败,错误会保存到_outcome.errors 中
            if error:  # 将错误信息截图,保存到指定路径
                case_name = self._testMethodName  # case名,即定义好的方法名
                report_error_name = case_name + '.png'
                report_error_path = os.path.join(setting.base_dir,'report', report_error_name)
                print("report_error:", report_error_name)
                self.driver.save_screenshot(report_error_path)

 


 

使用unittest框架 + HTMLTestRunner 结合,优化 first_case.py 代码:

from selenium import webdriver
from setting import setting
from business.register_business import RegisterBusiness
import unittest
import os
from util.HTMLTestRunner import HTMLTestRunner


class FirstCase(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome()
        cls.driver.get('http://www.5itest.cn/register')
        cls.driver.maximize_window()
        cls.file_name = setting.code_path
        cls.login = RegisterBusiness(cls.driver)

    def setUp(self):
        self.driver.refresh()
        # self.login = RegisterBusiness(self.driver)


    def tearDown(self):
        for method_name, error in self._outcome.errors:  # case如果执行失败,错误会保存到_outcome.errors 中
            if error:
                case_name = self._testMethodName  # case名,即定义好的方法名
                report_error_name = case_name + '.png'
                report_error_path = os.path.join(setting.base_dir,'report', report_error_name)
                print("report_error:", report_error_name)
                self.driver.save_screenshot(report_error_path)

    @classmethod
    def tearDownClass(cls):
        cls.driver.close()

    def test_login_success(self):
        login_success = self.login.register_succes()
        return self.assertFalse(login_success, "注册成功,case调试失败!")

    def test_login_email_error(self):
        email_error = self.login.login_email_error('1586457@qq.com','vvv','221111',self.file_name)
        return self.assertFalse(email_error, "邮箱格式输入正确,此条case执行失败!") # 判断email_error是否为False,如是表示测试通过,如不是表示测试失败,显示参数二的信息
        # if email_error == True:
        #     print("Error: email没有错误提示,此条case执行失败!")

    def test_login_username_error(self):
        # self.driver.refresh()
        username_error = self.login.login_name_error('qq.com','bbbaac','111111',self.file_name)
        return self.assertFalse(username_error, "用户名格式输入正确,此条case执行失败!")

    def test_login_password_error(self):
        # self.driver.refresh()
        password_error = self.login.login_password_error('3333666@qq.com','ccc','1114441',self.file_name)
        return self.assertFalse(password_error, "密码格式输入正确,此条case执行失败!")

    def test_login_code_error(self):
        # self.driver.refresh()
        code_error = self.login.login_code_error('44465745444@qq.com','ddfghdddd','1111',self.file_name)
        return self.assertFalse(code_error, "验证码格式输入正确,此条case执行失败!")

    # def main_run(self):
    #
    #     self.test_login_email_error()
    #     self.test_login_username_error()
    #     self.test_login_password_error()
    #     self.test_login_code_error()
    #     self.driver.close()



if __name__ == '__main__':
    # file_name = setting.code_path
    # url = 'http://www.5itest.cn/register'
    # first_case = FirstCase(file_name,url)
    # first_case.main_run()

    suite = unittest.TestSuite()
    suite.addTest(FirstCase('test_login_email_error'))
    suite.addTest(FirstCase('test_login_username_error'))
    suite.addTest(FirstCase('test_login_password_error'))
    suite.addTest(FirstCase('test_login_code_error'))
    suite.addTest(FirstCase('test_login_success'))
    with open(setting.report_path, 'wb') as f:
        runner = HTMLTestRunner(stream=f,
                                title="This is first123 report",
                                description="这个是我们第一次测试报告",
                                verbosity=2
                                )
        runner.run(suite)

 


 

二、数据驱动

 使用数据驱动,可以去除冗余。如同一方法传入不同参数这种,可以使用数据驱动程序,根据需求传入不同参数,驱动同一程序执行。

1、ddt安装:pip install ddt

ddt简单使用:

import ddt
import unittest
@ddt.ddt  # DataTest类加ddt装饰器
class DataTest(unittest.TestCase):
    def setUp(self):
        print("这个是setup")
    def tearDown(self):
        print("这个是teardown")

    @ddt.data(  # ddt数据
        [1,2,3,4],
        [3,4],
        [5,6]
    )
    @ddt.unpack  #传递的是复杂的数据结构时使用。比如使用元组或者列表,不是复杂的数据不需要用到unpack
    def test_add(self,a,b):
        print(a+b)

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

 

使用方法:

# 使用方法
dd.ddt:
# 装饰类,也就是继承自TestCase的类。

ddt.data:
# 装饰测试方法。参数是一系列的值。

ddt.file_data:
# 装饰测试方法。参数是文件名。文件可以是json 或者 yaml类型。

# 注意,如果文件以”.yml”或者”.yaml”结尾,ddt会作为yaml类型处理,其他所有文件都会作为json文件处理。

# 如果文件中是列表,每个列表的值会作为测试用例参数,同时作为测试用例方法名后缀显示。

# 如果文件中是字典,字典的key会作为测试用例方法的后缀显示,字典的值会作为测试用例参数。

ddt.unpack:
# 传递的是复杂的数据结构时使用。比如使用元组或者列表,添加unpack之后,ddt会自动把元组或者列表对应到多个参数上。字典也可以这样处理

 

使用json文件:

新建文件 test_data_list.json:

[
    "Hello",
    "Goodbye"
]

新建文件  test_data_dict.json:

{
    "unsorted_list": [ 10, 12, 15 ],
    "sorted_list": [ 15, 12, 50 ]
}

数据驱动测试脚本ddt_test.py:

import unittest
from ddt import ddt, file_data
from ddt_demo.mycode import has_three_elements,is_a_greeting

@ddt
class FooTestCase(unittest.TestCase):

    @file_data('test_data_dict.json')
    def test_file_data_json_dict(self, value):
        self.assertTrue(has_three_elements(value))

    @file_data('test_data_list.json')
    def test_file_data_json_list(self, value):
        self.assertTrue(is_a_greeting(value))
        
if __name__=='__main__':
    unittest.main(verbosity=2)

 2、以文件的形式实现数据驱动( excel表 )

1)xlrd安装:

 pip install xlrd

2)通过xlrd 获取excel表中数据行数,将每行数据以列表形式添加到一个大列表中

import xlrd
from setting.setting import excel_path

class Excel_Opertion(object):
    """excel表数据相关操作"""

    def __init__(self,ex_path=None,index=None):
        if ex_path == None:
            self.excel_path = excel_path  # 默认excel文件路径
        else:
            self.excel_path = ex_path
        if index == None:
            index = 0
        self.data = xlrd.open_workbook(self.excel_path)
        self.table = self.data.sheets()[index] # sheets第一页数据

    # 获取excel数据,按照每行一个list,添加到一个大的list里面
    def get_data(self):
        result = []
        rows = self.get_lines()
        if rows !=None:
            for i in range(1,rows):
                row = self.table.row_values(i)
                # print(row)   # ['test001@qq.com', 'Mushishi001', '111111', 'code', 'user_email_error', '请输入有效的电子邮件地址']
                result.append(row)   # [['test001@qq.com', 'Mushishi001', '111111', 'code', 'user_email_error', '请输入有效的电子邮件地址'], ['test002.com', 'Mushishi002', '111112', 'code', 'user_email_error', '请输入有效的电子邮件地址']]
            return result
        return None

    # 获取excel行数
    def get_lines(self):
        rows = self.table.nrows  # 获取行数
        if rows > 1:
            return rows
        return None

 

3)数据驱动case

 从excel表获取数据,通过ddt模块实现数据驱动模式,数据 → 驱动 → 程序,循环调用 test_register_case() 方法,直至excel表数据执行完

mport ddt
import unittest
import os
from selenium import webdriver
from setting import setting
from business.register_business import RegisterBusiness
from util.excel_operation import Excel_Opertion
from util.HTMLTestRunner import HTMLTestRunner

ex_opr = Excel_Opertion()  # 实例化
ex_data = ex_opr.get_data()  # 获取excel表中每行数据

@ddt.ddt
class FirstDdtCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome()
        cls.driver.get('http://www.5itest.cn/register')
        cls.driver.maximize_window()
        cls.file_name = setting.code_path
        cls.login = RegisterBusiness(cls.driver)

    def setUp(self):
        self.driver.refresh()
        # self.login = RegisterBusiness(self.driver)

    def tearDown(self):
        for method_name, error in self._outcome.errors:  # case如果执行失败,错误会保存到_outcome.errors 中
            if error:
                # case_name = self._testMethodName  # case名,即定义好的方法名
                report_error_name = self.assertCode + '.png'
                report_error_path = os.path.join(setting.base_dir, 'report', report_error_name)
                self.driver.save_screenshot(report_error_path)

    @classmethod
    def tearDownClass(cls):
        cls.driver.close()

    @ddt.data(*ex_data)
    def test_register_case(self,ex_data):   # ex_data:[[],[],..] 列表套列表
        """数据驱动模式,会按ex_data列表数据,一行一行循环执行,直至列表数据执行完毕"""
        email, username, password, self.assertCode, assertText = ex_data  # 将ex_data每个子列表的数据按顺序赋值。验证码需要提供路径,单独给
        # 邮箱、用户名、密码、验证码、错误信息定位元素、错误提示信息
        register_error = self.login.register_function(email, username, password, self.file_name,self.assertCode, assertText)
        self.assertFalse(register_error,"测试失败:{}".format(self.assertCode))

 

test_register_case方法:
# 数据驱动,只执行此条代码
    # 邮箱、用户名、密码、验证码、错误信息定位元素、错误提示信息
    def register_function(self,email,username,password,file_name,assertCode,assertText):
        self.user_base(email,username,password,file_name)
        if self.register_h.get_user_text(assertCode,assertText) == None:
            return True
        else:
            return False
test_register_case

 

转载于:https://www.cnblogs.com/Eric15/articles/9847556.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值