团队介绍
我们团队有一个英文简称:COD。COD有两层含义:戏谑一点说就是Code Of Dog(代码狗),这是我们私下里对自己的戏称;正式一点的话是代表Call Of Duty,大家可能玩过一个游戏叫使命召唤,对于我们来说自动化探索就是我们的使命。
我们是一个年轻的团队,成员基本上都是在24-26岁这样的年龄区间,主要致力于通过测试技术的研究、开发一系列自动化测试工具和平台,以满足部门的日常使用并提升整个团队的测试效率。
魅族测试的两个主要框架
魅族主要使用UIAutomator和Monkey两个框架进行自动化测试,两个框架均为系统原生支持的框架,不需要对系统修改或安装其他应用。
1. UIAutomator
UIAutomator是系统原生支持的框架,用它来做系统的稳定性测试是非常方便的,在4.0的机器上,升级完成固件之后就可以直接做测试工作。因为UIAutomator是基于系统底层的,所以整个框架代码不是很多,可以在上面做简单的扩展、实现一些便利于测试工作的逻辑。它还可以支持跨APP的测试,验证APP的功能是否正常、流畅度和待机耗电情况等。
2. Monkey
Monkey也是系统原生支持的,我们主要用它来做长时间的压测,如检查7*24小时的压测是否出现崩溃的情况。Monkey可以指定随机事件的种子,通过一些方法调整它的时长,调整事件的比例,来达到测试侧重点的要求。
3. 两个框架的应用角度
UIAutomator主要用于回归测试和冒烟测试。对于一些成熟的不容易产生控件变更的项目,我们主要通过UIAutomator的脚本测试APP是否正常;对于一些待机耗电的专项测试,我们也会用UIAutomator的脚本来辅助一些控件操作。
Monkey主要是用来做稳定性测试和冒烟测试。Monkey属于没有太多逻辑的点击,对于Monkey的冒烟测试,我们主要是测一些安装拆卸能不能正常打开、会不会产生无响应、崩溃等情况。
魅族自动化测试发展的五个阶段
第一个阶段
刚开始涉及自动化的时候,我们有一些专门的人去负责脚本的编码工作,执行是先用USB与设备连接,预置初始条件,比如登陆特定的账户、放一些固定文件到sdcard目录,通过执行结果来收集用例的正确性和某一些失败原因,出现崩溃的话我们会收集它的异常信息,最终生成一份报告,再把这些缺陷信息发布到内部的缺陷平台上去。
上图显示的是我们在第一阶段遇到的一些问题。最大的问题是报告不是很明确,我们不能从具体的SHELL输出中获取真正的含义,我们也不知道它究竟要达成什么效果?操作步骤是什么?失败了意味着什么?也不能捕获崩溃或是ANR。如果系统某个APP崩溃或发生ANR,单靠UIAutomator框架是拿不到有用的信息的,可能要开一个logcat窗口去收集,而这些信息又不是那么容易可以收集到的。
测试的过程中,我们希望收集测试结果并对测试的关键操作或步骤进行截图,虽然UIAutomator框架提供了截图,但大家实际编码中的实现方式千差万别没有统一,我们希望用统一的方式收集结果、关键操作和截图,并最终汇总在一起。我们内部有一个报告汇总的过程,但是只靠SHELL窗口的输出很难做到统一无误的汇总,这个过程很困难,需要耗费很多的人力。
第一阶段面临的主要就是这两个问题,针对这些问题我们也给出了一些解决方案。
比如对于自动化脚本来说,我们要求自动化脚本必须提供操作步骤和预期的结果信息,这些信息可能就是标记在方法上的注解。我们开发了一个APP,通过收集脚本发出来的广播来收集这个脚本最终的执行结果,刚开始比较简单,就是用例名字加结果,后来添加了操作步骤和期望。我们通过APP对脚本执行全过程的操作步骤进行录制和截图。也通过一些其他的系统底层的组件开发了监控崩溃、ANR和收集运行的Log,统一汇总结果发送给我们的web端展示。
这是第一阶段整体的架构图。先由自动化脚本提供操作步骤、期望、结果,然后由开发的守护APP提供录制、截图,对崩溃、ANR和LOG的收集,最后到平台上做结果展示,另外还对测试过程中复现的bug与内部缺陷平台对接。有了这个架构后报告就不会像以前那样分散在每个人手里,而是有一个统一的web页面展示,方便了存档与部门间的分享。
这是我们的报告截图,报告最主要的是以下几列状态信息:某一个模块总共跑多少条、失败多少条、成功多少条、出现了哪些bug等,这些只是一部分信息,还有一些操作比如某一个模块点进去就是显示执行的全部用例和结果信息。
第一阶段我们整体规划了自动化的报告形式,通过守护APP+WEB的形式实现了人性化的报告输出、错误的监控和与缺陷平台的无缝对接,方便测试人员查看与分享报告并追溯Bug来源。
第二阶段
第一阶段完工之后,我们发现脚本方面(主要是自动化脚本)还存在问题。我们会遇到这样一些情况:
同事A负责某个模块的自动化,但是他今天请假了,他的代码要交给同事B做编译和修改工作,A使用ANT做编译,编码中可能有不规范的路径引用,当这个代码拉到B电脑上的时候就出现编译不过或构建失败的情况。
或者有一些脚本执行过程中依赖一些资源,比如文档测试,可能需要SD卡放固定的文件,或要求登录特定的账户产生一些操作。而这个脚本跑完之后没有进行有效清理,比如退出账户。这样下一个自动化的登录可能会受到一定影响,如果不退出上一个账户,下一个账户就登录不上,这样就影响了其他自动化,这也导致脚本容错性差。不稳定、自动化执行效果不好等问题(这个可以理解为产出不是特别明显。一个自动化脚本是为了测试目标APP,如果自己本身不稳定,整个测试也是没有多大效益的)。很多同事在写脚本的过程中,对一些登陆账户、删除文件的操作各自实现了一系列的逻辑,这就导致很多地方大家关注的点不一样,没有办法统一,而且不稳定。
还有一些前置条件依赖手工构造的,如:手工往SD卡下放文件、手动登陆账号,这些前置条件有些并没有实现自动化。还有一个最重要的:它依赖USB的连接。我们都知道,测试过程中,我们需要在shell中敲命令启动UIAutomator测试,而我们有一些不在办公室测试的场景,比如:场测,我们不可能每天搬一台电脑去外部做这样的测试,于是我们希望能够脱离PC执行。
针对这些情况我们也给出了一些解决方案。
我们引入了Google出的代码审核工具Gerrit来做测试代码审核,引入Jenkins做Lint扫描和统一编译,把这些平台编译好的包统一发布到web端。我们还将基础操作封装为类库,要求运行时资源打包进行脚本执行文件,然后运行的时候释放。一个脚本执行完成之后,必须对更改的东西做有效清理,比如更改的设置、更改了账户,必须做退出操作,还有用守护APP调用脚本来脱离USB连接。
第二阶段的整体架构图如上。首先规范整个编译的描述文件,然后打包一些资源,由Gerrit服务器做代码的审核并扫描,再由Jenkins做编译,然后打包上传,最后由守护APP负责拉取这些脚本去调用执行。
在第二阶段解决了脚本的依赖性、编译、不稳定等问题,主要是通过一些审核、预编译等操作来提高脚本的稳定性。最重要的一点是我们可以脱离USB线缆去执行测试。
第三阶段
第三阶段首先要解决的是配置问题,我们内部有不同的机型,有一些机型可以共享代码,但是也有很多是不能共享的,比如:有的机型有NFC 这个设置,而有一些机型没有这个设置,所以代码编写人员要写逻辑判断来排除某些用例。不同机型执行的时候脚本执行参数可能不一样。比如早期我们需要根据机型对一些用例做一些特殊处理,通过脚本传递一些参数进去,让它做一些特殊的筛选工作。我们有一些基于Flyme的固件,也有一些基于YUNOS的固件,不同的固件会有一些UI上的差异,所以可能同一个应用我们却要用不同的脚本去适配不同的固件和机型。
早期的做法是依赖脚本编写人员在代码里做大量的IF、ELSE判断,判断特定的设置是否存在。但是这样的配置对我们来说很繁琐,比如今天突然来一个新机型做自动化测试,需要很多人修改代码,加上这个机型的判断、给它制造一个新的场景,这些都是很耗费时间的工作。
针对这个问题我们也有一些解决方案。
从设计模式的角度来说,有一个办法是把变化的部分抽离出来做封装,我们通过提供模块配置做这样的抽离工作。首先我们引入一个模块的概念,这个模块就是对应APP的脚本测试集,模块里包含针对不同场景(固件、机型等)的用例。我们又引入“仓库”的概念对应一个模块集,这个仓库是不同的脚本集,比如Flyme4、Flyme5或者一些特定场景测试。
这是我们第三阶段的仓库图。主要是脚本用例划分的结构,某一些用例差距比较大就单独拉出来做新的用例。
这个界面主要是我们内部用例配置界面。在我们内部有一些不同的用例,一些用例适配Flyme一些适配YunOS,针对不同的用例和机型我们可以通过调整“适配机型”和“用例选择”这两个参数来抽离配置,比如机型A的Flyme版可以执行全部用例而YUNOS版只能执行其中第10到第15条用例,就可以通过用例选择解决这样的问题。最终配置完成后界面会根据配置生成一串带有参数的命令。
下面还有两个需要SD卡和SIM卡的参数,主要是解决测试过程中有一些自动化测试的依赖场景问题,比如文档需要SD卡存在、短信电话需要保证SIM卡存在。这两个参数主要的作用是执行过程中约束这样的条件,比如达不到的时候会给出弹框或提示。
第三阶段主要通过一些模块的配置来解决不同机型和版本之间的依赖问题,通过参数扩展脚本的适用性。在这个阶段之后,我们不需要在脚本做逻辑判断来适应机型。有了新机型我们可以在刚才的页面配置上勾选几条用例,然后确定就可以了。
第四阶段
第四阶段主要是要解决自动化的“自动”问题。前几个阶段完成后报告收集没有太大问题,但是内部执行的时候,经常一天有很多设备或固件做自动化测试,而每个人手上的设备有限,A可能只有MX5、B只有MX6,脚本执行的时间有长有短,某一个设备跑完了就闲置浪费,我们也不好协调这种资源的分配。
此外,升级固件也需要手工来处理,测试一个固件时我们需要手动拷贝固件到设备然后操作设备进行固件升级,这也会浪费很多时间。有时候有些人可能做很多机型的自动化测试,就需要拉这些固件一个个去升级、测试,这样很繁琐很浪费时间。
我们的守护APP执行过程中主要的操作是:选择某一个模块点击执行操作,这个阶段的执行也是手动的。
另外,我们失败的用例需要人工确认,这个情况相信大家都遇到过。比如一部分用例失败了,可能是因为初始条件没有达到或是UI卡顿而导致的,如果能重复执行这些用例很大程度上会减少这种场景下的失败数量,早先这个重复的操作是由我们人工完成的,我们希望能以自动的形式帮我们完成这部分工作,让失败的用例重新再跑一遍。
针对这些问题的解决方案,就是比较抽象地提出了“设备池”的概念,把这些设备集中在一起,统一供电、统一管理,在每一个设备上装上我们添加了推送功能的APP,进行任务分发和固件升级工作。Web端从一个单纯的报告展示页面改造为任务中心,不单单只是展示报告,也可以创建任务,管理任务、查看任务状态等。我们把客户端和WEB端通过Push连接起来,基本得到完全自动化的调度平台。对一些执行错误的用例我们尝试通过平台重新分发,很大程度上避免了因UI控件找不到而引起的用例失败。
第四阶段我们整个平台的架构主要分为两部分:客户端和服务端。客户端有一部分本地调度的逻辑,因为它需要一些脱离平台执行的场景,比如场外的测试,它可能没办法连接到内部的平台。客户端还包含一些系统组件,如录制屏幕、崩溃监测、LOG收集、以及通讯的组件。
在这个阶段我们也对UIAutomator和Monkey做了一些改造。比如前一个阶段有一个用例选择的配置项,即选择部分用例执行,这个功能可以通过调整UIAutomator执行参数实现,但我们觉得这可能不是很合理,在改造的过程中我们专门为这一系列脚本和工具端的通讯需求提供了AIDL接口,我们内部把这个负责UIAutomator和工具通讯的组件叫UIBridge。在平台方面按功能主要分成在线列表、任务管理、模块管理和报告展示等。
第四阶段我们实现了完全无人工干预的自动化。实现了这点就有了上下衔接的可能,比如编译出来固件,可以通过持续集成全自动的进行固件的稳定测试、回归性验证等工作。同时也实现了资源的有效整合,以前做手工测试的时候,10个模块准备5台设备,需要一个下午的时间做整体测试,而现在有了自动调度逻辑,可能只需要两个小时就足够了。
第五阶段
在趋向稳定之后,我们也做了不同类型的测试,比如基于Monkey的稳定性测试、性能测试、升级包测试、安全测试等。自动化执行一段时间之后,我们收集了很多脚本用例方面的数据和崩溃的数据,也通过图表对这些数据做了一些趋势的分析。
不同于原来的只有UI自动化,第五阶段我们用了“任务类型”的概念,为了适配这些稳定性测试和压力测试的场景,我们通过区分任务类型,在APP端执行不同逻辑来收集不同的报告,然后归类,上传到web端展示。
1. 稳定性测试
有时候我们需要对一个固件进行7×24小时的持续稳定性监测,同时希望控制整体时间和初始的种子,对于运行崩溃或无响应问题,做到持续监控和收集,对执行期间APP的基本性能信息进行监控。基于这些要求,我们做了一些稳定性测试的功能。
上图是我们执行稳定性测试过程中的报告,会展示一些具体的执行命令。下面的时间轴展示执行过程中哪些时间点出现崩溃、无响应或重启等信息。这只是具体崩溃的截图,是整个页面的一部分,比如某个时间点发生了崩溃,下面就会有具体时间点的截图,可以在这个截图的控件上找到它崩溃的Log信息。还有一个图表来展示整体的CPU内存曲线以及FPS变化曲线。
有些人可能觉得只有崩溃信息是重要的,但是我们把崩溃信息和性能信息联系在一起,并经过一段时间的收集之后发现,不仅是崩溃异常栈,一些性能的信息展示对具体问题场景的分析也很重要。比如我们发现一些APP在无响应之前会有比较高的内存占用,或者在崩溃之前会有一些异常的波动信息。
这是具体查看崩溃Log的页面,这里可以看到崩溃的异常栈。有了这个页面之后收集崩溃信息就很方便,不用再专门用Logcat查看。
2. 性能测试
性能是一个比较难讲的问题。举个例子:一个固件,我们只针对某一个指标高低是否就能判断固件的性能问题?应该是不能的,我觉得只有对一系列指标的整体对比才能正确的描述这类问题,比如通过API获取系统启动时的一些信息。本次启动用了多少秒?系统启动时有多少线程和进程?这些线程和进程的内存占用是多少?系统整体内存占用多少?常用应用需要多少毫秒完成冷启动,这些数据跟上几个版本有怎样的差异?
这是性能测试的具体页面,可能有时候这个曲线的波动会稍微大一点,但是整体趋势基本是这样。我们针对某一个系统常驻的进程,跟踪他的线程数量,可以在早期发现一些性能问题。比如开发写了一个新的功能,我们可以通过这个曲线的变化为新功能的性能信息提供参考数据。对于多个版本持续的指标升高或者降低我们也会提供额外的预警信息。
3. 升级测试
我们现在主要的测试对象是以固件为单位。这个固件会针对不同的机型编出来,所有机型加起来可能有几十个。这些固件有很多增量包,我们需要对整包和增量包的质量做监控,比如检查收集升级前后它的基带是否符合预期,Recovery、kernel版本是否匹配,同时评估升级的耗时。
现在固件特别大,一个基本上都是上G的体量,这个工作起初都是需要人工去检验的,后来机型越来越多、固件越来越大,人工去做会比较繁琐,我们就做了这样一个工具来检查。上图展示的是检测具体增量包和整包的升级结果。
4. 安全测试
前段时间有些APP爆出来端口开放问题,为了通讯方便,一些APP没有用系统的AIDL接口,而使用HTTPRPC这样的协议通讯。这就导致同局域网的人可以利用这些RPC接口做一些比较危险的操作比如读取隐私数据、打电话等。针对这样类似的情况我们做了这样的尝试,监控系统主要组件的暴露情况,监控一些端口的开放以及使用特殊的intent触发组件,检查会不会暴露出来特定的敏感信息等,然后把这些信息推送到平台,形成一些开发课题或者安全缺陷告警。
这是安全测试的页面。我们会收集某些特定的APP版本信息并做一些基础的安全性展示,包括:它暴露哪些组件、这些组件是什么,是否开启debug等。
第五阶段我们主要做的工作是专项测试,我们觉得专项测试在现在的场景下是比较重要的,能有效补充UI自动化的不足。UI自动化是通过关键点的点击去检查不足,而专项测试可以从不同角度检查产品是否存在问题,包括性能方面、流畅度和安全方面等。
魅族的ATS平台
魅族自动化测试发展的5个阶段都是在我们内部的ATS平台上产生的。我们在整个自动化的研究过程中遇到了各式各样的问题,比如最开始的初始条件和脚本稳定性,到后来需要集中资源,再后来需要专项测试,我们希望有一个统一的测试平台来完成这些工作,所以就开发了ATS,它整合了UI自动化测试,压力测试、性能测试等。
我们也通过一些配置和调度功能去降低UI自动化的使用成本。以前UI自动化只有特定的代码编写人员才能使用,平台完善之后基本上整个测试体系的人员都可以通过平台对一些特定的APP做特定场景的测试,比如UI测试、稳定性测试和性能测试等。在测试的过程中,我们也把整个测试的数据做了一个有效的收集,比如我们通过形成各个阶段的流畅度、性能报告,向不同的测试和项目经理展示项目的质量情况。
这是ATS平台的截图。ATS平台主要分为在线设备列表、任务管理。调度计划是我们通过衔接整个流程去达到固件产出后自动执行自动化的功能点。还有一些模块管理和其他类型的报告,比如升级测试、稳定性测试等,还有一些收集缺陷的功能。
这个界面是具体的在线设备的展示。我们会定时收集设备的状态、电量等,方便用户了解设备的状况。
自动化过程中遇到的问题
我们经常遇到的问题是:有一些控件是通过API找不到的,比如一些状态栏上的控件,还有一些WEBVIEW,无法有效地从WEBVIEW中选择到控件,API基本上达不到预期的效果。
从我们的角度理解,有很多这样的操作是没有办法完成自动化,或没有办法按照我们预期的成本去实现自动化的。有一些解决方案不是最优却更合理,比如一些控件,其实我们的目的只是检查它到底存不存在(从可见性上讲),虽然我们可能拿不到控件,但是我们可以用预期的图像与当前做对比,这样也能达到测试的目的。
现在我们面临的另一个问题是内部整个平台流程的流通都是依赖WIFI传递信息,但是很多公司会出现信道干扰严重、固件尺寸越来越大、WIFI质量特别差的情况,在做固件的下载或特别大的资源包的下载工作时,这些情况就会严重困扰到我们。我们采用了另一种方式:通过ADB的命令把PC端的端口映射到设备上,下载的过程就可以通过USB线来传递,很大程度上解决了这方面的问题。
我们当然没有办法把所有用例都实现自动化,比如多设备之间的交互和验证码的问题。验证码有办法实现,但成本比较高。对于这方面的问题,我们认为既然有些东西没办法实现自动化,那就干脆不要做自动化。自动化要尽量保持简洁、稳定、高效,做了大量的工作去实现而稳定性却不好,这样的尝试是得不偿失的。
多设备的交互,我们内部也有一些用例会涉及。例如手机A给手机B发短信、打电话的场景,我们有了推送之后,可以点对点的将命令从A推送到B,这样A和B之间便可以协调起来验证一些电话或短信功能。当然这样的场景是很简单的的,具体到一些复杂的多设备交互场景,自动化可能难以低成本的去实现。
UI自动化的理解
UI自动化无法替代人工,受限于现有的技术和测试复杂度,很多地方没有办法用UI自动化。它的容错性也差,辛辛苦苦写了UI自动化脚本又要根据开发的变化时时维护。另外,除了回归验证以外,收益不高,UI自动化的介入基本上在大量的人工测试之后,能发现的问题并没有想象的那么多。很多人对UI自动化的期望是:UI自动化能代替人工作,就不用人工去做了,只要写一个自动化脚本就可以了。这是不可能的,它发现的问题并不是很多,不能期望UI自动化帮你做太多事情。
让“自动化”自动化,意思是在实现整体的自动化之后,我们希望自动化可以为上游的固件产出提供测试接口,把整个流程连接起来,为下游报告提供一系列的统计分析结果,为产品质量提供有说服力的数据。
很多公司可能在自动化上还要人工做一些干预,比如一些测试的预置条件需要人工干预。自动化如果不能完全自动起来,需要人工干预,需要人来执行,那就不是成功的自动化。一个理想的自动化就像一个黑盒子,数据从这里流入流出,为我们提供一些信息和质量数据。
UI或专项测试做完之后,我们现在的方向是通过底层的东西了解系统的问题。安卓发展到今天,应用质量趋向于稳定,很少有线上APP崩溃的体验了,但是不崩溃并不代表它已经做到足够优秀的程度了。我们希望通过调用栈的信息去获悉耗时和卡顿的具体原因,或对流畅度、资源占用做分析。
将来,我们能在专项测试上做得更好。我们之前做了一些专项测试,包括安全、稳定性等,但也有很多专项测试还没有做,比如现在APP耗电严重,我们希望能在我们的层面评估一个APP到底耗电多严重?为什么这么严重?为什么不流畅?还有哪些安全问题我们没有分析和挖掘出来?……我们希望能做进一步的研究和探索,让APP质量更好,让用户体验更好。 |