跨平台的移动端UI自动化测试

2018-05-22   出处:大商所行业测试中心  作/译者: 大连飞创  

摘要:本文提出一种跨平台的UI自动化测试方案,一方面使用像素级的截图对比技术,解决传统UI自动化测试难以验证页面样式的问题;另一方面用统一部署在服务器端的JavaScript测试代码代替Android和iOS测试代码,大大提高编写测试代码的效率。该方案经过实际验证,具有效率高、质量好、便于维护等多方面优点,文中将阐述具体设计思路以及各关键步骤的实现方法,为同类测试提供借鉴。

一、 背景

金融服务数字化、信息化和智能化趋势日益明显,在遵守法律法规、行业及公司制度、业务规则等多方面要求的前提下,面对瞬息万变的客户需求,金融行业的移动客户端越来越多采用敏捷开发的工程实践,灵活选用技术框架、平台和语言,持续迭代升级,优化客户体验。

日益频繁的需求变更所带来的不单是系统开发模式的变迁。质量控制方面,移动客户端需要适配的屏幕尺寸和机型多种多样,测试不仅要验证页面元素内容的正确性,还需验证各个元素的显示效果。而传统的页面自动化测试大多以断言的形式来验证页面元素的内容,无法验证视觉上的显示效果,在实际应用中,往往起不到实际作用,反而降低了测试效率。如何利用自动化方法更高效地完成庞杂的测试工作,成为敏捷开发模式下亟待解决的关键问题。

本文提出对比截图的方式,进行像素级别的UI测试,不仅可以验证内容的正确性,还可以验证视觉效果的一致。同时为了解决iOS和Android两个平台分别编写测试代码的重复工作问题,通过一个自主研发的测试工具包,把测试代码以JavaScript的形式部署在服务器端,通过HTTP协议下发到iOS和Android两个平台的多个测试设备分别执行,再把测试结果收集到服务器,大大提高了工作效率。

二、 跨平台的移动端UI自动化测试探索

(一) 测试思想

传统的自动化测试思想大多起源于JUnit这种单元测试框架,即使是页面级别的自动化测试,如Selenium等也多是通过断言语句检查页面上特定元素的属性值和推测结果是否一致。这种思想的自动化测试需要测试人员为所有待验证元素的属性值编写断言语句,不仅编写测试代码花时间费精力,也需要后期投入大量的维护成本。

即使拥有完整的自动化测试用例,项目中也不可能完全放弃人工测试,自动化测试和人工测试之间的相互关系,是制定项目测试方针时必须要考虑的顶层问题。一些适合人工去做的工作,例如验证页面元素的位置、颜色、线条粗细等断言语句不容易验证的内容,交给人工验证并无不妥,也是不可省略的步骤。而一些重复性的工作,例如修改了某个业务模块之后,系统中其他页面的显示效果在所有需要适配的设备上是否受到影响,没有修改过的其他业务流程是否会产生错误,这种回归性质的测试则要尽可能减少人工的参与。

在上述测试思想的指导下,工程师在UI自动化测试中使用一种“录制-截图-对比”的方法:

首先,按照正常流程编写测试用例、并且进行严格的人工测试,这一阶段与传统的人工测试完全一致。

然后,把经过测试过的页面,使用自动化脚本在所有测试设备上截图,把所有截图保存到服务器,这一步称为录制。

执行对比时,仍然使用自动化脚本对每一个页面截图,然后把截图和上一步的录制截图进行像素级的对比,保证每一个像素的一致,否则算作测试失败这一步相当于传统自动化测试的断言部分,当前截图是执行结果,录制的截图是预期结果。

通过上述步骤,每一次修改后都进行一次自动化回归测试,可以保证当前时间节点上,系统内所有页面在所有设备上显示时,每一个像素都和上一次截图时一致,即使是1个像素的偏移也可以被轻松发现。


表1 传统自动化测试和跨平台移动端UI自动化测试的对比

(二) 验证流程

1、 服务端部署JavaScript测试代码

每个文件是一个测试用例,一个测试用例中可以有多个确认点。可以部署在Apache、Nginx等HTTP服务器上,只要提供一个测试设备可以访问的HTTP地址即可,往往可以把测试代码、模拟测试数据部署在一个地方。

2、 测试设备下载代码

首先在客户端配置测试用例的执行地址,执行测试用例时,先把测试用例的JavaScript代码下载到客户端然后交给JavaScript引擎执行。

3、 使用JavaScript引擎执行代码

除了WebView,各平台都有比较成熟的JavaScript执行引擎,关于JavaScript引擎的选择,可以参照后面的关键点说明。

4、 JavaScript调用Native(Java以及Objective-C)端插件执行命令

关于JavaScript与Native端的交互方法,可以参考后面的关键点说明。

5、 根据指令运行测试业务

真正执行业务逻辑的地方,根据业务特点,可能会有与外部系统的交互,开发人员需要为外部系统准备好模拟数据。

6、 页面截图

调用客户端的页面截图方法,保存为JPEG或者PNG形式。

7、 截图回传给服务器

实际上也可以把截图直接保存在客户端,日后只要在客户端进行比较即可,但是为了方便进行统一的比较和分析展示,更推荐把截图回传给服务器进行处理。

8、 截图对比

服务器在像素级别对比当前截图和标准截图是否一致,不一致则报错。标准截图是第一次经过人工测试之后录制的截图,执行测试用例时,可以指定是录制模式还是验证模式,如果是录制模式,只进行截图和保存。验证模式时,把当前的截图和上一次录制的截图进行对比。

9、 将判断结果返回移动端,完成测试

将判断结果返回移动端是为了在移动端显示统一的测试报告,因为移动端除了UI部分的截图对比,可能还会有文件、数据库等其他验证项目的断言语句。

(三) 关键点说明

无论是跨平台还是截图对比,在具体的技术选型上都有各种各样的组合可以选择,经过各种调查和尝试,对主流的开发语言、框架和中间件进行评估,我们摸索出一套完整的解决方案,经过两年时间在实践中不断的改善和优化,已经可以在生产环境中发挥重大作用,节约人力成本的同时还能提高测试质量。现在把一些最佳实践点的关键内容总结如下:

1、服务端部署JavaScript测试代码:测试代码结构

测试代码是一段标准的HTML代码,具体的测试指令以JavaScript的形式包含在标签中,之所以采用JavaScript来编写测试,是因为无论iOS端还是Android端都有成熟的JavaScript执行引擎,而且JavaScript在描述用户行为和页面元素方面具有无与伦比的表达能力,无需编译即可执行,学习成本又低。


前面引用的native.js和testcase.js中包含了通用的工具代码,负责与移动端框架进行交互(交互方式后面会记述)。后面的中是具体的测试指令。测试设备从服务端下载这个HTML,找出其中的JavaScript交给JavaScript引擎执行。

2、 测试设备下载代码:代码文件的解析

标准的HTML文件也是XML文件,所以我们按照XML的规则来解析代码,仅提取XML中script标签的内容即可,如果遇到script标签中带有src属性,需要另外再下载src指定的文件,最后将所有JavaScript代码拼接成一整段代码交给执行引擎。当然可以直接下载JavaScript文件来执行,但是考虑到传统的Web开发中JavaScript是被包含在HTML中的,故采取同样的思路,事实证明这种方式大大增强了扩展能力。

3、使用JavaScript引擎执行代码:JavaScript引擎的选择

iOS中可以使用系统自带的JavaScriptCore框架执行。Android中可以使用Rhino作为执行引擎,Rhino 是一种使用 Java 语言编写的 JavaScript 的开源实现,原先由Mozilla开发,现在被集成进入JDK 6.0。当然也可以选择Chrome的V8引擎执行,选择Rhino引擎的原因是它用Java语言编写,可以直接以jar包的形式集成到项目中。

4.1、JavaScript调用Native端插件执行命令:JavaScript和Native端通信方法

如果使用WebView来显示网页,可以利用WebView的特性进行JavaScript和Native端(统指iOS和Android端)通信。但直接使用JavaScript引擎时,无法使用shouldOverrideUrlLoading()等方法进行拦截具体指令,并且Native代码和JavaScript分处不同的作用域,相互之间不能共享对象,因此无法直接相互调用。针对这种情况,工程师在JavaScript的内存空间维持一个指令队列,每当JavaScript需要调用Native代码时,把指令推入指令队列,Native端从指令队列中取出一个指令执行,执行完毕后,检查指令队列中是否有下一个指令继续执行,直到所有指令执行完毕。

4.2、JavaScript调用Native端插件执行命令:JavaScript和Native端通信协议

JavaScript指令队列中的每一条指令,包括插件名称、目标、参数三部分。

Native端收到插件名称后,查找预先在Native端注册的插件,传入具体“目标”和“参数”来执行。

每一条指令可以以“插件名称://目标?参数”的形式来传递。

例如在测试代码中经常使用的perform_event插件:

以上指令代表执行Native端的“点击界面上的7日年化收益率”用户操作。

5.1、根据指令运行测试业务:Native端使用插件执行指令

Native端启动时需要注册一系列插件,例如perform_event插件代表执行用户动作,snapshot代表截图。由于插件实现在Native端,所以要根据平台特性进行具体实现,例如“perform_event://client?text=7日年化收益率”这条指令,在iOS端可以从UIViewController的View开始,向下查找每一层的UILabel,看是否有包含特定文字的项目,然后在该项目上执行点击动作。而在Android端,可以使用solo这种测试框架来直接点击屏幕上的特定文字部分。

使用代码来模拟用户行为,例如打开“购买”页面,既可以点击“购买”按钮(perform_event://client?text=7日年化收益率),也可以直接启动购买页面(open://购买页面)。为了更加真实的模拟用户行为,建议测试过程中尽量模拟用户的真实行为,不要跳过用户操作直接实现结果,例如前例中建议从页面的所有View中找到带有“购买”文字的按钮,实现按钮的点击操作。

5.2、根据指令运行测试业务:测试数据准备

测试中从Native端请求的REST API必须使用模拟的测试数据,以保证在任何时刻页面数据的一致。

模拟数据一方面可以在Native端通过Mock一个HTTP Server来截断所有HTTP请求,但是这样同样需要iOS和Android两个平台都进行编写,日后也需要维护两部分数据。更好的方法是直接在测试服务器上搭建一个模拟的REST API服务,针对每一个测试用例,返回固定的模拟数据,如果数据简单,使用静态JSON文件即可。

6、数据收集和对比

截图之后的图片直接在Native端通过HTTP协议上传至服务器。在服务器端,通过手机型号、操作系统、操作系统版本、业务名称、编号进行分类保存,这样可以迅速查找到某一个设备上某一个业务的所有截图。测试服务器中保存所有录制数据,每一次从测试设备收集到测试截图后,在服务器端和录制的数据进行像素对比,然后把测试结果返回给移动端的Unit Test Case。

三、总结

通过以上方式,每次完成一个模块的集成测试,工程师都会编写完整的测试代码,并录制测试对象在所有设备上的截图。日后有任何修改,都会在所有测试设备上进行整个系统完整的回归测试,像素级别的比较可以保证系统所有的页面、每一个像素、在每一台设备上都没有变化,大大节省了回归测试的时间。

通过服务器端统一部署的方式,省去了在iOS和Android两端分别编写测试代码的工作。并且用于测试的JavaScript中,只需要编写面向用户操作的交互指令,大大降低了编写测试代码的技术要求和时间成本。

当系统变得日益复杂、开发人员越来越多、需要适配的机型不断增加时,这种跨平台的自动化UI测试,可以有效保证移动端的质量,毕竟一个移动端的致命bug,有可能需要通过升级版本、应用商店审核、最终用户更新的漫长过程才能得到解决,而这个过程会导致用户的流失。

当然,自动化的测试只能作为人工测试的辅助,完成不适合人类的工作,在第一轮测试中主动发现问题不是自动化测试的长处,即使可以发现,也要付出更多的成本。快速大量的回归测试才是自动化系统擅长的事情。

测试中没有银弹,提高人的主观能动性、避免形式化的束缚、使用自动化技术替代机械劳动,不断提高系统交付质量这条路上永远没有终点。






欢迎给测试窝投稿或参与内容翻译工作,请邮件至editors@testwo.com。也欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,并与我们的编辑和其他窝友交流。
164°|1649 人阅读|0 条评论

登录 后发表评论
最新文章