Appium UI 自动化到底要不要用 Page Object 模式,以及 yaml 数据驱动?

小怪兽 · 2019年07月11日 · 最后由 mind 回复于 2020年07月10日 · 3773 次阅读

业务背景

我这是一家手游公司, 前端使用 unity,appium 之类框架的都无法识别 unity 控件,最后得知网易 airtest 下面的 poco 框架可识别 unity 控件。
由于之前没有相关经验靠自己摸爬滚打,走了很多弯路,代码结构/框架也重构了几次(现在还想重构😂 )。
在设计之初有过很多构想,觉得应该满足那些要求:

  • 颗粒度尽可能小且 case 互不影响
  • 可根据不同策略执行不同深度 case 集
  • 负载均衡:收集可用测试机根据对应测试机执行快慢分发不同数量 case 任务
  • 重复执行
  • 失败重试(因业务特殊目前不准备失败重试,因为 case 前置数据准备是通过跑 sql 修改数据,但前端不会及时刷新,需要找到一个刷新点,主流的刷新点是重登, 从当前 case 界面-》跳转游戏主界面-》设置切换账号-》登录界面登录账号-》跳转游戏主界面-》跳转 case 指定界面,这个过程非常耗时,会大幅增加 case 执行时间)
  • 让 case 编写者只需要关注业务
  • 以最小的改动面对未来需求的变化
  • ............... 还实现了很多哈 不一一列举

问题介绍

UI 自动化到底要不要用 Page Object 模式,以及 yaml 数据驱动? 或者说我这个情况要不要使用 po 模式?

了解到 Page Object 模式很主流,很火。然后使用 yaml 数据驱动,很炫酷,高大上的样子(想立马就应用到项目中)
但任何技术最终还要是服务于业务,是要能解决某些或某类问题
这里以我对 po 模式非常浅显的理解和我当前的做法做了个对比:

po 模式 当前搞法
代码量 多 两倍以上
复杂度 较复杂 简单明了
ui 变化 修改简单 修改简单

单看表格可能看不懂哈,直接贴 python 代码如下:

省去了 case 前置界面准备,前置数据准备

代码内容大概是把一个战术技能从 0 级升级到 10, 并做相关断言

data_0 = [
    ['0', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+0</color>', '1', '15001', 9],
    ['1', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+1</color>', '2', '15002', 9],
    ['2', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+2</color>', '4', '15003', 9],
    ['3', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+4</color>', '6', '15004', 9],
    ['4', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+6</color>', '9', '15005', 9],
    ['5', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+9</color>', '12', '15006', 9],
    ['6', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+12</color>', '16', '15007', 9],
    ['7', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+16</color>', '20', '15008', 9],
    ['8', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+20</color>', '25', '15009', 9],
    ['9', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+25</color>', '30', '15010', 9],
    ['10', '每有一个进攻战术达到<color=#fca926>10级</color>,\n球队进攻<color=#fca926>+30</color>', '0', '0', 0],
]
@allure.story('战术素养')
@allure.title('升级常规进攻素养0-10级')
@user3ize('level, des, number, style, t_book', data_0)
def test_0(self, mt, level, des, number, style, t_book):
    poco = mt.poco
    poco("Content").child("TacticsStyleItem(Clone)")[0].click()                        #选中常规进攻素养
    assert poco("Content").child("TacticsStyleItem(Clone)")[0].child('GiftType').get_text().split('.')[1] == level
                                                                                       #检查当前进攻素养的等级是否正确
    assert poco("DetailPanel").child("CurrentEffect").child('Desc').get_text() == des  #断言当前进攻素养的文案内容是否正确
    if level != '10':
        poco("UpgradePanel").child("UpgradeBtn").click()                                #点击升级按钮
        poco("Content").child("TacticsStyleItem(Clone)")[0].click()                     #点击跳过动画
        assert mt.sql.select(SQL_1_0+style)[1][0] == t_book, SQL_1_0+style         #查看数据库对应类型道具应该减少一个
        assert poco("CurrentEffect").child('Desc').get_text().split('+')[1].split('<')[0] == number #升级后加成的数值是否正确
    else:
        assert poco("UpgradePanel").child("UpgradeBtn").child("Label").get_text() == "已满级"     #满级时应不能升级

疑惑

这样看来,po 模式会更加繁琐,笨重,好像 po 模式没有什么优势。 请有这方面经验的朋友解惑一下

共收到 21 条回复 时间 点赞

PO 只是解耦和提高控件复用性吧,至于复杂度这个和业务、你的测试用例都有关系。

楼主你这个代码就像单身汉的屋子一样,哪怕乱如麻也能很快找到要找的东西,因为你已经非常熟悉了。但是在多人协作的时候,你这个代码就不忍直视了,一坨一坨的堆积在一起无从落脚。
再说 PO,其实 PO 设计模式是一种指引,而不是枷锁。在指引的帮助下可以很好的帮助我们梳理项目结构,优化层级,让协作更流畅,新人上手更容易,维护起来更方便。但是楼主提到的业务情况也会存在特殊性,比如我们之前也纠结过 “断言要不要放到 test 方法里面”,“业务方法到底拆到什么颗粒度”,元素剥离、数据分离、方法分层到底收益如何度量,这些经历过阵痛的人会理解的更加深刻一些。

simple 回复

多谢 ,回答的详细,但感觉还是没 get 到点哈。可能还需要更多坑的洗礼才会明白😂

"UI 自动化到底要不要用 Page Object 模式,以及 yaml 数据驱动 " 建议你不要在意这些,这些也就是之前写自动化人的自己的规范而已,不可一叶遮目,就像 SQL 范式官方的范式,实际的开发有多少人遵循了;

你想怎么写 就怎么写, 怎样爽怎么来,

说真的,你这个可维护性和工作量真的很大,到后面说不定都很难搞下去,我建议你们还是将页面元素抽离分装一层,编写用例者只关心业务,环境这一块也有用例本身去控制,这样效果会比较好,用例格式统一,封装格式统一,复用性强,即使不会写代码的人也会很容易上手,一旦某个控件元素有调整了只需要修改封装的几个接口就行了,除非大面积改动才改用例,底层处理好后,一个项目不到 2 个人很快就可以搞起来的,我这边是这么实施的,也是游戏哈

我觉得 po 模式比较注重可复用性吧,多人写脚本的时候,重复的部分都可以提取做公共模块,新人接手的时候上手也比较快

恩 以务实为主, 但想把框架设计的更好,就还是需要借助外界 行业的力量 碰到好的思路 方法 就想看能不能把它融入到项目中

小怪兽 · #8 · 2019年07月12日 Author
仅楼主可见

为啥不用 airtest 呢

我去催饭 回复

感觉图片识别 准确率 和效率 都是瓶颈 另外 case 多了后 海量图片的储存都很担忧 , 所有没有使用 airtest 下面的图片识别 只用了 poco

小怪兽 回复

游戏的话可能变化比较快,不太适合用图像识别做测试,我们的业务还挺稳定的,基本上两周维护一下就行了,图片不会变

没有最好的设计,大部分情况只需要够用的设计。如果你觉得目前的设计没遇到什么大问题,沿用就好。

po 偏设计模式,主要应用在大量用例、协作写脚本比较多的场景,用尽量少的额外成本,解决这个场景下不好协作维护的问题。
从楼主的说明看,用例不多、协作也基本没有,不必强求。

用例多了 Page Object 更好(实际能写很多的情况很少)
多人开发 Page Object 更好(实际很多人写的情况很少)
抽象成 Page Object 复用性可读性更好(实际抽象的很差复用性可读性很差)
UI 改动频繁 Page Object 修改起来更快(实际和全局搜索替换差不多)
……
即使需要更高的抽象了, Page Object 或者数据驱动也只是方向之一。
这和开发论坛上常见的轻量重量框架之争差不多,能清楚的了解场景,然后根据场景做合适的选择才是困难的地方。

好几年前开始学 UI 自动化的时候,到处都在教 Page Object ,就跟着这样写咯。后来听 SAP 的人分享端到端自动化,说他们有两万多的用例……

我个人倾向 bot-style,对 PO 的那些优势实在毫无感觉,因为我还没遇到需要协作的情况,就算我的子模块有 8K 的 case 那也是我自己写,无法指望别人来帮忙的……我觉得需要协作本质上是模块之间的耦合在自动化测试的数据依赖上没处理好而已~所以面向业务的线性脚本才是我的心头爱~

  • 我觉得 po 最主要的作用不是解耦,因为测试流程里面包含了很多页面状态,其他人想复用是不太可能的,对单独的某一个人来说可能能提高代码复用率,我的理解最重要的作用是便于代码维护,因为可能经常需要修改
  • 数据驱动的方式有很多种,看哪种方式合适就用哪种
陈恒捷 回复

用例数量:

哈哈, 用例数量其实还挺多的,游戏业务较复杂,大多时间花费在前置数据准备,目前一共有三四十个模块, 一个模块大概有八九十条左右(模块大小数量也不一定,在 case 稳定性允许的前提下尽可能覆盖更广更深,所以各种异常情况也会覆盖)。已写完十几个模块,五台手机一起跑大概一小时左右跑完,预计所有 case 写完后 8 台机器一跑 两三小时的样子

设计问题

当前设计暂时还没发现什么大问题,相反现在的 case 如果有异常可以直接 copy 代码到 airtest IDE 里面快速调试很方便。

协作

按照我的计划,可能两人一组负责前期 case 的堆积(迭代了好几年的项目),后期留下一人维护

就像 webmvc 模式一样,代码量大了就会知道解耦的好处

槽神 回复

bot-style 是啥?查了好像很少资料。PO 设计模式倒是用过。

小怪兽 回复

那你现在的设计应该可以满足了。不用太纠结 PO 。

小怪兽 [该话题已被删除] 中提及了此贴 07月16日 20:43
simple 回复

希望分享一篇,目前正在阵痛中

大佬!负载均衡:收集可用测试机根据对应测试机执行快慢分发不同数量 case 任务,这个咱实现的?对用例数上千的自动化执行是福音啊?能讲讲思路吗?期待大佬的回复!

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册