不会做UI自动化测试?一起设计框架再实践吧

目的

相信做过测试的同学都听说过自动化测试,而UI自动化无论何时对测试来说都是比较吸引人的存在。

相较于接口自动化来说它可以最大程度的模拟真实用户的日常操作与特定业务场景的模拟,那么存在即合理,自动化UI测试自然也是广大测试同学职业道路上必不可少的必修课题之一了。

意义

说到UI自动化,不同的公司、不同的团队往往看待它的态度也存在着很大的差异:

项目或产品是否值得做UI自动化?
执行的方向是否正确?
落地的成本是否过大?
大部分的测试团队都会有同样的疑问,不管初衷如何(KPI、晋升、内部推广、效率优化),最大的难点一般都在于落地后如何保持一个稳定的使用周期与实际维护的成本是否小于团队投入期望值,说人话就是用来UI自动化之后是否大家都能用且可以长久的持续与维护下去。

这里博主建议的是,在做UI自动化之前先想清楚动机是什么:

如果真的只是自我拓展、KPI或者个人成果展示,那就掌握掌握原理与实操一下即可,没有必要在团队内进行推广;
如果真的是解决团队的实际需求:历次回归都需大量的手工,每次右移后需要全功能回归,功能数量大、场景多、功能增量后耦合较低的情况,则可以简单的评估一下引入自动化UI测试预计带来的成果与提升效果。

设计理念

之所以选用PO模式,也正是因为一般的APP项目或产品功能都是增量式迭代开发的,那么必定会面临需要维护的功能页面越来越多的处境。

如果是传统的设计模式,页面的元素与业务的操作会全部放在一个脚本内,有点类似于面向过程的编程理念。这样的模式必定会导致编写与维护的周期与成本增加,同样也不利于团队内成员共同维护的模式。

相较于传统模式,PO(Page Object)模式则是将一个页面的所有元素对象定位和对元素对象的操作封装成类,测试用例的编写也依照单个页面来进行,目的就是实现页面对象和测试用例的分离。

这样做的好处有3点:

低耦合:将每个页面单独进行封装,类似与面向对象,互相之间低耦合,即时需要业务流程连续执行也不会互相影响;
易维护:当界面发生变化时,只需修改对应的页面类中的元素即可,其他相关的不会受到影响也无需修改;
易上手:基于PO模式的设计理念,页面类的实现与细节不会暴露在外,都通过公共方法进行提供,使用者无需对代码的实现逻辑进行学习,只需要对业务与编程语言有足够的了解后直接编写与使用。
接下来了解了PO模式的优势之后,就需要对自动化框架进行设。

先考虑清楚使用了自动化测试框架是要解决什么问题,这里的问题不能是模糊且没有边界的,之后将要自动化的产品、模块、流程进行分类与整理,这里一般来说推荐产品的核心主流程,一般覆盖happy path即可,但如果需要加入一些反向用例与使用场景也是可以的,但切忌不要一股脑的把团队的手工测试用例都加进去,到了后期你会体验到什么叫维护的时间比测试的时间更长。

决定好以上这些了之后,就可以进行技术栈与框架的选择了,那这里我们选用的是appium+python+unittest的组合来进行PO模式测试框架的设计。当然,这里还是推荐大家根据自己的技术栈与公司环境现状来进行有效选择。

PO模式

这里先声明一点,所有的框架都不是一蹴而就的,和我们熟知的软件一样,无论是结构还是代码都是的一版一版优化出来的,所以大家现在看到的框架不会是最初与最终的模样,无论是拿来优化、二开还是直接使用都是可以的。

如果是自己写,哪怕一开始写得很简单也无所谓,要始终记住你落地自动化的目的是什么,只要能针对产品持续优化与反复总结,相信会有令人满意的结果的。

这里我们先将一个页面类分成两层,一个是对象操作层、另一个是业务层。

对象操作层指的是页面中的元素定位与单个元素操作;
业务层顾名思义是把对应的元素操作组合起来形成一些列的业务操作。
基于PO模式设计框架之前,我们还需要了解一下PO模式的6大原则,了解了原则之后才能更好地在实现过程中将PO模式的优势融入自己的框架之中。

6大原则

1.用公共方法代表页面提供的功能;

2.不要暴露页面元素到外部;

3.一般不在方法内加断言;

4.方法应该返回其他PO对象;

5.不需要封装页面内所有元素;

6.同样的行为不同的结果可以封装成不同的方法。

个人解读

1.一些可复用的操作,可以用公共的方法进行统一封装,即使不在同一页面;

2.封装实现方法,对外只提供方法名或接口名;

3.封装的实现方法中不要使用断言,把断言可以统一放在测试用例中;

4.可以使用其他对象作为一个方法的返回结果,比如页面的跳转,就可以用方法的结果进行返回;

5.页面中只对重要的元素进行PO设计,不重要的、非主流程的可以舍弃(这里可以更好的迎合只覆盖happy path);

6.如果一个操作可能有多种结果的时候,将结果封装成不同的方法,比如保存成功与保存失败。

框架设计

目录结构

图片

这里简单说明下目录的结构:

base:存放一些框架与页面的公共方法;
po:存放所有的页面,这里就是被测对象相关的被测页面,不需要放全部页面;
result:存放相关的自动化测试结果报告;
test_case:存放测试用例。
根目录下还有一个run文件,这个是运行主入口,可以设置运行哪些测试用例集与使用什么样的测试报告套件。

实现步骤

这里的PO模式设计其实没有那么的复杂,从目录就可以看出,首先将一些基础的元素定位、通用操作封装到对应的BasePage类中。

这里插一句,其实做APP自动化也好,做web自动化也好,很大程度上开发的代码规范性决定了你的框架实现过程是否顺畅。所以这里大家也可以在平时的工作中与开发事先沟通好一些元素的属性写法规范,别觉得不可能,行不行事在人为。

然后根据事先整理好的业务操作流程与页面跳转关系(设计理念中提到的前置工作输出)进行功能的封装,这里推荐根据6大原则对相关操作进行实现,顺了之后就是熟练工了,大同小异的。如果日后出现了布局变更或者业务变更,统一在对应的po页面中进行修改即可。

另外,一些业务逻辑的判断,(比如是否存在该用户,不存在新建,存在直接进入),也可以放在po中,但是需要谨慎,这里比较推荐的还是放在测试用例内,也方便大家根据不同的情况做断言。

最后在页面元素、业务操作齐全的状态下进行测试用例的实现,一般来说可以先使用冒烟测试的测试用例来进行简单的业务验证,当然直接使用系统测试的测试用例也是完全没问题的,之后只需要根据之前整理好的用例选单进行转化即可。至于用例的存放目录结构可以根据po页面维度来存放,也可以根据业务维度来进行存放,见仁见智。

具体实现

base部分

这边先定义一个BasePage类,用来实现一些公共方法与元素定位的实现(webdriver):

class BasePage:def __init__(self, driver):self.driver = driverself.driver.implicitly_wait(10)def by_id(self, id):return self.driver.find_element(By.ID, id)def by_xpath(self, xpath):return self.driver.find_element(By.XPATH, xpath)def by_class_name(self, class_name):return self.driver.find_element(By.CLASS_NAME, class_name)def by_uiautomator(self, uiautomator):return self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, uiautomator)

另外后续的一些触屏的操作、元素判断也可以按需放在这里面:

def is_element(self, element):source = self.driver.page_sourceif element in source:return Trueelse:return Falsedef drag(self, bx=0.50, bw=0.05, by=0.4, bz=0.9):x = self.driver.get_window_size()['width']y = self.driver.get_window_size()['height']sx = x * bxex = x * bwsy = y * byey = y * bzreturn self.driver.swipe(sx, sy, ex, ey, 1000)

这里我定义了另一个driver_setup的方法,方便每次设备启动使用:

def driver_setup():desired_caps = dict()desired_caps['platformName'] = 'Android'desired_caps['platformVersion'] = '10'desired_caps['deviceName'] = '你自己的设备名'desired_caps['appPackage'] = '包名'desired_caps['appActivity'] = '启动名'desired_caps['noReset'] = True # 不重置session信息desired_caps['fullReset'] = False # 效果类似与卸载APP 如果不想每次重新登录,设为Falsereturn desired_caps

po部分

在这里插入图片描述

目录大致如上,这里值得注意的是,不要把APP里所有的页面都加入到自动化测试中,100%的自动化测试覆盖率会让你苦不堪言,也大可不必。将每次必须回归的重要流程与高重复业务流程、场景加入即可。

以下就是po中的创建顾客页面的实现方法了,直接继承BasePage类,这里有几个例子需要关注的是,性别选择可以封装成两个方法,尽量不用同一个;另一个如果是点击类事件(单结果事件),直接click就行,不用单独在封装完元素后再进行业务操作封装,备注这样的多结果事件则要在下面单独进行业务指定。

class CustomerCreatePage(BasePage):"""定义封装创建客户页面的各类操作创建客创建客户并开卡"""# 定义会员编号输入框def customer_number(self):return self.by_id('com.tiffany.rta.debug:id/edt_customer_number')# 定义姓名输入框def customer_name(self):return self.by_id('com.tiffany.rta.debug:id/edt_customer_name')# 定义手机输入框def customer_mobile(self):return self.by_id('com.tiffany.rta.debug:id/edt_customer_mobile')# 定义性别选择def customer_sex(self):return self.by_id('com.tiffany.rta.debug:id/tv_customer_sex')# 定义性别内选择项目-男def customer_sex_item_male(self):return self.by_id('com.tiffany.rta.debug:id/tv_customer_boy').click()# 定义性别内选择项目-女def customer_sex_item_female(self):return self.by_id('com.tiffany.rta.debug:id/tv_customer_girl').click()# 定义生日选择框def customer_birthday(self):return self.by_id('com.tiffany.rta.debug:id/tv_customer_birthday')# 定义备注输入框def customer_memo(self):return self.by_id('com.tiffany.rta.debug:id/ed_remark')# 定义保存并开卡按钮def save_and_register_card_button(self):return self.by_id('com.tiffany.rta.debug:id/mb_save_open_card').click()

接下来就是组合多个元素进行业务操作的定义:

# 定义新建顾客操作
def do_create_customer(self):self.customer_number().send_keys('00001')self.customer_name().send_keys('自动化测试01')self.customer_mobile().send_keys('13200000000')self.customer_sex()self.customer_sex_item_male()self.save_button()

test_case部分

测试用例类继承unittest下的TestCase,初始化的时候将对应的用例业务流程加入到里面,另外在具体的测试用例中需要加对应的判断逻辑与操作步骤完整的添加在里面。

使用try捕获异常的时候记得把对应的报错名也写上,一是方便定位问题,二是有可能会导致即使用例失败,测试报告上的结果也是pass。

class TestCustomerListPage(unittest.TestCase):"""定义客户列表界面的测试用例创建客户"""# 初始化必要的设备信息与业务页面def setUp(self):self.driver = webdriver.Remote('http://localhost:4723/wd/hub', driver_setup())self.base_page = BasePage(driver=self.driver)self.home_page = HomePage(driver=self.driver)self.customer_list = CustomerListPage(driver=self.driver)self.customer_detail = CustomerDetailPage(driver=self.driver)self.customer_create = CustomerCreatePage(driver=self.driver)# 测试用例1 -- 创建顾客def test_1_create_customer(self):self.home_page.go_customer()customer_name = '自动化测试01'# 业务逻辑判断 -- 是否存在该新客if self.base_page.is_element(customer_name):self.customer_list.select_customer()self.customer_detail.do_delete_customer()self.home_page.go_index()self.home_page.go_customer()if self.base_page.is_element(customer_name):self.customer_check.check_pass()else:self.customer_list.goto_create_customer()self.customer_create.do_create_customer()self.customer_detail.back_button()else:self.customer_list.goto_create_customer()self.customer_create.do_create_customer()self.customer_detail.back_button()try:self.assertTrue(self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,'new UiSelector().text("自动化测试01")'))except NoSuchElementException as e:return esleep(5)def tearDown(self):self.driver.quit()if __name__ == "__main__":unittest.main()

run部分

具体的测试用例报告模板,大家可以自由选择,这边使用的是HTMLTestReportCN,启动的方式都是大同小异的,无非就是根据自己的测试场景进行定制就行。另外测试模板的组合和样式有兴趣的同学可以自己对报告脚本进行修改,打造更适合自己团队需求的测试报告。

# 两套测试报告模板路径,只用一个的可以就定义一个
report_path = os.path.join(os.getcwd() + '\\result')
result_path = os.path.join(report_path, 'report.html')
# 测试套件路径,根据需求修改
test_dir = os.path.join(os.getcwd() + '\\test_case\\trade')# 执行指定测试用例
def test_suit():suit = unittest.TestSuite()suit.addTest(TestOrderResultPage('test_1_order_result'))suit.addTest(TestOrderResultPage('test_2_order_result_home_page'))return suit# 执行测试用例集
dis = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py")if __name__ == "__main__":with open(result_path, 'wb') as fp:runner = HTMLTestReportCN.HTMLTestRunner(stream=fp, title='自动化APP测试报告',description='基于自动化APP测试框架产生的测试报告')runner.run(test_suit())

注意点

1、PO模式虽然可以解决UI自动化测试中设计的部分问题,也仍然是目前比较主流的设计方案,后期面对大量的业务页面增加的情况,虽然可以使用通用页面来解决部分问题,但仍然避免不了界面与业务改动后大量调试代码的情况出现。

所以这也是很多公司无法将大量成本聚焦在UI自动化测试的原因,将UI自动化应用于部分主要业务的做法还是值得提倡的,它也只是提高测试团队工作效率与投入产出比的一项手段而已,千万不可本末倒置。

2、测试用例的合理设计与执行安排,如果你的测试用例的相关命名、流程设计、存放路径过于凌乱与潦草的话,相信我,后期当框架具有一定的规模后,你会发现往往在维护测试用例时花费的精力要远远大于你的执行时间。

与手工测试用例一样,无效用例始终都会出现在你的框架之中,这是无可避免的,但如何快速定位与规整这些用例就成了后期需要面对的日常问题之一,所以用例实现之初的命名规则、存放路径、实现时的备注就成了日后减少维护工作量的良好开端。

3、相较于接口自动化,UI自动化的性价比还是有一定的局限性,针对这样的情况,测试团队中如果要投入UI自动化的话可能就需要将团队中的成员定位做好一定的有效安排。

框架设计与实现的问题不大,有专业的业务理解与一定的代码功底一般都可以很好的完成对应的测试框架,这里只针对维护层面的工作来说,是专职人员定岗安排还是团队成员穿插进行都需要根据各自的团队实际情况来分配,各有利弊,毕竟维护是一件费时费力的持久性工作。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/169874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker Swarm总结+基础、集群搭建维护、安全以及集群容灾(1/3)

博主介绍:Java领域优质创作者,博客之星城市赛道TOP20、专注于前端流行技术框架、Java后端技术领域、项目实战运维以及GIS地理信息领域。 🍅文末获取源码下载地址🍅 👇🏻 精彩专栏推荐订阅👇🏻…

css中flex两列布局(一列自适应其他固定)

问题 最近写一个布局的时候,遇到一个问题。如下图的布局。在没有图片的时候布局是正常的,如果有图片且设置了width:100%;height: 100%; 则会出现图片将自适应布局撑开的情况。 我的解决方式是让图片不缩放,图片外层再添加一个div元素。形如…

MySQL介绍及安装

MySQL介绍及安装 一、MySQL概述 1、关系型数据库与非关系型数据库 RDBMS(relational database management system),既关系型数据库管理系统。 简单来说,关系型数据库,是指采用了二维表格来组织数据的数据库。 扩展…

字符串转换成十进制整数

编程要求 输入一个以#结束的字符串,本题要求滤去所有的非十六进制字符(不分大小写),组成一个新的表示十六进制数字的字符串,然后将其转换为十进制数后输出。如果在第一个十六进制字符之前存在字符“-”,则…

【Qt绘制仪表盘】

目的 使用Qt的绘制事件绘制一个仪表盘 思路 需要创建一个带绘制事件的控件重写绘制事件显示 实现 以下是实现代码&#xff0c;可复制到程序到&#xff0c;直接运行。 .h // GaugeWidget.h #ifndef GAUGEWIDGET_H #define GAUGEWIDGET_H#include <QWidget>class Ga…

Ubuntu开机显示recovering journal,进入emergency mode

在一次正常的shutdown -r now之后&#xff0c;服务器启动不起来了&#xff0c;登录界面显示recovering journal&#xff0c;主要报错信息如下所示&#xff1a; /dev/sda2:recovering journal /dev/sda2:Clearn... You are in emergency mode. After logging in, type journalc…

Kotlin学习——流程控制,when,循环,range工具 kt里的equals if实现类似三元表达式的效果

Kotlin 是一门现代但已成熟的编程语言&#xff0c;旨在让开发人员更幸福快乐。 它简洁、安全、可与 Java 及其他语言互操作&#xff0c;并提供了多种方式在多个平台间复用代码&#xff0c;以实现高效编程。 https://play.kotlinlang.org/byExample/01_introduction/02_Functio…

Elasticsearch集群部署 head监控插件 Kibana部署 Nginx反向代理 Logstash部署

一、组件介绍1、Elasticsearch&#xff1a;2 、Logstash3、Kibana4、Kafka&#xff1a;5、Filebeat: 二、 Elasticsearch集群部署服务器创建用户安装ES修改配置文件创建数据和日志目录设置JVM堆大小 #7.0默认为4G修改安装目录及存储目录权限系统优化&#xff08;1&#xff09;增…

一定要会用selenium的等待,三种等待方式解读

​很多人问&#xff0c;这个下拉框定位不到、那个弹出框定位不到…各种定位不到&#xff0c;其实大多数情况下就是两种问题&#xff1a; 有frame 没有加等待 殊不知&#xff0c;你的代码运行速度是什么量级的&#xff0c;而浏览器加载渲染速度又是什么量级的&#xff0c;就好…

vs2015如何远程启动程序来进行调试

vs远程调试的方式有两种&#xff0c;远程启动方式和附加进程方式。   一般来说&#xff0c;咱们使用vs调试代码时&#xff0c;直接附加进程即可&#xff0c;但某些时候附加进程方式无法命中断点。比如我们想调试的C代码&#xff0c;但是调试的入口程序是C#程序&#xff0c;如…

经典的回溯算法题leetcode棋盘问题思路代码详解

目录 棋盘问题 leetcode51题.N皇后 对回溯算法感兴趣的朋友也可以多多支持一下我的其他文章。 回溯算法详解-CSDN博客 经典的回溯算法题leetcode组合问题整理及思路代码详解-CSDN博客 经典的回溯算法题leetcode子集问题思路代码详解-CSDN博客 经典的回溯算法题leetcode全…

人工智能轨道交通行业周刊-第66期(2023.11.20-11.26)

本期关键词&#xff1a;智能铁鞋、TFDS、道岔密贴检查、Agent、Q*假说 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道世界铁路那些事铁路…

多丽特膳:肥胖与七大营养素的关系、影响及作用

在探讨肥胖与身体的关系时&#xff0c;我们不能忽视七大营养素的重要作用。这些营养素包括碳水化合物、蛋白质、脂肪、维生素、矿物质、膳食纤维和水。它们对肥胖和身体的影响及作用各有不同&#xff0c;一起构成了我们日常饮食的基础&#xff0c;那么让我来给大家说道说道它们…

【2023传智杯】第六届传智杯程序设计挑战赛AB组-ABC题解题分析详解【JavaPythonC++解题笔记】

本文仅为第六届传智杯程序设计挑战赛-题目解题分析详解的解题个人笔记,个人解题分析记录。 本文包含:第六届传智杯程序设计挑战赛题目、解题思路分析、解题代码、解题代码详解 文章目录 一.前言二.比赛题目(AB俩组)A题题目B题题目C题题目三.解题代码A题解题思路解题代码【J…

每日一题 1457. 二叉树中的伪回文路径(中等,DFS)

一句话&#xff0c;深度搜索所有路径&#xff0c;判断路径是否伪回文 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right clas…

ddns-go部署在linux虚拟机

ddns-go部署ubuntu1804 1.二进制部署 1.虚拟机部署 1.下载linux的x86二进制包 wget https://github.com/jeessy2/ddns-go/releases/download/v5.6.3/ddns-go_5.6.3_linux_x86_64.tar.gz2.解压 tar -xzf ddns-go_5.6.3_linux_x86_64.tar.gz3.拷贝执行文件到PATH下&#xff0c…

通过视频文件地址截取图像生成图片保存为封面图

安装 RPM Fusion 软件库 FFmpeg并不包含在 CentOS 官方软件库中,需要使用第三方软件库安装。可以使用 RPM Fusion 软件库来获取 FFmpeg。 首先,使用以下命令安装 RPM Fusion 软件库: sudo yum install epel-release -y sudo rpm -Uvh https://download1.rpmfusion.org/fre…

《斯坦福数据挖掘教程·第三版》读书笔记(英文版)Chapter 3 Finding Similar Items

来源&#xff1a;《斯坦福数据挖掘教程第三版》对应的公开英文书和PPT It is therefore a pleasant surprise to learn of a family of techniques called locality-sensitive hashing, or LSH, that allows us to focus on pairs that are likely to be similar, without hav…

基于C#实现双端队列

话说有很多数据结构都在玩组合拳&#xff0c;比如说&#xff1a;块状链表&#xff0c;块状数组&#xff0c;当然还有本篇的双端队列&#xff0c;是的&#xff0c;它就是栈和队列的组合体。 一、概念 我们知道普通队列是限制级的一端进&#xff0c;另一端出的 FIFO 形式&#…

信息检索策略和技巧

指定检索策略并检索 确定检索词 检索课题&#xff1a;查找与“新型冠状病毒疫苗研制进展”有关的学术论文 检索式(2019-nCoV or 2019新型冠状病毒 or nCov-2019 or SARS-CoV-2 or COVID-19) and (疫苗 or 预防针 or 防疫针 or vaccin or vaccine) 扩展检索词的方式 同义词…