2.3.1 自动化测试如何分层
前言
在测试自动化中,测试代码中不仅仅包含测试逻辑,还包含许多其他代码,比如 URL 拼接、html/xml 解析、访问 UI 控件,等等。若把测试逻辑与这些无关代码混在一起,测试逻辑将会很难理解, 也不容易维护。小编于本文介绍如何用分层结构来解决测试自动化中遇到的这些问题。
为什么要写框架?
从个人方面来说:
-
逼格高,让别人感觉你好厉害,技术强
-
面试是加分项,会写框架,可以作为谈资硬性指标
从实际方面来说:
-
好的测试框架,可以稳定性,健壮性强,可降低代码维护成本
-
方便定位问题,失败定位问题会比较方便
-
可以提升测试效率,编写脚本成本,拿来就用,直接点方法就行
自动化技术
软件自动化测试技术主要有:录制/回放、线性脚本、结构化脚本、数据驱动、关键字驱动。
-
线性脚本:最早的脚本方式,直接一行一行进行实现顺序的代码执行;该阶段只是适用于初学者的理解selenium相关的api操作;该类型的脚本没有任何意义。
-
结构化脚本:主要是通过selenium+api+python面向对象(类与对象)进行封装后的脚本;该类型脚本主要包括两种类型:模块化脚本、库/包脚本;不同的业务场景会涉及在不同的模块中;
-
数据驱动脚本:实现脚本中的数据与代码进行分离操作;此步主要考虑的是业务场景上的数据(应该将数据提取到专门存储数据的格式文件中、数据参数化操作)
-
关键字驱动脚本:将每步业务操作封装到每个关键字(例如:登陆操作---->input:username input:password button:submit----->Selenium IDE RF);将每个业务流操作封装成关键字对象;
如何分层封装
-
分层会根据不同的设计者、公司定义规则、参考主流分层的架构模式等等所得到的封层模型会略有不同;但是整体的核心分层思想是不会变;
-
一般定义分层层次为4-6层较为合适;可以根据自身的思想进行封装设计;
-
a.页面元素处理层:(PO模式:Page Object表示IDE是页面对象管理;表示的是意思:将每个页面上的所有元素定义在一个模块中)----->对后期前端页面进行修改,后期脚本进行维护十分方便(定位明确)
-
b.业务流操作层:表示的是基于页面元素处理层实现业务流的*组织;(实际对应自动化的业务流场景的测试用例执行)
-
c.测试用例层:根据业务流场景进行设计相应的测试用例并执行;用例的执行都是通过框架完成(单元测试框架:unittest、pytest),并且可以很好的*组织测试用例最好执行产生结果并分析
-
d.数据分离层:将脚本中的所有数据全部提取出来进行专门的数据模块管理,后期可直接修改相应数据即可;不需要进行底层的代码查看分析;
-
e.公共层:常量数据的存储、报告的生成、日志的保存、邮件的发送等
-
f.主程序入口应用层:执行只需要设定一个入口,最后整体框架只需要执行主程序入口模块,只需要修改数据分离层中的数据,只需要新增测试用例层的用例即可;其它层底层进行封装;
-
例如:整体工程如图:
驱动器对象封装
首先需要封装的就是驱动器对象的获取,可以将驱动器对象中的浏览器进行基本设置(例如:驱动器文件、浏览器下载默认地址、传入不同浏览器参数完成不同浏览器驱动器对象创建),再者就是定位元素的方式(前面我们总结过定位方式约三十多种:常规八种,复数形式八种,By形式八种,js方式六种、jquery、还有xpath模糊定位、二次定位等)都可以直接封装到一个或者两个方法中;
部分封装后的代码如下:
#-*- coding:utf-8 -*-#
#-------------------------------------------------------------------------
#ProjectName: Python2020
#FileName: Base_Page.py
#Author: mutou
#Date: 2020/6/18 22:17
#Description:是所有页面都需要获取驱动器对象进行抽取出来的基类
#--------------------------------------------------------------------------
#需要获取驱动器对象
from selenium import webdriver
from CRMProject.GETP_ATH import GETPATH
import os
import subprocess
import sys
from selenium.common.exceptions import WebDriverException
#设定驱动器版本参数:当前自带的驱动器chrome:70以上 当前的火狐:80以上
browser_version={"chrome":70,"firefox":60,"edge":18}
#此模块相当于封装底层的所有相关东西:驱动器对象、元素定位方式
class BasePage(object):
def __init__(self, url, browsertype="chrome"):
"""
:param url:需要访问的url地址
:param browsertype: 传入浏览器类型实现对应浏览器的驱动器对象创建;忽略大小写;默认使用谷歌浏览器;其值可以是:firefox、ie、edge
"""
#该变量不需要被外界所直接访问,那么将其设定为私有的变量值
__browsertype=browsertype.lower()
#下载的所有资源存储目录:
self.download_path=GETPATH + "\Page_Object\Base\DownLoad"
#驱动器地址只需要当前模块引用,其他模块不需要引用
__driverdir=GETPATH+"\Page_Object\Base\DriverDir"
try:
if __browsertype == "firefox":
fireoptions = webdriver.FirefoxProfile()
# 将下载的方式设定,2表示保存到指定的文件夹路径
fireoptions.set_preference("browser.download.folderList", 2)
# 设定文件夹路径
fireoptions.set_preference("browser.download.dir", self.download_path)
# 去除询问框,下载文件是存在对应的一个文本类型的,其文本类型即content-type:text/html text/css text/json
# 文件是以二进制流的形式进行传输的
fireoptions.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream")
self.get_driver = webdriver.Firefox(firefox_profile=fireoptions,executable_path=__driverdir+"\geckodriver.exe")
elif __browsertype == "chrome":
# 对谷歌浏览器进行相关设置
options = webdriver.ChromeOptions()
# options的值都是以键值对的形式存在:设置最多的一个操作就是修改下载的默认路径
dict1 = {"download.default_directory":self.download_path}
# 将设定的操作添加到谷歌浏览器中;prefs是谷歌浏览器存在一个选项的关键字名称,不能够随意定义
options.add_experimental_option("prefs", dict1)
# 浏览器的类首字母都是大写的
self.get_driver = webdriver.Chrome(options=options,executable_path=__driverdir+"\chromedriver.exe")
elif __browsertype =="ie":
self.get_driver=webdriver.Ie()
elif __browsertype=="edge":
#确保edge的驱动器进行安装
#os.system("DISM.exe /Online /Add-Capability /CapabilityName:Microsoft.WebDriver~~~~0.0.1.0")
# self.get_driver=webdriver.Edge()
self.__check_edge_driver()
#以上都错误,则可以抛出驱动器异常,也可以写输出语句print,实际后期此步还需要优化,此步的操作应该要将其写入到日志模块中
else:
print("传入的浏览器类型参数错误")
self.get_driver.get(url)
self.get_driver.maximize_window()
# 设定隐式等待的时间
self.get_driver.implicitly_wait(30)
except:
print("%s驱动器与当前浏览器不匹配!"%__browsertype)
#声明一个方法进行判定edge的驱动器
def __install_edge_driver(self,username="administrator",password="vA0j26NY"):
#默认的管理员身份用户名:administrator、123456 域不写表示的是当前域
sub2 = subprocess.run(
'lsrunase /user:%s /password:%s /domain: /command:"DISM.exe /Online /Add-Capability /CapabilityName:Microsoft.WebDriver~~~~0.0.1.0" /runpath:c:\\'%(username,password),
capture_output=True)
#返回的是输出结果,如果命令执行成功是没有结果输出的,并且返回码是0;
return (sub2.stdout,sub2.returncode)
#实现edge驱动器是否安装的判定
def __check_edge_driver(self):
try:
self.get_driver = webdriver.Edge()
except WebDriverException:
get_result=self.__install_edge_driver()
if len(get_result[0])==0 and get_result[1]==0:
self.get_driver = webdriver.Edge()
else:
print("当前的驱动器版本与浏览器版本不一致")
except Exception:
print(sys.exc_info())
#考虑:自动实现浏览器的升级或降级:#1.同样使用第三方工具完成 2.获取官方的下载对应浏览器的地址,执行自动安装
#http://file.cdn.cqttech.com/file/ChromeCore_1287_3.0.1.6.exe
@staticmethod
def get_element1(get_driver,type,value,js_type="id"):
__get_type = type.lower()
if __get_type == "id":
return get_driver.find_element_by_id(value)
#封装所有定位方式的方法:就相当于等价封装find_element(By.ID,"")
def get_element(self,type,value,js_type="id"):
"""
:param type:其值可以是id、class、name、xpath、css、js、jquery ;如果不是指定的此类型参数的话,其会抛出一个值错误异常
#xpath的模糊定位、文本定位,js定位,jquery定位
#xpath定位的所有方法封装method:contains/starts-with/text()
:return:
"""
try:
__get_type=type.lower()
if __get_type=="id":
return self.get_driver.find_element_by_id(value)
elif __get_type=="name":
return self.get_driver.find_element_by_name(value)
elif __get_type=="class":
return self.get_driver.find_element_by_class_name(value)
elif __get_type=="tag":
return self.get_driver.find_element_by_tag_name(value)
elif __get_type=="link":
return self.get_driver.find_element_by_link_text(value)
elif __get_type=="partial":
return self.get_driver.find_element_by_partial_link_text(value)
elif __get_type=="xpath":
return self.get_driver.find_element_by_xpath(value)
elif __get_type=="css":
return self.get_driver.find_element_by_css_selector(value)
elif __get_type=="js":
#又要分id,name,classname,tagname,css
return self.js_element(value,js_type)
elif __get_type=="jqueyr":
pass
else:
raise ValueError
except:
print(sys.exc_info())
#js的定位封装
def js_element(self,value,js_type="id"):
__js_type=js_type.lower()
if __js_type=="id":
return self.get_driver.execute_script("return document.getElementById('%s')"%value)
#如果是name,tag、classname的话返回多个元素对象的话,默认操作第一个
elif __js_type=="name":
return self.get_driver.execute_script("return document.getElementsByName('%s')"%value)[0]
elif __js_type=="tag":
return self.get_driver.execute_script("return document.getElementsByTagName('%s')"%value)[0]
elif __js_type=="css":
pass
#今天练习:把js、jquery进行封装
def get_elements(self, type, value):
"""
:param type:其值可以是id、class、name、xpath、css、js、jquery
:return:
"""
__get_type = type.lower()
if __get_type == "id":
return self.get_driver.find_elements_by_id(value)
elif __get_type == "name":
return self.get_driver.find_elements_by_name(value)
elif __get_type == "class":
return self.get_driver.find_elements_by_class_name(value)
elif __get_type == "tag":
return self.get_driver.find_elements_by_tag_name(value)