作者:彭海波
前言
Android App的性能测试是移动测试过程中必不可少的一个环节。在我们项目组内,性能测试的过程是这样的,先设置测试场景,然后一边手工执行场景,一边通过工具获取性能数据,为了减少误差,一个场景一般重复执行3-5次,测试完后将各种性能数据整理成报告。这个过程如果是一个经验丰富的测试工程师去做,可能要花半天到一天时间,而如果是一个新人去做,甚至可能要花两天时间。而且有时候还会遇到,刚测完,开发说优化了性能,又需要重新测试。这还只是一个Android端的时间,如果再加上iOS端,时间加倍。对于这样一个现状,我们急需探索出一种自动化的方式提高性能测试的效率。本文接下来将要介绍云测试平台在自动化性能测试方面的探索和成果。
需求分析
作为一个负责任的测试开发工程师,我们首先要搞清楚需求,即产品线的性能测试到底要怎么测,要测哪些数据。于是,我跟着参与了产品线的性能测试方案及标准的讨论。了解到的性能测试需要采集的数据有:1.响应时间;2.耗电量;3.CPU,内存消耗,网络流量;4.渲染帧率。而这几组数据需要覆盖的测试场景大概有16种,如下表所示。
||响应时间|CPU|内存|耗电量|网络流量|流畅度|
|-|-|-|-|-|-|
|首次APP启动到首页加载完成|yes|yes|yes|no|yes|no|
|生活-缴费|yes|yes|yes|no|yes|yes|
|生活-查违章|yes|yes|yes|no|yes|yes|
|生活-好医生|yes|yes|yes|no|yes|yes|
|生活-添加房|yes|yes|yes|no|yes|yes|
|生活-添加车|yes|yes|yes|no|yes|yes|
|生活-添加随手记|yes|yes|yes|no|yes|yes|
|侧边栏页面加载|yes|yes|yes|no|yes|yes|
|生活缴费-收银台功能|yes|yes|yes|no|yes|yes|
|查违章缴费-收银台功能|yes|yes|yes|no|yes|yes|
|首页滑动|no|no|no|no|no|yes|
|Tab页切换|no|no|no|no|no|yes|
|生活页面滑动|no|no|no|no|no|yes|
|理财页滑动|no|no|no|no|no|yes|
|App登录状态静默30分钟|no|no|no|yes|no|no|
|App非登录状态静默30分钟|no|no|no|yes|no|no|
方案调研
UI自动化与性能测试相结合
在Android端,获取性能数据的方式有很多种,可以通过开源工具采集,可以通过adb命令获取,也可以调用系统API获取。各种方法采集的数据基本差别不大,重点是场景操作和数据整理比较麻烦。既然要提高测试效率,那最好的方式肯定是通过自动化来解决。自动化的框架有很多种,这里就不一一做讨论了,我们采用的是Appium框架,但是做了二次封装和改进,易用性更好。关于云测试平台UI自动化功能的使用方法介绍,大家可以参考云测试平台帮助文档一文。
测试方案
对于CPU,内存,FPS,流量这几组数据,云测平台早就可以提供测试了。通过一个与UI自动化并行的线程来发送adb命令获取应用的性能数据。我们需要解决的是如何设置性能测试的起点和终点,还有一个场景多次重复测试的问题。
那么我们需要想办法测试响应时间,我们采用的方案是设置一个待加载页面的基准控件,利用Appium的元素查找功能一直循环查找,一旦控件找到了,则认为页面加载成功,这中间的时间差即为页面响应时间。这个时间虽然没有代码插桩准确,但误差范围基本控制在100ms内,对整体测试结果的影响不大。
方案实施
开启一个性能测试的线程
关于获取方式前面也介绍了,话不多说,直接上代码:
@Override
public void run() {
// TODO Auto-generated method stub
this.running = true;
while (running) {
String time = String.valueOf(System.currentTimeMillis());
time = CalendarDate.GetCurrentTime();
//获取内存数据
int [] memArray = AndroidPerformanceTools.getMemoryInfo(androidPerformance.getPkgname(), androidPerformance.getDevice());
int totalMem = memArray[0];
int appMem = memArray[1];
//获取CPU数据
int cpuUsage = AndroidPerformanceTools.getCPUInfo(androidPerformance.getPkgname(), androidPerformance.getDevice());
//获取FPS
float fps = AndroidPerformanceTools.getFPSInfo(androidPerformance.getPkgname(), androidPerformance.getDevice());
//获取流量数据
long [] trafficArray = AndroidPerformanceTools.getTrafficInfo(androidPerformance.getPkgname(), androidPerformance.getDevice());
long totalTrffic = trafficArray[0];
long recTraffic = trafficArray[1];
long sndTraffic = trafficArray[2];
//数据初始化
if (this.androidPerformance.getAndroidPerformanceData().getInittotal() == -1
&& totalTrffic > 0) {
this.androidPerformance.getAndroidPerformanceData().setInittotal(totalTrffic);
this.androidPerformance.getAndroidPerformanceData().setInitrec(recTraffic);
this.androidPerformance.getAndroidPerformanceData().setInitsnd(sndTraffic);
}
//汇总数据
MemInfo memInfo = new MemInfo(time, totalMem, appMem);
FPSInfo fpsInfo = new FPSInfo(time, fps);
CPUInfo cpuInfo = new CPUInfo(time, cpuUsage);
TrafficInfo trafficInfo = new TrafficInfo(time, totalTrffic, recTraffic, sndTraffic);
this.androidPerformance.getAndroidPerformanceData().getCpuinfolist().add(cpuInfo);
this.androidPerformance.getAndroidPerformanceData().getMeminfolist().add(memInfo);
this.androidPerformance.getAndroidPerformanceData().getTraffinfolist().add(trafficInfo);
this.androidPerformance.getAndroidPerformanceData().getFpsinfolist().add(fpsInfo);
//设置采集间隔时间
try {
Thread.sleep(this.sleepTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
设置开始和结束的点
由于UI自动化的执行每次都是从启动App开始,但性能数据的收集却是针对具体某个场景的,比如进入生活缴费页面。因此为了方便设置性能测试的开始和结束时间,我们在UI自动化脚本的录制action中加了两个字段:startPerformance和stopPerformance。用户可以在脚本的任意位置加入这两个action,但主要先后关系,而且需要配对使用。当我们在执行脚本的时候,遇到startPeformance,就启动性能测试线程,然后遇到stopPerformance,则停止线程,并保存数据。一个完整的用于做性能测试的脚本如下所示:
重复执行
对于有些场景,我们为了减少误差,通常需要重复执行多次取平均值。因此,我们在新建测试任务的时候,增加了设置重复次数的参数。测试任务会根据设置的参数,重复执行相应的次数。
响应时间的测试
页面时间是通过计算开始加载到某个关键元素出现的时间差来作为衡量的,具体实现方式如下:
@BeforeMethod
public void beforeTest() {
createDriver();
stepStartTime = System.currentTimeMillis();
Log.logger.debug("hello");
}
@Test
public void runStep(){
stepTestTime = System.currentTimeMillis() - stepStartTime;
if(loadingtime == 0)
loadingtime = stepTestTime;
//设置结果
if(task.getTasktype().equals("Android 性能测试")){
JSONObject stepResult = new JSONObject();
stepResult.put("uielement", testStep.getUi_element().find_method_value);
stepResult.put("stepname", testStep.getUi_element().name);
stepResult.put("steptime", stepTestTime);
stepResult.put("stepresult", "success");
stepResultList.add(stepResult);
//taskService.setTaskResultInfo(taskId, stepResultList.toJSONString());
}
}
数据汇总与统计
性能测试结束后,我们将数据汇总并进行简单的统计,最终写入文件,保存最为测试报告的数据支持。这里简单列举下CPU的统计代码:
ArrayList cpuInfos = androidPerformanceData.getCpuinfolist();
JSONArray cpuTimeJsonArray = new JSONArray();
JSONArray cpuDataJsonArray = new JSONArray();
double avgCPU = 0.0;
long total = 0;
for (CPUInfo cpuInfo : cpuInfos) {
total += cpuInfo.cpuUsage;
cpuTimeJsonArray.add(cpuInfo.time);
cpuDataJsonArray.add(cpuInfo.cpuUsage);
}
avgCPU = total/cpuInfos.size();
成果展示
整体性能报告
详细性能报告
详细性能报告主要显示每次执行的响应时间结果,CPU,内存,流量消耗以及FPS的分布曲线。这里简单列举其中两项数据的截图如下。
总结
至此,我们通过UI自动化与性能测试结合的方式解决了Android App自动化性能测试的问题。而且从脚本的录制,到测试执行,报告的生成都已经平台化。测试人员只需要录制非常简单的脚本,即可一键得到相应的性能测试报告。当然,这种方式也有其缺点,比如响应时间的测试有些小误差,录制脚本有一定的学习成本。这些都是我们未来要优化的方向,欢迎大家提出改进建议。