Appium PO模式UI自动化测试框架——设计与实践

news2024/11/28 19:04:54

在这里插入图片描述
 
 

😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。
📡主页地址:【Austin_zhai】
🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。
💎声明:博主日常工作较为繁忙,文章会不定期更新,各类行业或职场问题欢迎大家私信,有空必回。

在这里插入图片描述

阅读目录

  • 1. 目的
  • 2. 意义
  • 3. 设计理念
  • 4. PO模式
  • 5. 框架设计
    • 5.1 目录结构
    • 5.2 实现步骤
    • 5.3 具体实现
      • 5.3.1 base部分
      • 5.3.2 po部分
      • 5.3.3 test_case部分
      • 5.3.4 run部分
  • 6. 注意点

 
 

1. 目的

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

 
 

2. 意义

  说到UI自动化,不同的公司、不同的团队往往看待它的态度也存在着很大的差异。项目或产品是否值得做UI自动化?执行的方向是否正确?落地的成本是否过大?大部分的测试团队都会有同样的疑问,不管初衷如何,(KPI? 晋升?内部推广?效率优化?)最大的难点一般都在于落地后如何保持一个稳定的使用周期与实际维护的成本是否小于团队投入期望值,说人话就是用来UI自动化之后是否大家都能用且可以长久的持续与维护下去。这里博主建议的是,在做UI自动化之前先想清楚动机是什么,如果真的只是自我拓展、KPI或者个人成果展示,那就掌握掌握原理与实操一下即可,没有必要在团队内进行推广;如果真的是解决团队的实际需求:历次回归都需大量的手工,每次右移后需要全功能回归,功能数量大、场景多、功能增量后耦合较低的情况,则可以简单的评估一下引入自动化UI测试预计带来的成果与提升效果。

 
 

3. 设计理念

  之所以选用PO模式,也正是因为一般的APP项目或产品功能都是增量式迭代开发的,那么必定会面临需要维护的功能页面越来越多的处境。如果是传统的设计模式,页面的元素与业务的操作会全部放在一个脚本内,有点类似于面向过程的编程理念。这样的模式必定会导致编写与维护的周期与成本增加,同样也不利于团队内成员共同维护的模式。
  相较于传统模式,PO(Page Object)模式则是将一个页面的所有元素对象定位和对元素对象的操作封装成类,测试用例的编写也依照单个页面来进行,目的就是实现页面对象和测试用例的分离。

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

  接下来了解了PO模式的优势之后,就需要对自动化框架进行设。先考虑清楚使用了自动化测试框架是要解决什么问题,这里的问题不能是模糊且没有边界的,之后将要自动化的产品、模块、流程进行分类与整理,这里一般来说推荐产品的核心主流程,一般覆盖happy path即可,但如果需要加入一些反向用例与使用场景也是可以的,但切忌不要一股脑的把团队的手工测试用例都加进去,到了后期你会体验到什么叫维护的时间比测试的时间更长。决定好以上这些了之后,就可以进行技术栈与框架的选择了,那这里我们选用的是appium+python+unittest的组合来进行PO模式测试框架的设计。当然,这里还是推荐大家根据自己的技术栈与公司环境现状来进行有效选择。

 
 

4. PO模式

  这里先声明一点,所有的框架都不是一蹴而就的,和我们熟知的软件一样,无论是结构还是代码都是的一版一版优化出来的,所以大家现在看到的框架不会是最初与最终的模样,无论是拿来优化、二开还是直接使用都是可以的。如果是自己写,哪怕一开始写的很简单也无所谓,要始终记住你落地自动化的目的是什么,只要能针对产品持续优化与反复总结,相信会有令人满意的结果的。

  这里我们先将一个页面类分成两层,一个是对象操作层、另一个是业务层。对象操作层指的是页面中的元素定位与单个元素操作;业务层顾名思义是把对应的元素操作组合起来形成一些列的业务操作。

  基于PO模式设计框架之前,我们还需要了解一下PO模式的6大原则,了解了原则之后才能更好的在实现过程中将PO模式的优势融入自己的框架之中。

6大原则

1.The public methods represent the services that the page offers
2.Try not to expose the internals of the page
3.Generally don’t make assertions
4.Methods return other PageObjects
5.Need not represent an entire page
6.Different results for the same action are modelled as different methods

1.用公共方法代表页面提供的功能
2.不要暴露页面元素到外部
3.一般不在方法内加断言
4.方法应该返回其他PO对象
5.不需要封装页面内所有元素
6.同样的行为不同的结果可以封装成不同的方法

个人解读

1.一些可复用的操作,可以用公共的方法进行统一封装,即使不在同一页面;
2.封装实现方法,对外只提供方法名或接口名;
3.封装的实现方法中不要使用断言,把断言可以统一放在测试用例中;
4.可以使用其他对象作为一个方法的返回结果,比如页面的跳转,就可以用方法的结果进行返回;
5.页面中只对重要的元素进行PO设计,不重要的、非主流程的可以舍弃(这里可以更好的迎合只覆盖happy path);
6.如果一个操作可能有多种结果的时候,将结果封装成不同的方法,比如保存成功与保存失败。

 
 

5. 框架设计

5.1 目录结构

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

 

5.2 实现步骤

  这里的PO模式设计其实没有那么的复杂,从目录就可以看出,首先将一些基础的元素定位、通用操作封装到对应的BasePage类中,(这里插一句,其实做APP自动化也好,做web自动化也好,很大程度上开发的代码规范性决定了你的框架实现过程是否顺畅。所以这里大家也可以在平时的工作中与开发事先沟通好一些元素的属性写法规范,别觉得不可能,行不行事在人为。)

  然后根据事先整理好的业务操作流程与页面跳转关系(3.设计理念中提到的前置工作输出)进行功能的封装,这里推荐根据6大原则对相关操作进行实现,顺了之后就是熟练工了,大同小异的。如果日后出现了布局变更或者业务变更,统一在对应的po页面中进行修改即可。另外,一些业务逻辑的判断,(比如是否存在该用户,不存在新建,存在直接进入),也可以放在po中,但是需要谨慎,这里比较推荐的还是放在测试用例内,也方便大家根据不同的情况做断言。

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

 

5.3 具体实现

5.3.1 base部分


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

class BasePage:

    def __init__(self, driver):
        self.driver = driver
        self.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_source
        if element in source:
            return True
        else:
            return False

    def 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 * bx
        ex = x * bw
        sy = y * by
        ey = y * bz
        return 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 如果不想每次重新登录,设为False
    return desired_caps

 

5.3.2 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()

 

5.3.3 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 e
        sleep(5)

    def tearDown(self):
        self.driver.quit()


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

 

5.3.4 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())

 
 

6. 注意点

1.PO模式虽然可以解决UI自动化测试中设计的部分问题,也仍然是目前比较主流的设计方案,后期面对大量的业务页面增加的情况,虽然可以使用通用页面来解决部分问题,但仍然避免不了界面与业务改动后大量调试代码的情况出现。所以这也是很多公司无法将大量成本聚焦在UI自动化测试的原因,将UI自动化应用于部分主要业务的做法还是值得提倡的,它也只是提高测试团队工作效率与投入产出比的一项手段而已,千万不可本末倒置;

2.测试用例的合理设计与执行安排,如果你的测试用例的相关命名、流程设计、存放路径过于凌乱与潦草的话,相信我,后期当框架具有一定的规模后,你会发现往往在维护测试用例时花费的精力要远远大于你的执行时间。与手工测试用例一样,无效用例始终都会出现在你的框架之中,这是无可避免的,但如何快速定位与规整这些用例就成了后期需要面对的日常问题之一,所以用例实现之初的命名规则、存放路径、实现时的备注就成了日后减少维护工作量的良好开端;

3.相较于接口自动化,UI自动化的性价比还是有一定的局限性,针对这样的情况,测试团队中如果要投入UI自动化的话可能就需要将团队中的成员定位做好一定的有效安排。框架设计与实现的问题不大,有专业的业务理解与一定的代码功底一般都可以很好的完成对应的测试框架,这里只针对维护层面的工作来说,是专职人员定岗安排还是团队成员穿插进行都需要根据各自的团队实际情况来分配,各有利弊,毕竟维护是一件费时费力的持久性工作。

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

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

相关文章

数学建模笔记 多元回归分析

本文内容来自数学建模清风老师的课件,是个人学习笔记,不保证完全正确,在此推荐学习清风老师视频讲解:清风建模算法、编程、写作培训https://www.bilibili.com/video/BV1DW411s7wi/?p3&wxfido7omF0atg6R7pnBLLqfBx0B-rjBU 多元…

安装Lombok--Lombok的常用注解说明及使用方法

😀前言 本篇博文是关于Lombok的基本介绍和基本使用,希望能够帮助到您😊 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满…

23款奔驰GLS450时尚型升级原厂香氛负离子系统,清香宜人,久闻不腻

奔驰原厂香氛合理性可通过车内空气调节组件营造芳香四溢的怡人氛围。通过更换手套箱内香氛喷雾发生器所用的香水瓶,可轻松选择其他香氛。香氛的浓度和持续时间可调。淡雅的香氛缓缓喷出,并且在关闭后能够立刻散去。车内气味不会永久改变,香氛…

将大容量机械硬盘克隆到固态硬盘的简单方法!

可以大容量机械硬盘克隆到固态硬盘吗? 随着硬盘使用时间增长,电脑的性能可能会下降。为了追求更快的读写速度,不少用户将目光投向了固态硬盘。 ​众所周知,固态硬盘的读写速度和启动速度比机械硬盘快。用固态硬盘替…

关于笔记本 win11系统出现的白屏闪烁问题

环境 windows 11 21H2 小新16pro笔记本起因 今天早上到公司,打开电脑后,发现电脑在登录后出现白屏闪烁的情况,闪烁是以下两张图反复更换: 看起来像是桌面程序出现问题,背景一会有一会没有的状况,通过Ctrl…

如何系列 如何使用Resilience4j提高应用弹性和容错

文章目录 背景简介Maven重试器注解式编程式配置事件监听指标监控健康检查 速率限制注解式编程式配置事件监听指标监控动态修改配置 断路器注解式配置 舱壁注解式 时间限制器多组件配合使用最佳实践 配置参考: 背景 在应用程序开发的过程中,特别是在构建…

力扣初级算法(旋转矩阵)

力扣初级算法(旋转矩阵) 每日一算法:旋转矩阵 学习内容: 1.问题: 给你一幅由 N N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。 不占用额外内存空间能否做到? 2.…

React源码解析18(3)------ beginWork的工作流程【mount】

摘要 OK,经过上一篇文章。我们调用了: const root document.querySelector(#root); ReactDOM.createRoot(root)生成了FilberRootNode和HostRootFilber。 并且二者之间的对应关系也已经确定。 而下一步我们就需要调用render方法来讲react元素挂载在ro…

途乐证券-保险消费者信心延续恢复态势 健康险、意外险增购意愿上

今年以来,稳妥顾客决心延续调整康复态势。我国稳妥保证基金有限责任公司(以下简称“稳妥保证基金公司”)近来发布数据显现,2023年二季度,我国稳妥顾客决心指数为67.7,环比下降3.6,同比上升1.2&a…

一文读懂c++语言

一文读懂C语言 C的发展C的设计目标C的特性C的挑战 C的发展 C是一种通用的、高级的编程语言,它是C语言的扩展。C由Bjarne Stroustrup于1983年首次引入,并在之后的几十年中不断发展壮大。C被广泛应用于各种领域,包括系统开发、游戏开发、嵌入式…

keil显示中文代码正常,但是编译中文乱码的问题

Configuration---Editor---Encoding,ANSI或是UTF8,总之这2个,是A就改为U,是U就改为A。 MDK5中文编译乱码,不是显示乱码哦。

Android Camera预览画面变形问题

csdn 问题 安卓camera1在预览时,预览画面看起来被拉伸了. 如图,圆形的盖子,变成椭圆形了. 代码 默认流程,如下为大致的打开摄像头并进行预览显示的代码 private Camera mCamera null; private Surfa…

YOLOv5入门

模型检测 关键参数 weights:训练好的模型文件 source: 检测的目标,可以是单张图片、文件夹、屏幕或者摄像头等 conf-thres: 置信度闯值,越低框越多,越高框越少 iou-thres: IOU闻值,越低框越少,越少框越多 torch.hu…

windows安装apache-jmeter-5.6.2教程

目录 一、下载安装包(推荐第二种) 二、安装jmeter 三、启动jmeter 一、下载安装包(推荐第二种) 1.官网下载:Apache JMeter - Download Apache JMeter 2.百度云下载:链接:https://pan.baidu.…

vivo 场景下的 H5无障碍适配实践

作者:vivo 互联网前端团队- Zhang Li、Dai Wenkuan 随着信息无障碍的建设越来越受重视,开发人员在无障碍适配中也遇到了越来越多的挑战。本文是笔者在vivo开发H5项目做无障碍适配的实践总结。本文主要介绍了在前端项目中常用的无障碍手势和无障碍属性&am…

kubeadm安装

master(2C/4G,cpu核心数要求大于2) 192.168.223.71 node01(2C/2G) 192.168.223.72 node02(2C/2G) 192.168.223.73…

validator入门

validator中文文档地址和英文地址 https://docs.jboss.org/hibernate/validator/4.2/reference/zh-CN/html/validator-gettingstarted.html https://docs.jboss.org/hibernate/validator/6.0/reference/en-US/html_single/#preface自定义hibernate-validator校验 工具类Valid…

基于SOM神经网络的柴油机故障诊断

1.案例背景 1.1 SOM神经网络概述 自组织特征映射网络(Self - Organizing Feature Map,SOM)也称Kohonen网络,它是由荷兰学者Teuvo Kohonen于1981年提出的。该网络是一个由全连接的神经元阵列组成的无教师、自组织、自学习网络。Kohonen认为,处于空间中不同区域的神经元有着不同…

别克VELITE 5增程式混动电动汽车变速箱解析

别克VELITE 5增程式电动汽车智能无极变速箱采用双电机双行星轮组的设计,相比其他技术具有更高的自由度,两个点击可同时参与驱动或单独发电,在内部集成的TPIM 模块的控制下,发动机与点击可同时100%参与驱动,变速范围更加…

嘉楠勘智k230开发板上手记录(二)--hello world

上次成功在k230上烧录sdk,这次准备实现hello world和ssh scp远程k230 主要是按照K230 SDK 基础教程的K230_实战基础篇_hello_world.md 一、PC连接k230 1. 初步准备 首先下载串口工具PuTTY,这个我个人感觉比较方便。 准备两根USB type-C数据线&#…