移动无线应用专项测试浅谈
前言
其实去年年底我就说过很多公司功能测试都做的差不多了,接下来就开始折腾什么性能测试啊,安全测试啊,持续集成啊,Hybrid
啦等等。果不其然,最近很多测试同学开始问我性能相关的问题。当然我们专业点来讲这个叫做专项测试
,那么专项测试其实也是区分什么人去做,工具组的人也在做,业务组的人也在做,只不过大家做的切入点会很不同。也许很多同学也比较好奇我毕竟也去那么多公司撕逼了,到底我平时在做什么,怎么做的。这边那就简单说下吧。
做了什么
嗯,我想想我做了什么。其实我现在就是,公司做到移动无线的应用专项测试就会想到我。然后之前一年我一个人做了持续集成,BDD,功能回归自动化,接口测试,静动态扫描,从客户端发起的接口测试等等。其实到现在为止我还是觉得我不知道我怎么走上专项测试这条路的,貌似某一天公司说我来做就变成我来做了。
怎么做
其实别的先不说吧,我们先来说下专项吧。专项这个东西关键在于几点
怎么手动获取数据
怎么自动化获取数据
怎么分析数据
怎么定位问题
怎么优化
当然我这里还是要吐槽一句,大家醒醒吧。花个几年去研究UI 功能的自动化有意义吗?要学的东西太多了,还是醒醒吧。其实很多同学关键是上面我说的这些都不知道,而且不仅如此,还不知道的有
我什么阶段去做
每个阶段做什么
做到什么颗粒度
怎么才算完成了
。。。
也许还有很多,我也不想列了。其实我想说的是,其实也没有什么绝对,还是看你的团队,看你的项目,看你这次的目的,看项目阶段等等。不同的时间都是不同的策略。好吧,我们一个一个来吧。
什么阶段去做,每个阶段做什么,做什么颗粒度
如果你有时间,并且项目是初级阶段。那么按照周为单位需要去做一次专项评估。那么这个过程中你至少要mvn
或者gradle
或者pod install
等都success,否则搞个毛线。那么这个过程中需要去根据本周代码的修改,从业务和技术角度去给出专项数据。这个数据到底给什么,是根据这次新功能的定位,这个产品的定位来定的,不要去想着有什么固定模式。比如腾讯qq和支付宝这两个产品无论怎么样,在专项的技术上面不会差很多,但是专项测试想到达到的目的和测试的场景肯定是天差地别的,明白?接着随着功能完善之后,最终还是需要做一次类似于所有模块集成之后的专项测试,记住,颗粒度,范围,数据怎么获取,完全根据测试这个owner的策略来。
反过来,如果你没有时间。那么在success的基础之上,你可以在功能完全完成之后直接来做一次集成专项评估。虽然效果不会差太多,但是这个方式的弊端在于,如果有问题,可能开发修改的时间就会很少,而且专项测试本身消耗的时间就会很长,所以最好是循序渐进,而不是集成之后去做,往往时间来不及。
真实场景模拟
比如这次有一个新项目,刚开始的项目,然后代码编译都ok了。那么作为新项目而言,我们专项其实有很多,但是又不可能都做,这个受限于你的团队的大小,以及功能的完整度。那么可能先做最关键的,比如CR,比如功能体验路径的对比,比如内存消耗的对比,比如不同网络下的数据对比,那么这些都是相对一个移动应用来讲最最重要的。剩下的可以在之后的迭代中陆续去评估掉。颗粒度的话,还是那句话,目前除了电量以外,剩下的数据基本上都是可以通过各种方式(插桩,越狱,调用原生API,Hook等)拿到的。
怎么才算完成
这个的确是一个比较逼人的问题。所以我才会说评估不仅仅是通过打出来的apk或者ipa来做的。而是在在项目迭代中持续去做的,那么直到功能完成度100%的时候就差不多可以做一轮完整的。那么问题又来了,一般应用都还会继续去改,怎么办呢?所以我说要CR啊。要结合业务重要性,功能重要性,代码的修改来一起评估每次修改所造成的影响。我们不可能每次都去做一次专项,所以这个是必须会的技能。
好了,这些解释完了。那么我们继续来看最最上面我提到的专项的关键点怎么办。这个我就拿我在西安写的keynote为例子吧。
Monkey
好吧。这个不是我,是工具。我先澄清下。
使用不同的策略:其实就是根据自己的策略(各种操作比重不同)来制定脚本,包括也可以简单的二次开发,现在流行的做法就是去读取当前所有的Views,然后去做遍历,保证monkey可以在每个Activity上面都执行的到。
使用不同渠道商的脚本:现在各个渠道商都是有自己的monkey脚本来做测试的,如果不通过那么一样耶会被退回来,那么与其这样,不如提前去做。
修复所有的bug:那么这个就是标准了,0 crash和 0 ANR。这两个都是不允许的。
内存
这个其实也是很重要的一个数据。那么我们在做之前首先先要来关注每个机器的OS给每个应用分配了多少内存占用量,否则你怎么知道数据是大是小呢。
这里提供的这个方法是我推荐的,原因是它给出的数据是个规则的矩阵,容易去做分析。但是也不是所有机器都可以去使用这个命令的,如果提示不能使用,大部分情况就是因为没有procrank
这个文件,自己Google下去下载一个push到手机里即可。当然没有root的貌似应该不行。
当然我们也还有别的方式。
当然我们还有别的方式,比如adb shell dumpsys
这个命令也可以拿到比较全的内存信息
启动性能
启动性能分这两种,毕竟有缓存和没有缓存差别很大。那么问题来了,虽然类型就两种,但是纬度很多。
Native启动时间
通过插桩或者grep ActivityManager或者ActivityLauncher都ok,当然更细节的话应该还有别的方式。
Hybrid启动时间
这个详见我QCon的PPT。这个插桩的地方就非常多了才能够很准确的去获取到
业务功能对应的网络消耗时间
这个也是非常重要的。我们不仅仅要关注时间长短,更要关心每个业务到底要调用多少个接口,其中css,js,png等是否根据网络做了不同策略的调整,是否被压缩了。时间数据仅仅是最终的一个展现。尼玛,今天还有人和我说公司的东西我不敢放,老子就放了这次。类似于
流量
到流量了。也是几种方式吧
第一种通过ddms,具体怎么用我就不教了,我不喜欢手把手。
第二种就是从OS中去获取。和新方法如下:
String rcvPath = "/proc/uid_stat/" + uid + "/tcp_rcv";
String sndPath = "/proc/uid_stat/" + uid + "/tcp_snd";
当然未必一定要用java,可以用shell或者python等脚本直接拿出来都可以啦。
第三种就是通过直接去调原生的接口来获取,其实和ddms是一样的道理。
@SuppressLint("ShowToast")
public void getAppTrafficList() {
PackageManager pm = getPackageManager();
List<PackageInfo> pinfos = pm
.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES
| PackageManager.GET_PERMISSIONS);
for (PackageInfo info : pinfos) {
String[] premissions = info.requestedPermissions;
if (premissions != null && premissions.length > 0) {
for (String premission : premissions) {
if ("android.permission.INTERNET".equals(premission)) {
int uId = info.applicationInfo.uid;
long rx = TrafficStats.getUidRxBytes(uId);
long tx = TrafficStats.getUidTxBytes(uId);
if (rx < 0 || tx < 0) {
continue;
} else {
Log.e(info.packageName.toString() + "Traffic",
(rx + tx) + "kb");
}
}
}
}
}
}
第四种就是架代理了。抓包去获取流量大小和网络数据。
当然还有最后一种,也是很重要的一种,那就是tcpdump
获取数据和wireshark
来分析。具体不在这里做教导了。
应用占用量
随着现在应用使用频率越来越高,应用发布时候的大小也许还看得过去,但是用户用着用着就不堪入目了。所以应用占用量的增长也是我关注的点。
我编写了一个应用来监控被测应用的三个数据的大小。核心代码:
@SuppressLint("NewApi")
public void queryPacakgeSize(String pkgName) throws Exception {
if (pkgName != null) {
PackageManager pm = getPackageManager();
try {
Method getPackageSizeInfo = pm.getClass().getDeclaredMethod(
"getPackageSizeInfo", String.class, int.class,
IPackageStatsObserver.class);
getPackageSizeInfo.invoke(pm, "<package name>",
Process.myUid() / 100000, new PkgSizeObserver());
} catch (Exception ex) {
Log.e(TAG, "NoSuchMethodException");
ex.printStackTrace();
throw ex;
}
}
}
然后我们就可以看到一排一排的日志啦
电量
现在的确也有三种方式
1.功耗仪(安捷伦)
精准度最高,但费用消耗庞大,并且使用不方便。无法做自动化
2.结合cpu等各种数据最终计算出电量消耗,单位是mA
精准度不如功耗仪,这个公式我这里就不能给出了
3.通过消息的方式获取。
精准度最低,单位是%。核心代码
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
int status = intent.getIntExtra("status", 0); //
if (status == BatteryManager.BATTERY_STATUS_CHARGING)
else
}
};
CPU
和启动性能一样,也有两种。活动状态和静默状态
内存泄漏
据不可靠消息,stackoverflow上面其实有人已经说新版本的Android OS monkey就算增加这个参数在/misc/data 下面也不会有了。这个我还在进一步验证中
那么hprof
文件主要来源就是ddms了。但是为什么一定要强调场景,是因为我们单独去拿这个数据也没有什么太大意义。MAT分析的时候一般都是做diff的对比。而这个diff的对比必须基于场景之上。
GPU过渡绘制
我们在测试之前总要知道标准吧。
同样的,会根据不同场景进行测试和分析。一方面是和自己比,一方面是和竞品去比。一旦有超出标准的,那么继续跟到代码去分析。
gfxinfo
同样的需要去根据场景来分析。由于这个功能是动态的。所以我们需要去挑选场景。比如listview的场景,比如有tabbutton的场景,也就是说用户在做滑动啊,滚动啊,切换界面等操作时候进行动态的数据的获取。
systrace
基本上是被我放弃的东西了。由于限制比较多。不过效果还是很不错的,也能够发现比较深层次的问题
IO性能
这个需要自己做一定的二次开发了。IO性能对App整体性能提升会很大。最高可以达到30%之多。
Android主要使用的是Xposed
,主要是hook被测应用中针对的方法,而iOS的话在越狱手机上去使用IOStringBuffer
这样的方法去监控IO的头文件,进行分析。无详细案例
iOS
iOS的话我其实也就不想多说了。其实就两个图
![](http://testerhome.com/photo/2015/37332600f6efc06a77674bd59b788cf2.png)
自动化
最后还是来说下自动化吧。其实最早我开源过一个python的,被吐槽的一塌糊涂。后来现在又重新造轮子造了一个java的。目录如
其实方式很简单,就是把我们所有能够通过命令方式抓取的到的数据全部封装起来,通过一个多线程的方式一边在跑的时候一边读取数据一边写文件即可。
Monitor代码如下:
public StartPerformaceMonitor(String filePath, String sn, String PackageName) {
this.sn = sn;
this.PackageName = PackageName;
this.filePath = filePath;
}
public void stop() {
this.isRunning = false;
}
@Override
public void run() {
cpu_index = CpuInfo.getCpuIndex(this.sn);
vss_index = VssInfo.getVssIndex(this.sn);
rss_index = RssInfo.getRssIndex(this.sn);
String anyproxyData = "";
try {
anyproxyData = AnyproxyInfo.StartCommand();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// TODO Auto-generated method stub
while (this.isRunning) {
try {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// anyproxy
anyproxyData = AnyproxyInfo.getALLData();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
resultList.put("anyproxyData", anyproxyData + "");
// traffic
String traffic = TrafficInfo.getTrafficData(this.sn,
this.PackageName);
resultList.put("traffic", traffic);
System.out.println("应用网络消耗流量:"
+ traffic.substring(0, traffic.length() - 2) + "kb");
// totalsize = cachesize + datasize + codesize;
String[] size = AppSizeInfo.getAppSizeData(this.sn,
this.PackageName);
resultList.put("cachesize", size[0]);
resultList.put