通过unittest框架完成自动化分层操作,实现数据分离,减少代码于数据之间的依赖性,完成报告的生成并自动发送一系列操作。
前言:
有人认为,在进行自动化测试过程中,测试代码只需要包含测试逻辑即可。其实不然,他需要包括很多类的代码,如URL拼接、访问UI控件、HTML/XML的解析等,如果将测试逻辑代码于上面这些类型的代码混合在一起,那么代码就显得非常难以理解,也不容易维护,所以需要使用分层结构来解决自动化测试过程中遇到的这些问题。
正文:
一、为什么要写框架
首先需要了解什么 是框架,。框架用于解决或者处理复杂的问题并将其简单化,从个人方面来说,如果在自动化测试过程中融入了框架,可以提高脚本的规范性,这也是面试的加分项,是自身能力的硬性指标。从实际工作而言,一个好的测试框架可以提升项目的稳定性、健壮性。降低维护成本,也非常容易解决问题,如准确定位问题等。
二、自动化技术
现如今主要的软件自动化测试技术有录制/回放、脚本技术、数据驱动、关键字驱动、业务驱动。
在学习数据驱动之前,先了解脚本技术的发展,脚本技术最早是基于线性脚本发展的,所谓线性就是一行一行的顺序执行代码。该阶段只适用于让初学者理解selenium相关API接口的操作,该类型脚本在实际工作中你那个没有任何意义。
线性脚本之后就是结构化脚本。主要是通过使用selenium+API+Python模块封装面向对象(类于对象)的脚本,该类型脚本主要包括两种类型:模块化脚本、库/包脚本,不同的业务场景会涉及不同的模块。
随着结构化脚本的发展,慢慢的进入数据驱动时代。数据驱动时代实现了将脚本中的数据于代码进行分离的操作,从而减少了其彼此的依赖,脚本的利用率、可维护性大大提高,但是界面变化对其影响仍然非常大。
基于这种考虑,数据驱动技术将测试逻辑按照关键字进行分解,形成数据文件,关键字对应封装的业务逻辑,即关键字驱动,这也是数据驱动测试的一种改进。目前大多数测试工具技术处于数据驱动技术到关键字驱动技术之间。
最后就是根据关键字驱动完成不同类型的业务驱动,业务驱动可以分为接入层业务驱动、业务层业务驱动、数据层业务驱动和性能驱动等。
发展:线性脚本——>结构化脚本——>数据驱动——>关键字驱动
三、如何分层封装
根据不同的设计者、公司定义规则,参考主流分层的架构模式等所得到的分层模型会略有不同,但是整体的核心分层思想不变。
一般将分层层次定义为4~6层,可以根据实际情况进行封装设计。
1、页面元素处理层:即Page Object(PO模式)表页面对象管理,将每个页面上的所有元素定义在一个模块中,即使后期要对前段页面进行修改,其脚本的维护也十分方便。
2、业务流操作层:表示基于页面元素处理层实现业务流的自由组织,实际对应自动化测试的业务流场景的执行测试用例
3、测试用例层:根据业务流场景设计相应的测试用例并执行,用例的执行都是通过框架完成(unittest、pytest),并且可以很好的自由组织测试用例的执行并分析结果。
4、数据分离层:将脚本中的所有数据提取出来进行专门的数据模块管理,后期直接修改相应的数据即可,不需要进行底层代码的查看分析。
5、公共层:进行常量数据的存储、报告的生成、日志的保存、邮件的发送等
6、主程序入口应用层:执行只需要设定一个入口,最后整体框架只需要执行主程序入口模块、修改数据分离层中的数据、新增测试用例层的用例即可,其他底层进行封装。
四、驱动器对象示例封装
首先需要封装驱动器对象的获取方法,可以将驱动器对象中的浏览器进行基本设置,如驱动器文件、浏览器下载默认地址、传入不同浏览器参数完成不同浏览器驱动器对象创建等。再着就是元素定位的方式,前面总结的定位方式有三十多种,其中常规八种、复数八种、By形式八种、JS方式六种、jQuery,还有XPath模糊定位、二次定位等,这些都可以直接封装到方法中
# ----------------------------------- ''' @Author:C_N_Candy @Date :2023/7/9 17:07 @File :GET_PATH.py @Desc :获取当前工作目录路径 ''' # ------------------------------------ import os def GETPATH(): path = os.getcwd() # 获取当前工作目录 # print(path) return path if __name__ == '__main__': GETPATH()
# ----------------------------------- ''' @Author:C_N_Candy @Date :2023/7/9 18:01 @File :Base_Page.py @Desc :从所有需要获取驱动器对象的页面抽取出来的基类 ''' # ------------------------------------ from selenium import webdriver import os import subprocess import sys from selenium.common.exceptions import WebDriverException from CRMProject.GET_PATH import GETPATH from selenium.webdriver.common.by import By # 设定驱动器版本参数,当前自带的驱动器Chrome:70以上当前的火狐:80以上 broser_version = {'Chrome': 70, 'FireFox': 80, '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("broser.download.folderList", 2) # 设定文件夹路径 fireoptions.set_preference("broser.download.dir", self.download_path) """ 去除询问窗口,定义下载文件的文本类型:content-type:text/html text/css text/json """ fireoptions.set_preference("broser.helperApps.neverAsk.saveToDisk", "application/octet-stream") self.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.driver = webdriver.Chrome(options=options, executable_path=__driverdir + "\chromedriver.exe") elif __browsertype == "Ie": self.driver = webdriver.Ie() elif __browsertype == "Edge": # 确定Edge的驱动已经安装的话, # os.system("DISM.exe /Online /Add-Capability /CapabilityName:Microsoft.WebDriver~~~~0.0.1.0") # self.driver = webdriver.Edge() self.__check_edge_driver() # 以上都错误的话,则可以抛出驱动器异常,或者print输出错误语句,实际上后期优化,写入日志中 else: print("传入的浏览器类型参数错误") self.driver.get(url) self.driver.maximize_window() self.driver.implicitly_wait(30) except: print("%s驱动器与当前浏览器不匹配!" % __browsertype) # 声明一个方法判定Edge的驱动器是否下载 def __install_edge_driver(self, username="administrator", password="123456"): sub2 = subprocess.run( 'Isrunase/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.driver = webdriver.Edge() except WebDriverException: get_result = self.__install_edge_driver() if len(get_result[0]) == 0 and get_result[1] == 0: self.driver = webdriver.Edge() else: print("当前驱动器版本与浏览器版本不一致") except Exception: print(sys.exc_info()) @staticmethod def get_element1(driver, type, value, js_type='id'): # 默认id;忽略大小写 __get_type = type.lower() if __get_type == 'id': return driver.find_element(By.ID, value) # 封装所有定位方式的方法 def get_element(self, type, value, js_type="id"): """ :param type: 其值可以是id,class,name,tag,link,partial,XPath,CSS,JS,jQuery;如果不是指定类型的参数,则会抛出异常 :param value: :param js_type: :return: """ try: __get_type = type.lower() if __get_type == "id": return self.driver.find_element(By.ID, value) elif __get_type == "class": return self.driver.find_element(By.CLASS_NAME, value) elif __get_type == "name": return self.driver.find_element(By.NAME, value) elif __get_type == "tag": return self.driver.find_element(By.TAG_NAME, value) elif __get_type == "link": return self.driver.find_element(By.LINK_TEXT, value) elif __get_type == "XPath": return self.driver.find_element(By.XPATH, value) elif __get_type == "CSS": return self.driver.find_element(By.CSS_SELECTOR, value) elif __get_type == "partial": return self.driver.find_element(By.PARTIAL_LINK_TEXT, value) elif __get_type == "JS": return self.js_element(value, js_type) elif __get_type == "jQuery": 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.driver.execute_script("return document.getElementById('%s')"%value) # 如果是name,tag,css返回多个元素对象,默认操作第一个 elif __js_type=="name": return self.driver.execute_script("return document.getElementByName('%s')"%value)[0] elif __js_type=="tag": return self.driver.execute_script("return document.getElementByTagName('%s')"%value)[0] elif __js_type=="CSS": return self.driver.execute_script("return document.querySelectorAll('%s')"%value)[0] elif __js_type=="class": return self.driver.execute_script("return document.getElementByClassName('%s')"%value) else: raise ValueError def get_elements(self,type,value): """ :param type: 其值可以是id,class,name,tag,link,partial,XPath,CSS,JS,jQuery :param value: :return: """ __get_type = type.lower() if __get_type == "id": return self.driver.find_elements(By.ID, value) elif __get_type == "class": return self.driver.find_elements(By.CLASS_NAME, value) elif __get_type == "name": return self.driver.find_elements(By.NAME, value) elif __get_type == "tag": return self.driver.find_elements(By.TAG_NAME, value) elif __get_type == "link": return self.driver.find_elements(By.LINK_TEXT, value) elif __get_type == "XPath": return self.driver.find_elements(By.XPATH, value) elif __get_type == "CSS": return self.driver.find_elements(By.CSS_SELECTOR, value) elif __get_type == "partial": return self.driver.find_elements(By.PARTIAL_LINK_TEXT, value) elif __get_type == "jQuery": pass else: raise ValueError
对驱动器对象、各种定位方式封装。源码参考
参考扩展:
Python 中 OS 模块获取文件/目录路径方法 - 守护往昔 - 博客园 (cnblogs.com)
python3中super()参数意义和用法_super里面的参数_wowocpp的博客-CSDN博客
【Python】python之subprocess模块详解_python subprocess_伐尘的博客-CSDN博客
import os # 导入必要模块 main_path="I:/s/ss/"#文件保存路径,如果不存在就会被重建 if not os.path.exists(main_path):#如果路径不存在 os.makedirs(main_path)
import os filename = '/Users/joseph/work/python/poem.txt' if not os.path.exists(filename): os.system(r"touch {}".format(path))#调用系统命令行来创建文件
标签:__,return,get,unittest,driver,----,测试,type,self From: https://www.cnblogs.com/Candy123/p/17519597.html