新方案的设计
一、通用的方案概要
自动化的实施,基本都是覆盖用例、调度执行、结果检测、结果上报。什么时候通过什么途径执行什么场景用例,执行的检测结果是什么,测试结果需要同步到哪里。
自动化的实施有很多需要挑战的内容:就如脚本的稳定性、脚本编写的效率、脚本的维护成本、断言的有效性…。在此之上我理解最难的挑战还是怎么让团队接受自动化实施的价值。
二、方案拆解(基础部分:事件能力、最小单元、测试用例)
为了加快节奏,本节的拆解我计划介绍的是最基层的内容,不包含CI/CD模块,在后续的章节中会继续升级。
①事件能力
①.①概述
最小单元事件设计的基准为单个最小函数,这里的函数为关键字函数,就如点击操作、输入操作、滑动操作。在此我觉得上一段简单的demo代码更加容易清楚它的定位:
def click(self,coor :tuple,msg=None):
"""
坐标点击事件
:param coor: 元组坐标(x,y)
:param msg:
:return:
"""
touch(coor)
②单元模块
②.①概述
本次方案以.yml文件配置代替实例化的单元模块,后续可规划以web界面、GUI界面呈现编排,其用意也是想抽象出来,以便单元模块公共化,可以支撑脚本用例的快速组装。本设计的单元模块的与PageObject类似,但也支持更加定制的跨页面特殊处理定制。
单元模块案例:在登录页面中包含账号输入框、密码输入框、登录按钮,此场景可以设计单元模块包含:
1.一整个用户的登录流程
2.单个输入框的操作、单个按钮的操作
②.②单元模块的设计设想及场景
单元模块的支持依赖事件能力:如上文①中的各类事件。
在我的构想中一个单元模块的设计步骤如下(以输入登录账号为demo):
#step-1:识别输入框位置,获得输入框的中心坐标(输入框通过文本识别,没有文案可以精准识别,可能无法通过文字识别获得,可以通过训练自己的ocr实验,对输入框进行打标标记。在我的专题文章中有关于自己训练ocr模型的介绍,具体可以参考训练自己的ocr模型(直跳)),当然还有别的方案,后续会陆续介绍。
#step-2:点击识别的中心位置#step-3:操作输入事件
③用例组装
用例组装的核心就是将yml配置解析完成后进行动态引包,通过关键字驱动将对应的函数进行调用执行。在不做配置化的时候,testcase代码大概是这样:
class LuckySign:
def __init__(self):
self.openMiniProgeamElem = OpenMiniProgeamElem()
self.homeElem = HomeElem()
self.personalCenterElem = PersonalCenterElem()
self.settingElem = SettingElem()
self.loginElem = LoginElem()
self.smallXingXingElem =SmallXingXingElem()
self.signElem = SignElem()
self.wechatElem = WechatElem()
def initConsumerAccount(self):
self.wechatElem.restartWechat()
self.loginElem.initConsumerAccount()
def doSign(self):
# 执行签到
self.homeElem.openPersonalCenter()
# 处理个人中心引导
self.personalCenterElem.guideOperation()
# 弹窗处理
self.personalCenterElem.checkWindowsAndClose()
# 打开签到页面
self.personalCenterElem.openSmallXingXing()
# 签到页面渲染校验
self.signElem.checkSignPageLoad()
# 签到操作
self.signElem.clickSignBtn()
# 防止订阅
self.personalCenterElem.subScribe()
# 签到明细校验
self.signElem.openSignDetailed()
def dotest(self):
self.personalCenterElem.guideOperation()
luckySign = LuckySign()
luckySign.initConsumerAccount()
luckySign.doSign()
现在我们做的是将一个个的step转换成了yml配置节点。其核心就是动态引入,动态执行。将事件函数的路径与关键字做哈希映射。如下:
import importlib
# 定义关键字和对应的函数
keywords = {
'keyword1': 'ymlFunc.func_a',
'keyword2': 'ymlFunc.func_b',
}
def func_a():
print('这是一个函数:func_a')
def func_b():
print('这是一个函数:func_b')
# 测试调用
def call_function(keyword):
func_name = keywords.get(keyword)
if func_name:
module_name, function_name = func_name.rsplit('.', 1)
module = importlib.import_module(module_name)
func = getattr(module, function_name)
func()
else:
print(f"Function not found for keyword: {keyword}")
call_function('keyword1')
改造一下上面动态引入的用法,不再去动态引包,直接继承函数封装类,动态调用父类的函数。
关于动态引包可能存在的性能问题说明:
动态引入开始的用意是支持配置文件函数动态调用,模块在实际需要时才导入,可以减少启动时间和内存占用,但每次动态导入都需要进行模块的查找和加载,可能会增加一些性能开销以及运行时延迟。在我们的项目中存在一个case反复调用某个事件,难道每次都去动态引包一次?如果这样操作的话,可能会产生不必要的开销,因而引入了变量缓存、继承父类解决这个问题。也考虑过静态函数,但是为了后续扩展性,现状暂时不做该处理。
class KeyFunc:
def printTest1(self, name):
print(name)
def printTest2(self, name):
print(name)
class Muban(KeyFunc):
def __init__(self):
self.cache={}
def runParentFunc(self):
## 模拟读取配置
key_method={
'step1':['printTest1','value1'],
'step2':['printTest1','value2'],
'step3':['printTest1', 'value3'],
'step4':['printTest1', 'value4'],
'step5':['printTest2', 'value5'],
'step6':['printTest2', 'value6']
}
for key,value in key_method.items():
method = self.cache.get(value[0])
if callable(method):
print('进入了缓存')
method(value[1])
else:
method = getattr(self, value[0])
self.cache[value[0]] = method
method(value[1])
Muban().runParentFunc()
三、本节结束语
本节就基础部分:事件能力、最小单元、测试用例进行了介绍,初期设计可能还比较粗糙,后面的章节中会不断地打磨、完善。相信任何有意义的事情都不是一蹴而就,需要循循渐进。
不同阶段的认知都不一样,在现在前面几个章节中,重点就带大家一起看看基础设计,在我的UI自动化实施的生涯中,期间也遇到了不少的坎坷、本专题重点就是介绍我在不断打磨后的结果。
后续的章节中会将CI/CD、如果推广UI自动化、如果识别UI自动化的投入回报率(怎么辨别UI自动化的实施是有意义)的方法向大家介绍。