一 、引子

UI自动化,在移动互联网时代的今天,一直都是在各大测试社区最为火爆的一个TOPIC。甚至在测试同行面前一提起自动化,大家就会自然而然的问:“恩,你们是用的什么框架?appium?还是robotium?”

其实在笔者看来,UI自动化是一个ROI较低的测试项(ROI即return on investment,中文意思是投资回报率)。但UI自动化相比接口自动化、白盒测试等,它更贴近手工业务测试行为。对于刚起步测试左移、效率提升的团队来说,是最迅速的切入点,也是广大黑盒tester,提升自身技术能力的起跑线。

笔者接触UI自动化一年多,兼顾业务测试的同时断断续续地投入,曾经无数次的想放弃:

“才刚写完用例,怎么开发大哥又改了UI了?”

“维护这些破用例的时间,都够我手工测三遍了,真的有意义么?”

“测试框架自己有bug,我改用例也没用啊……”

“我调试的时候这个用例还是通的,放到daily里面跑就不通,到底怎么回事嘛!”

“adb怎么这么不稳定啊,老是断!!!”

“怎么跑着跑着就crash了,到底是被测应用有问题,还是测试代码有问题啊?”

“明明界面上有这个元素,怎么就是查不到呢?”

“这破手机,能不能别老是系统弹框……”

“这手机真是渣,adb screencap截个图,居然要三分钟才返回!”

“这些控件都没有id,没有text,层级还三天两头改,要我怎么查……”

“查了这么多论坛,怎么就没有人遇到过类似的问题呢?”

……

这些问题让笔者一度怀疑,UI自动化这个TOPIC,是不是根本没用,只是tester为了涨薪,或者为了摆脱重复无聊的手工业务测试,而YY出来自我欺骗的。

二、 问题分类及目标明确

笔者将以上所有的问题简单分成三类:设计类,环境类,细节类。一个好的设计模式,能够避免一部分问题;一套好的环境,可以让我们从乏味的维护工作中解脱;精益求精的细节,让测试用例更加可靠稳定。

图一 UI自动化常见问题

填掉这三类坑,基本上就获得了一套低成本高产出、少量维护、稳定可靠的UI自动化用例集。

三、 设计类问题分析与解决

“才刚写完用例,怎么开发大哥又改了UI了?”

“测试框架自己有bug,我改用例也没用啊……”

这类问题,我们需要从根上治。UI自动化开发,也应该是严谨的开发工作,它也需要设计模式,也是磨刀不误砍柴工。这里的设计,主要包括选工具、框架分层等。很多前辈都分析过UI自动化各类工具的优缺点,对工具选用笔者不再赘述。主要依托uiautomator来介绍下笔者认为比较巧妙的用例框架设计。

1 优化测试代码框架

无论你选择appium、uiautomator、robotium还是espresso,刚入门时,看到的sample应该大致都是这样的。

图二uiautomator和espresso逻辑样例

问题在哪里?这些sample过于简单,都只教了我们UI自动化三元素:怎么查找元素、怎么操作元素、怎么校验结果。如果我们按照大多数分享帖或GitHub sample来写作自己的case。最后这种没有任何设计模式的框架,肯定会面临重构。拿上面的espresso来说:

假如action_save这个id开发改了,而你的用例集中,有30个步骤用例到了这个id,一个个去改,是不是要疯?

不厌其烦的重复写onView(withXX(xxx)).perform(click())这一长串,你不烦?

笔者是如何做的?分层设计和PageObjects模式。这两个方法,基本解决了笔者遇到的图一中所有的设计类问题。

图三 框架设计建议

按照图三进行分层设计后,得到如图四的测试代码包。

图四 分层后的用例框架

PageObjects模式发源于selenium社区,它的目的是减少重复代码,当开发修改UI时,测试只需在有限的位置修改代码。如果大家想深入了解PageObjects,请参照如下wiki:

https://github.com/SeleniumHQ/selenium/wiki/PageObjects

http://blog.csdn.net/kittyboy0001/article/details/25219053

我们来看一下,现在手管首页Page包中的代码和页面。

图五 手管首页Page层部分代码

回忆一下上面的google提供的sample,再对比引入分层设计和PO模式前后的代码,点击图五中的一键加速:

图六 引入PO前后代码对比

带来的好处,当然不仅仅是业务用例代码更清爽。

通过将查找和操作封装到基础层中,这部分代码就具体业务无关了,即使拿到其他产品中也可以复用;

通过page层的分离,所有的与业务相关的id,text等都被限定在了page包中,哪怕开发改了UI,修改page包特定的页面中对应的元素就好了。

对page包进行合理的业务拆分,比如将手管分成 MainPage(主页),SoftwareManagerPage(软件管理页),WiFiManagerPage(WiFi管理页)等,在开发改了某个具体业务的界面后,测试能够迅速知道测试代码需要改哪里。

2 兼容资源混淆的测试代码

除了整个框架的设计,有时候一些小问题也可以经过巧妙设计。比如资源混淆的问题。

图七 资源混淆

如图七,在手机管家的发布包中,用uiautomatorviewer dump下来发现,一键优化的button,其resource-id是o3,但其实开发coding时,定义的id显然不会用这种没有任何字面意义的代号,它在混淆之前叫optimize_button。

纯黑盒的UI自动化,也许你会摒弃optimize_button,直接写o3,但这样显然不够科学,既带来了严重的代码可读性问题,同时一旦版本迭代,混淆变了,o3也许就变成了o4。或者你会让开发给你测试的包,不要混淆,但如果想用UI自动化测试已发布的apk呢?

=解决该问题,也得从PageObjects说起。回到图五中OPTIMIZE_BTN的定义,这个静态变量并未在page中初始化,只有一个@FindBy的注解。其实,在框架层驱动测试开始前,框架会先调用如下图八所示的setAllField来初始化所有的page页面。

如果被测应用未混淆资源,该方法只是将@FindBy中的值赋值给Field。

如果被测应用已混淆资源,该方法则会从mObfuscationMap(未贴出全部代码,实际是解析一个开发提供的混淆表,以原始id为key,混淆id为value的HashMap)中读出对应的id对应关系,将混淆后的id赋值给Field。

图八 Page层动态初始化

四、环境类问题分析与解决

“adb怎么这么不稳定啊,老是断!!!”

“明明界面上有这个元素,怎么就是查不到呢?”

“这破手机,能不能别老是系统弹框……”

“这手机真是渣,adb screencap截个图,居然要三分钟才返回!”……

引子中提到的这些问题,根据经验,多半你的环境执行环境还不够稳定。

1 ADB相关问题

已知的ADB不稳定原因如:电压不稳,各类手机助手的干扰,系统版本与ADB版本不匹配、ADBcrash等等。如果我们迎难而上,去重写ADB,投入将无限扩大。所以建议主要的解决方案,还是尽量规避。

a) 选用可靠硬件规避电压不稳定,github上的STF项目组有过成熟的经验,选用性能更优的USB分接器,电压和可靠性会有更稳定的表现。(附上链接,wiki Recommended hardware一节中有不同硬件详细的性能对比,https://github.com/openstf/stf)

b) 屏蔽各类手机助手的干扰。91助手、豌豆荚等,基本都在adb上做了二次开发,它们会与原生adb间有兼容性问题。建议直接使用Linux/MAC系统作为运行环境以屏蔽这类干扰。

c) 降低用例在执行过程中对环境的依赖。Appium这类自动化工具,每一个测试步骤都需要PC端的appium server和测试手机端的bootstrap交互消息。测试过程中只要USB连接不稳定,都会导致整个测试套的失败。所以笔者认为,使用更原生的uiautomator会是更好的选择;同时,测试过程中的日志、截图等,也尽量在测试手机上做持久化。

2 弹框问题的解决

权限弹框,是手管UI自动化中的一个大坑。如下图,是测试手管过程中,在华为手机上遇到的部分权限弹框。这些弹框,并不会用例每次执行都弹出,不同厂商的弹出框也不一致。显然点击弹框的逻辑,写在case逻辑中,只会导致自动化变得更复杂更不稳定。

图九 各类权限弹框

uiautomator的watcher,能够完全实现点击弹框和用例逻辑的解耦。当前笔者的实现逻辑是,监听弹框上的某个控件,当该控件出现时,执行action来点击掉其中的取消或确定按钮。这样,用例就只需关注业务逻辑,而任何时候的弹框,都由watcher来自动点击。如下图中,checkForCondition关注条件,action是操作。

图十查找型Watcher

将所有的watcher分不同的手机厂商进行注册后,再调用runWatchers(),然后再执行用例。该方法可以在@BeforeClass中或者RunListener的testRunStarted中调用。当然,如果某个用例不想某个具体的弹框被watcher点击掉,也可以调用removeWatcher()反注册。

图十一 注册监听器

Watcher并不能解决所有的弹框问题。例如,在开启WiFi的场景中,由于WifiManager的setWifiEnabled和UI上的弹框点击是同步的(意思是调用了setWifiEnabled之后,如果界面上不点允许,该方法是不会返回的),使用上面的watcher方式并不会点击WiFi权限申请的允许。这时,就需要用到线程方式来解决(如下图十二),调用setWifiEnabled前,先启动一个线程等待弹框弹出。

图十二 多线程方式点掉弹框

五、细节类问题分析与解决

“我调试的时候这个用例还是通的,放到daily里面跑就不通,到底怎么回事嘛!”

出现上述问题,多半是因为我们的用例细节不够严谨。这类问题,往往决定着我们自动化用例集,是不是能从90%的case通过率,提升到100%。

1 顺序逻辑的用例

自动化相比手工,它只会关注code告诉它的验证点,所以选择逻辑在用例中应该是禁用的。如下图十三中右侧的case,如果用例执行到if中,也许else流程中存在BUG,反之亦然。此时考虑拆分用例,左侧才是理想的用例逻辑。

图十三用例逻辑

另外,写作case时,一定要牢记,只有我们告知程序要assert,它才会去assert。查找,操作,断言,UI自动化三要素缺一不可。

2 解耦的用例

在testng中,会提供dependsOnMethods注解,似乎在鼓励写作用例时,使用用例间依赖。但笔者认为,用例间的依赖,会带来不必要的维护成本。只有高度解耦的用例逻辑,才能够更加健壮的支撑用例执行顺序调整、用例增删、出现异常场景后,A用例失败不会导致B用例也失败。

3 优化等待

有时候会遇到以下场景,虽然原生的自动化工具提供了等待元素可见的方法,但使用起来,还是无法真正等到元素可见。针对这个问题,如下图的waitCondition方法是一个不错的方案,它相对于thread.sleep来说,更节省时间。

图十四反复等待方法

4 不用绝对坐标点击

绝对坐标点击,在不同尺寸屏幕上无法兼容。

第一方案应该是,推动开发对需要用到的控件添加ID或Accessibility。但根据经验还是会有一些场景需要用到坐标点击:

1, 考虑投入产出比,为所有控件添加id的成本过高;

2, 动态布局添加的ID都一样;

3, 存在非xml布局的界面(代码中直接布局)。

这时,笔者依然不建议mDevice.click(100,200)这样的坐标点击。有以下两种值得一试的方案。

a) 找到相邻控件坐标,计算当前控件的绝对坐标。如下图十四,uiautomatorviewer中点击右上角警告小三角,会得到有一些元素(黄色控件),是可能无法找到的。而使用相对坐标就是说,我们可以获取它相邻控件的坐标,然后减去或加上一个比较小的px值,再点击计算后的坐标即可。

图十五 相对坐标

b) 使用屏幕尺寸计算相对位置。在测试开始,将屏幕尺寸存下来,使用百分比的方式计算得到需要点击的位置。如下,点击【50%宽度,80%高度】的位置。

六、总结

UI自动化测试是一门学起来很简单,用起来很麻烦的测试技术。

想要入门,两周就可以了解清楚uiautomator或espresso这类工具。UI自动化,无非就是查找元素、操作元素或设备、验证结果。这三个步骤循环多次,就是一个用例。

但要用好,并产出能效,需要走的路其实很长。由于篇幅限制和知识有限,这里不可能把所有的问题一一列出。对于所有这些问题,无非两个思路:一是绕过,二是解决。

选一个尽量简化,尽量底层的工具(uiautomator或espresso),从根上绕过一些工具会存在的问题;

采用良好的设计模式,让自己的框架更稳定,生命周期更长,维护成本更低;

明知道会耗费很多时间精力,收效却很小的环境问题,尽量绕过;

优化用例逻辑和细节,使之稳定可靠,更能说服别人相信自动化的测试结论。

最后,祝愿大家在UI自动化的道路上越走越顺!

文章来源:http://www.uml.org.cn/Test/201710253.asp

小心!做 UI 自动化一定要跨过这些坑的更多相关文章

  1. 使用appium+python做UI自动化的demo

    使用appium+python做UI自动化的demo 案例使用的知乎app,下载最新的知乎apk,存在了电脑上,只需要配置本机上app目录,不需要再配置appPackage和appActivity # ...

  2. 如何做ui自动化---步骤详解

    第一步: 得到功能测试的常规用例,查看是否可以进行自动化,要明确,自动化不是为了自动化而自动化,自动化是节省人力,主要做回归测试,如果变动性特别大,不建议做自动化,具体可查看其它文章“什么适合做自动化 ...

  3. ui自动化测试数据复原遇到的坑——1、hibernate输出完整sql

    公司老项目使用SSH+informix+weblogic+IE开发,我们要做ui自动化测试,其中的测试数据复原,我打算通过hibernate输出sql,然后把插入.更新的sql改为delete或upd ...

  4. selenium做UI自动化时,模拟鼠标各种操作的ActionChains的用法

    1.selenium做自动化的时候,需要模拟鼠标进行单击.双击.右键.拖拽等操作,selenium提供了ActionChains类来进行处理. 2.执行原理:当你调用ActionChains的方法时, ...

  5. ui自动化测试数据复原遇到的坑——2、python连接informix时pytest报致命错误Windows fatal exception: access violation

    python连接informix只能通过jdbc(需要先部署java环境.我试过到IBM上下载ODBC但结局是失败的),在执行pytest时发现有一串报错(大致是下面的这样): Windows fat ...

  6. UI自动化,你值得拥有

    去年春节联欢晚会,为了那张“敬业福”,全家都卯足了劲儿“咻一咻”,连节目都顾不上看了.当时我就想,要是能自动化该多好,不停点击屏幕,屏幕不疼手还疼呢,何况还不好分心,生怕错过了“敬业福”.玩“咻一咻” ...

  7. UI自动化测试之Jenkins配置

    前一段时间帮助团队搭建了UI自动化环境,这里将Jenkins环境的一些配置分享给大家. 背景: 团队下半年的目标之一是实现自动化测试,这里要吐槽一下,之前开发的测试平台了,最初的目的是用来做接口自动化 ...

  8. java -ui自动化初体验

    本文来讲一下ui自动化的环境搭建,以及最初级的打开网页操作 说起ui自动化,想想大概是前年的时候我开始接触和学习的吧,怎么说呢无论是pc还是app,ios还是android,确实很神奇而且很华丽,但是 ...

  9. UI自动化(七)selenium简述

    1.什么是ui自动化模拟人用代码的方式去操作页面2.为什么要做ui自动化后期迭代的时候,老功能比较多,人工维护成本大这时候考虑引入ui自动化3.什么时候做ui自动化项目稳定,不在修改的某些老功能,为这 ...

随机推荐

  1. 前端跨域之Jsonp实现原理及.Net下Jsonp的实现

    jsonp的本质是通过script标签的src属性请求到服务端,拿到到服务端返回的数据 ,因为src是可以跨域的.前端通过src发送跨域请求时在请求的url带上回调函数,服务端收到请求时,接受前端传过 ...

  2. PIE SDK打开自定义栅格数据

    1. 数据介绍 信息提取和解译的过程中,经常会生成一部分中间临时栅格数据,这些数据在执行完对应操作后就失去了存在的价值,针对这种情况,PIE增加了内存栅格数据集,来协助用户完成对自定义栅格数据的读取和 ...

  3. Linux命令2

    http://note.youdao.com/noteshare?id=172b36da28e63a528f2cb70fb7d9ea96 http://note.youdao.com/noteshar ...

  4. Windows 计算机取证

    windows安全账号管理(SAM) Unveilling The Password Encryption Process Under Windows –a Practical Attack 上述这篇 ...

  5. 基于windows 10打造的kali工具集

    基于windows 10打造的kali工具集.iso,适合于习惯使用windows的安全从业者.if you like it,please touch star! 作为安全从业主,Kali都是必备工具 ...

  6. linux echo命令颜色显示

    echo命令颜色显示: echo:      -n:  不换行.      -e:让转移符生效. \t(tab) \n (换行) 实例: $ echo -e "\033[34mabcd\03 ...

  7. Spring通过配置类加载实体bean

    以下4个java类都在都一个包下: 1.定义接口 public interface AA { void play(); } 2.定义实体bean //组件注解,表明该类是一个组件 @Component ...

  8. <人人都懂设计模式>-装饰模式

    书上,真的用一个人穿衣打拌来讲解装饰模式的呢. from abc import ABCMeta, abstractmethod class Person(metaclass=ABCMeta): def ...

  9. linux之shell脚本学习(一)

    #!/bin/bash echo 'hello' your_name='longxiong' echo $your_name echo ${your_name} for i in `ls /opt`; ...

  10. python3字符串的方法及注释/ 字符串格式化符号含义及格式化符号含义

     capitalize()   把字符串的第一个字符改为大写   casefold()   把整个字符串的所有字符改为小写   center(width)   将字符串居中,并使用空格填充至长度wid ...