客户端自动化测试研究
背景
测试作为质量保证极其重要的一环,在移动App开发流程中起到非常关键的作用。从开发工程师到测试工程师,人人都应具备良好的测试意识,将隐患和风险在上线之前找出并解决,可以有效的减少线上事故。
美团和大众点评App作为美团点评平台的主要入口,支持承载着美团点评各大业务。其中美团点评境外度假业务主要包括了出境游相关业务以及所有的境外城市站,也是美团点评非常看重和大力发展的业务线。为了保证质量,需要进行各项测试:冒烟测试[1]、功能测试、集成测试、专项性能测试,回归测试[2]。其中冒烟测试和回归测试大多由开发自己手动执行,有较大的优化空间。一方面,测试的人力成本较高;另一方面,在之前的测试过程中发生过漏测等问题,这些问题在测试阶段被QA发现,又会再次返工,费时费力。
鉴于这两部分测试用例相对稳定,不会频繁发生较大的变化,我们打算将其自动化,降低人力成本投入,将测试结果报表化,避免人为疏漏造成的一系列问题。
[1]冒烟测试(smoke testing),就是开发人员在个人版本的软件上执行目前的冒烟测试项目,确定新的程序代码不出故障。冒烟测试的对象是每一个新编译的需要正式测试的软件版本,目的是确认软件基本功能正常,可以进行后续的正式测试工作。冒烟测试的执行者是版本编译人员。[2]回归测试是软件测试的一种,旨在检验软件原有功能在修改后是否保持完整。
方案选型
目前业界测试方案非常多,Android和iOS双平台的方案加起来大约有十七八种。应该如何选择适合团队的测试方案呢?我们主要考虑以下几个方面:
- 平台支持。
- 稳定性。
- 维护成本。
- 可扩展性。
其中维护成本我们尤为看重。目前团队的开发和测试同学任务都比较饱和,业务处于高速发展期,没法抽出太多的时间开发/维护测试脚本,这就需要在这方面做到在投入较少时间的前提下不影响自动化测试的结果产出。常规的TDD[3]是函数级别进行测试驱动开发,通常需要在代码级别做很多工作,需要测试团队投入较大的开发成本。鉴于在成本方面的考虑,我们打算使用BDD[4]来解决这个问题。主要在行为层面进行测试投入,在代码层级方面投入较小,用非常有辨识力的行为进行测试。
在平台支持方面,由于是客户端团队,所以我们希望写好的用例可以同时跑在Android和iOS两个平台上,还希望用例可以一部分进行美团和大众点评两个App的复用,所以需要一个可以跨平台的方案。
[3]测试驱动开发(Test-driven development,缩写为TDD)是一种软件开发过程中的应用方法,倡导先写测试程序,然后编码实现其功能得名。测试驱动开发是戴两顶帽子思考的开发方式:先戴上实现功能的帽子,在测试的辅助下,快速实现其功能;再戴上重构的帽子,在测试的保护下,通过去除冗余的代码,提高代码质量。[4]行为驱动开发(Behavior-driven development,缩写BDD)是一种敏捷软件开发的技术。它通过用自然语言书写非程序员可读的测试用例扩展了测试驱动开发方法。
从入门到放弃
去年年底的时候我们团队就自动化测试方面进行了探索。发现Calabash满足BDD和跨平台,于是进行了小范围试用。在脚本开发和维护方面,成本确实低于函数级别的测试开发,它可以用一种类似自然语言的方式编写测试用例,这是一个简单的test case示例:
Scenario: 首页 Then I press "上海" When I press view with id "city" Then I see "海外" When I press "海外" And I press view with id "start_search" When I enter "东京" into input field number 1 Then I press list item number 1 Then I see "东京" When I press "美食" ...
这个示例相信开发工程师们甚至没写过代码的人也看得懂,其实就是用常规的行为思维模式去编写测试用例。其中Feature、Scenario、Step是BDD的三个核心概念:
- Feature:就是字面意思,主要是描述功能特性。
- Scenario:场景,在这里可以简单的理解为一个个的细分case,通常情况下需要多个场景拼接来完成一个具体的test case。
- Step:实现场景的步骤代码。
但是Calabash在业内相对小众,遇到问题就不太好解决。比如在某些三星手机上就遇到了某些控件根据ID找不到的问题,会影响UI元素的定位。在编写自动化脚本时,元素定位的唯一性是一个看似简单实际上会有很多坑的问题,脚本的稳定性一定程度上依赖了如何进行元素定位。
其次,在Android团队想要把方案推广到iOS平台的时候,我们发现了一个很大的问题:iOS接入Calabash的成本太高。Android的接入成本很低,只需要一个重签名的apk文件就可以了,并不依赖源码,而iOS的接入需要依赖源码做一些工作,这就给iOS同学造成了很多困难。美团和大众点评是两个巨大的App,在源码接入方面的工作量并不小,而且很多隐患无法预料,就算依赖源码接入之后,还有一个问题需要解决:iOS的ID系统。通常iOS业务开发代码中不是通过ID来获取页面元素,不管是手写布局代码还是用xib布局,开发者一般不会给界面元素加ID,所以iOS的元素大多都没有ID,而Calabash对元素的定位主要依赖ID,这无疑让我们感到雪上加霜。
在Android团队用写好的用例进行了几个版本的冒烟测试之后,团队内部Android、iOS、QA的同学坐下来一起进行了方案后续的探究,最终决定放弃Calabash,继续寻找可以替代的方案。
从放弃到再次拥抱
在经历过Calabash的挫折之后,我们在选型方面更加慎重。QA同学对Appium有一定的经验,于是先采用了Appium方案进行兼容性测试和部分回归测试。在业务快速发展的过程中,维护成本让QA同学越来越疲于应付,于是我们又坐在一起进行新方案的讨论和探索。
Calabash的BDD模式是大家认可的,也是大家愿意接受的,那就需要在新的方案中,继续使用这种方式编写维护测试用例。我们想把Appium和Calabash两者的优势结合起来,还想把之前写过的Calabash的测试用例无缝迁移继续使用。
取其精华
Calabash为什么可以使用类似自然语言的方式编写测试用例达到BDD的效果呢?根本原因是因为Cucumber。
在Calabash官网中注明了他们使用了Cucumber(一种简单的自然语言方式的BDD开源解决方案),那么我们能否底层使用Appium支持,上层使用Cucumber进行测试用例的开发和维护呢?
答案当然是可行的。我们在Appium的官方示例代码中找到了答案。Appium官方提供了与Cucumber结合使用的例子作为参考,虽然这部分代码已经两年没更新了,但是依然给我们提供了关键思路。
新方案形成
客户端的同学与QA同学进行了讨论,确认了使用QA同学目前使用的按照App进行用例拆分的方案。之前Calabash的方案有很多可以借鉴过来,于是我们先进行了整体结构的调整:
按照点评和美团两个App进行用例区分,公共步骤的封装在common_steps.rb中。点评和美团的目录下分别有cucumber.yml脚本,这是用来区分Android和iOS平台的,内容大概是这样:
# config/cucumber.yml ##YAML Template --- ios: IDEVICENAME='ios' android: IDEVICENAME='android'
其中Android/config和iOS/config是Android和iOS两个平台的特定配置,这部分配置代码在support包内,是Appium启动需要加载的配置。
平台的区分在env.rb中体现出来:
class AppiumWorld end if ENV['IDEVICENAME']=='android' caps = Appium.load_appium_txt file: File.expand_path("./../android/appium.txt", __FILE__), verbose: true elsif ENV['IDEVICENAME']=='ios' caps = Appium.load_appium_txt file: File.expand_path("./../ios/appium.txt", __FILE__), verbose: true else caps = Appium.load_appium_txt file: File.expand_path('./', __FILE__), verbose: true end Appium::Driver.new(caps) Appium.promote_appium_methods AppiumWorld World do AppiumWorld.new end
这样通过cucumber -p android/ios就能运行相应平台的用例了,Cucumber其他参数自行查阅,和Calabash非常相似。
完全移除Calabash之后,所有Calabash内置的Steps就没有了,需要重新封装。其中Feature、Scenario、Step的概念没有发生变化,和Calabash完全一致。重新封装Steps需要依赖appium_lib。为了降低封装成本,提供更多可用的Steps,我们还引入了selenium-cucumber作为辅助使用。
最后testdata.rb是保存测试数据的文件,例如测试账号的登录用户名和密码等数据。
最终需要依赖的库大致是这些:
gem 'appium_lib', '~> 9.4.2' gem 'rest-client', '~> 2.0.2' gem 'rspec', '~> 3.5.0' gem 'cucumber', '~> 2.4.0' gem 'rspec-expectations', '~> 3.5.0' gem 'spec', '~> 5.3.4' gem 'selenium-cucumber', '~> 3.1.5'
这样就完成了组合方案的整体框架。
新方案优势
新方案形成之后,我们的提测流程就多了一道保障:
于是每个客户端RD都可以愉快的点击脚本生成测试报告,提交给QA同学,省去了大家本地跑测试的时间,也帮助QA同学节约了时间,不会再出现返工或者测试遗漏的情况。
整体稳定性提高
由于底层切换到了Appium,稳定性提高了,同样的机型不再出现类似Calabash的不兼容问题了(根据ID无法定位到某个元素),QA同学在Appium的自动化道路上已经做过不少实践,具有相关经验。在Webview方面支持也是比较好的,相比Calabash只是多了切换Webview和Native上下文的步骤,Appium的优势完全体现出来了。
iOS接入成本降低
针对Android和iOS的接入成本,也降低到了一致。Android依旧是提供apk,iOS提供重签名的ipa包即可,无需源码集成,这就解决了Calabash方案iOS集成成本大的问题。
元素定位手段增多
公共Steps一次封装处处可用,在跨App复用的业务上,测试代码也几乎可以复用,编写测试脚本的成本再次降低。iOS控件缺少ID不好定位的问题也得到了解决,Appium支持ID、class、name、XPath等元素定位方式,如果前三者都不可用的情况下,使用相对复杂但几乎万能的XPath都可以得到解决。
例如一个复杂的XPath:
Then I press view with xpath "//android.widget.LinearLayout[1] /android.widget.FrameLayout[1]/android.widget.LinearLayout[1] /android.widget.FrameLayout[1]/android.widget.LinearLayout[2] /android.widget.FrameLayout[1]/android.widget.ListView[1] /android.widget.LinearLayout[1]/android.widget.LinearLayout[1]"
不用担心这么复杂的XPath应该怎么写,这其实是最简单的,因为可以通过Appium-inspector抓取得到。当然XPath的写法有很多种,可以选用兼容性更好的写法。
原有脚本无缝迁移
之前在使用Calabash的时候编写的脚本,在封装好公共Steps之后,几乎无缝的进行了迁移,对上层编写测试用例的同学来说,几乎没有变化,无需关心是Calabash还是Appium,使用和原先一样的BDD方式继续愉快的写用例就好。
Calabash方案时期的homepage场景(部分):
切换新方案后homepage场景(部分):
并没有太大的差别。
易集成Jenkins,报告可视化
Cucumber可以进行报表的可视化输出,只要在命令后面追加--format html --out reports.html --format pretty,在执行完全部脚本之后就可以看到生成好的HTML格式的测试报告,也可以使用JSON的格式。
集成Jenkins的方式也相对常规,只要安装好需要的依赖就可以。
在测试过程中,我们使用了公司内部的云测机器远程平台:
利用远程平台的真机进行远程脚本测试,测试报告示例如下:
自动化测试运行效果
在境外业务线客户端进行了自动化测试实践,目前用于固有冒烟自动化,方案前后对比如下。
Calabash方案时期境外点评固有冒烟用例耗时:
新方案境外点评固有冒烟用例耗时(相比之前Calabash方案时期的用例有所增加):
通过数据对比可以看出,用例数量与执行耗时并不是严格的线性关系,在用例数量扩大一倍的情况下,耗时并不会线性的扩大一倍。
开发成本:单个用例的开发成本主要根据用例规模相关,开发一个包含7个动作的用例大概耗时30分钟左右,其中包括了定位元素的耗时。多个用例的开发成本不止和用例规模相关,还和用例之间是否有复用的场景相关,这就牵扯到了Scenario拆分粒度的问题,下文中有提到。
目前执行用例美团+点评总耗时20分钟左右,降低了人力成本,避免了QA同学返工的情况,方案新老交替无缝平滑过渡,维护成本低。这不仅是我们团队对自动化方案的期许,也是自动化测试的价值所在。
问题与展望
问题
scroll or swipe?
在使用UIAutomation的时候,Android页面滑动采取的方式是调用scroll_uiselector方法,例如:
Then /^I scroll to view with text "([^\"]*)"$/ do |value| text = %Q("#{value}") args = scroll_uiselector("new UiSelector().textContains(#{text})") find_element :uiautomator, args end
但是这种方式存在不稳定性因素,在某些情况下,滑动搜索UI元素非常慢(上下滑动很多次)甚至滑动多次最后仍然搜索不到,脚本会执行失败。在比较复杂的App上很容易出现,是整体脚本稳定性和成功率的瓶颈。如果更换为UIAutomation2,就可以使用swipe语句进行相对精准的滑动:
swipe start_x: start_x, start_y: start_y, end_x: start_x, end_y: start_y - pixel.to_i
根据撰写本文时Appium的最新版本v1.6.5进行实践,发现切换UIAutomation2后使用swipe滑动,对比scroll的方式成功率提高了一倍多,耗时减半,效果非常显著。虽然其他语句会略微受一点影响,不过整体改动幅度很小,性价比很高,而且UIAutomation2还支持对Toast的识别,整体稳定性大幅提高,建议使用UIAutomation2。
Scenario拆分粒度
在很多情况下,一个test case是由一个或多个Scenario组成的,不同的test case又会存在部分Scenario复用的情况,明确Scenario的拆分粒度可以帮助开发人员降低测试脚本的编写成本,达到一定程度上的App内部复用甚至跨App复用。尤其在多人协作的环境下,这是一个非常值得探究的问题。
展望
自动触发云测
目前触发的方式是人工触发Jenkins job,最后输出报告。未来要做的是在特定的时期自动触发job进行云端自动化,触发时期可能会参考App的开发周期时间节点。
人人都是测试工程师
我们希望团队内人人都具备良好的测试思维,能站在测试的角度想问题,领悟测试驱动开发的意义。通过简单的方式让团队内的同学们参与测试,体会测试,写出更优秀的代码。
参考资料
- Appium Doc
- appium/ruby_lib docs
- selenium-cucumber-ruby Canned Steps
作者简介
立成,美团点评酒旅境外度假研发组Android高级开发工程师,在Android开发、跨平台开发、移动端测试等领域有一定的实践经验,热爱新技术并愿意付诸实践,致力于产出高质量代码。
酒旅境外度假研发组,负责美团点评境外度假业务。美团点评海外站覆盖全球100多个国家地区,收录美食、购物、酒店、景点等各类海外商户超过500万家,为出国游玩用户提供吃、玩、住、买、交通一站式解决方案。我们的使命是做「最好用的境外中文消费指南」,全方位解决用户海外旅行中的问题,最终成为出境游行业技术领域智能化、国际化的标杆!我们团队长期招聘Android、iOS、FE、Java、算法等技术方向的工程师,诚挚欢迎投递简历至hongguangyan#meituan.com。
【思考题】本文为大家介绍的新型客户端自动化测试方案大大降低了自动化测试的成本,但是依然存在测试脚本的迭代与维护的问题。那么应该在什么时间节点或者时间段对脚本进行维护,才能保证在测试过程中不会因为脚本滞后而导致测试失败?如何将这部分成本降到最低?如何实现人人都是测试工程师的愿景?
下一章:Android增量代码测试覆盖率工具
前言: 美团点评业务快速发展,新项目新业务不断出现,在项目开发和测试人员不足、开发同学粗心的情况下,难免会出现少测漏测的情况,如何保证新增代码有足够的测试覆盖率是我们需要思考的问题。 Bad-Cas ...