一、引子
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来说:
1.假如action_save这个id开发改了,而你的用例集中,有30个步骤用例到了这个id,一个个去改,是不是要疯?
2.不厌其烦的重复写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前后代码对比
带来的好处,当然不仅仅是业务用例代码更清爽。
1、通过将查找和操作封装到基础层中,这部分代码就具体业务无关了,即使拿到其他产品中也可以复用;
2、通过page层的分离,所有的与业务相关的id,text等都被限定在了page包中,哪怕开发改了UI,修改page包特定的页面中对应的元素就好了。
3、对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页面。
1、如果被测应用未混淆资源,该方法只是将@FindBy中的值赋值给Field。
2、如果被测应用已混淆资源,该方法则会从mObfuscationMap(未贴出全部代码,实际是解析一个开发提供的混淆表,以原始id为key,混淆id为value的HashMap)中读出对应的id对应关系,将混淆后的id赋值给Field。
图八 Page层动态初始化
四、环境类问题分析与解决
“adb怎么这么不稳定啊,老是断!!!”
“明明界面上有这个元素,怎么就是查不到呢?”
“这破手机,能不能别老是系统弹框……”
“这手机真是渣,adb screencap截个图,居然要三分钟才返回!”……
引子中提到的这些问题,根据经验,多半你的环境执行环境还不够稳定。
1、ADB相关问题
已知的ADB不稳定原因如:电压不稳,各类手机助手的干扰,系统版本与ADB版本不匹配、ADB crash等等。如果我们迎难而上,去重写ADB,投入将无限扩大。所以建议主要的解决方案,还是尽量规避。
1)、选用可靠硬件规避电压不稳定。github上的STF项目组有过成熟的经验,选用性能更优的USB分接器,电压和可靠性会有更稳定的表现。(附上链接,wiki Recommended hardware一节中有不同硬件详细的性能对比:https://github.com/openstf/stf)。
2)、屏蔽各类手机助手的干扰。91助手、豌豆荚等,基本都在adb上做了二次开发,它们会与原生adb间有兼容性问题。建议直接使用Linux/MAC系统作为运行环境以屏蔽这类干扰。
3)、降低用例在执行过程中对环境的依赖。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)这样的坐标点击。有以下两种值得一试的方案。
1、找到相邻控件坐标,计算当前控件的绝对坐标。如下图十四,uiautomatorviewer中点击右上角警告小三角,会得到有一些元素(黄色控件),是可能无法找到的。而使用相对坐标就是说,我们可以获取它相邻控件的坐标,然后减去或加上一个比较小的px值,再点击计算后的坐标即可。
图十五相对坐标
2、使用屏幕尺寸计算相对位置。在测试开始,将屏幕尺寸存下来,使用百分比的方式计算得到需要点击的位置。如下代码,点击【50%屏幕宽度,80%屏幕高度】的位置。
CODE: mDevice.click( screenWidth/2 , screenHeight*80/100 )。
六、总结
UI自动化测试是一门学起来很简单,用起来很麻烦的测试技术。想要入门,两周就可以了解清楚uiautomator或espresso这类工具。UI自动化,无非就是查找元素、操作元素或设备、验证结果。这三个步骤循环多次,就是一个用例。
但要用好,并产出能效,需要走的路其实很长。由于篇幅限制和知识有限,这里不可能把所有的问题一一列出。对于所有这些问题,无非两个思路:一是绕过,二是解决。
1、选一个尽量简化,尽量底层的工具(uiautomator或espresso),从根上绕过一些工具会存在的问题;
2、采用良好的设计模式,让自己的框架更稳定,生命周期更长,维护成本更低;
3、明知道会耗费很多时间精力,收效却很小的环境问题,尽量绕过;
4、优化用例逻辑和细节,使之稳定可靠,更能说服别人相信自动化的测试结论。