标签:
前言:
最近开始研究Android自动化测试方法,对其中的一些工具、方法和框架做了一些简单的整理,其中包括android测试框架、CTS、Monkey、Monkeyrunner、benchmark、其它test tool等等。因接触时间很短,很多地方有不足之处,希望能和大家多多交流。
一、 什么是Monkey
Monkey是Android中的一个命令行工具,可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试。Monkey测试是一种为了测试软件的稳定性、健壮性的快速有效的方法。
二、 Monkey的特征
1、 测试的对象仅为应用程序包,有一定的局限性。
2、 Monky测试使用的事件流数据流是随机的,不能进行自定义。
3、 可对MonkeyTest的对象,事件数量,类型,频率等进行设置。
三、Monkey的基本用法
基本语法如下:
$ adb shell monkey [options]
如果不指定options,Monkey将以无反馈模式启动,并把事件任意发送到安装在目标环境中的全部包。下面是一个更为典型的命令行示例,它启动指定的应用程序,并向其发送500个伪随机事件:
$ adb shell monkey -p your.package.name -v 500
四、Monkey测试的一个实例
通过这个实例,我们能理解Monkey测试的步骤以及如何知道哪些应用程序能够用Monkey进行测试。
Windows下(注:2—4步是为了查看我们可以测试哪些应用程序包,可省略):
1、 通过eclipse启动一个Android的emulator
2、 在命令行中输入:adb devices查看设备连接情况
C:\Documents and Settings\Administrator>adb devices
List of devices attached
emulator-5554 device
3、 在有设备连接的前提下,在命令行中输入:adb shell 进入shell界面
C:\Documents and Settings\Administrator>adb shell
#
4、 查看data/data文件夹下的应用程序包。注:我们能测试的应用程序包都在这个目录下面
C:\Documents and Settings\Administrator>adb shell
# ls data/data
ls data/data
com.google.android.btrouter
com.android.providers.telephony
com.android.mms
com.android.providers.downloads
com.android.deskclock
com.android.email
com.android.providers.media
com.android.settings
jp.co.omronsoft.openwnn
com.android.providers.userdictionary
com.android.quicksearchbox
com.android.protips
com.android.browser
com.android.launcher
com.android.term
com.android.speechrecorder
com.android.server.vpn
com.android.defcontainer
com.svox.pico
com.android.customlocale
com.android.development
com.android.soundrecorder
com.android.providers.drm
com.android.spare_parts
com.android.providers.downloads.ui
com.android.fallback
com.android.providers.applications
com.android.netspeed
com.android.wallpaper.livepicker
android.tts
com.android.htmlviewer
com.android.music
com.android.certinstaller
com.android.inputmethod.pinyin
com.android.providers.subscribedfeeds
com.android.inputmethod.latin
com.android.gallery
com.android.systemui
com.android.contacts
com.android.phone
com.android.sdksetup
com.android.calculator2
com.android.packageinstaller
com.android.camera
com.android.providers.settings
com.thestore.main
com.android.providers.contacts
5、 以com.android.calculator2作为对象进行MonkeyTest
#monkey -p com.android.calculator2 -v 500
其中-p表示对象包 –v 表示反馈信息级别
运行过程中,Emulator中的应用程序在不断地切换画面。
按照选定的不同级别的反馈信息,在Monkey中还可以看到其执行过程报告和生成的事件。
注:具体参数的设定可参考:http://developer.android.com/guide/developing/tools/monkey.html
五、关于Monkey测试的停止条件
Monkey Test执行过程中在下列三种情况下会自动停止:
1、如果限定了Monkey运行在一个或几个特定的包上,那么它会监测试图转到其它包的操作,并对其进行阻止。
2、如果应用程序崩溃或接收到任何失控异常,Monkey将停止并报错。
3、如果应用程序产生了应用程序不响应(application not responding)的错误,Monkey将会停止并报错。
通过多次并且不同设定下的Monkey测试才算它是一个稳定性足够的程序。
monkeyrunner工具
一、什么是monkeyrunner
monkeyrunner工具提供了一个API,使用此API写出的程序可以在Android代码之外控制Android设备和模拟器。通过monkeyrunner,您可以写出一个Python程序去安装一个Android应用程序或测试包,运行它,向它发送模拟击键,截取它的用户界面图片,并将截图存储于工作站上。monkeyrunner工具的主要设计目的是用于测试功能/框架水平上的应用程序和设备,或用于运行单元测试套件,但您当然也可以将其用于其它目的。
二、monkeyrunner工具同Monkey工具的差别
Monkey:
Monkey工具直接运行在设备或模拟器的adb shell中,生成用户或系统的伪随机事件流。
monkeyrunner:
monkeyrunner工具则是在工作站上通过API定义的特定命令和事件控制设备或模拟器。
三、monkeyrunner的测试类型
1、多设备控制:monkeyrunner API可以跨多个设备或模拟器实施测试套件。您可以在同一时间接上所有的设备或一次启动全部模拟器(或统统一起),依据程序依次连接到每一个,然后运行一个或多个测试。您也可以用程序启动一个配置好的模拟器,运行一个或多个测试,然后关闭模拟器。
2、 功能测试: monkeyrunner可以为一个应用自动贯彻一次功能测试。您提供按键或触摸事件的输入数值,然后观察输出结果的截屏。
3、 回归测试:monkeyrunner可以运行某个应用,并将其结果截屏与既定已知正确的结果截屏相比较,以此测试应用的稳定性。
4、 可扩展的自动化:由于monkeyrunner是一个API工具包,您可以基于Python模块和程序开发一整套系统,以此来控制Android设备。除了使用monkeyrunner API之外,您还可以使用标准的Python os和subprocess模块来调用Android Debug Bridge这样的Android工具。
四、运行monkeyrunner
您可以直接使用一个代码文件运行monkeyrunner,抑或在交互式对话中输入monkeyrunner语句。不论使用哪种方式,您都需要调用SDK目录的tools子目录下的monkeyrunner命令。如果您提供一个文件名作为运行参数,则monkeyrunner将视文件内容为Python程序,并加以运行;否则,它将提供一个交互对话环境。
monkeyrunner的命令语法为:
monkeyrunner -plugin <plugin_jar> <program_filename> <program_options>
五、实例
以sample中的ApiDemos为例,先将其生成ApiDemos.apk。
前提:已有device连接
1、 将ApiDemos.apk放在$Android_Root\tools下。
2、 在$Android_Root\tools下新建一个monkeyrunnerprogram.py文件,里面内容为:
注意:SDK上的例子有些错误,不可直接复制,否则执行命令时会发生错误。具体可与我的上面这段代码对照。
3、 打开命令行转到Android_Root\tools目录下运行一下命令:
monkeyrunner monkeyrunnerprogram.py
110307 15:33:19.625:I [main] [com.android.monkeyrunner.MonkeyManager] Monkey Command: wake.
110307 15:33:20.625:I [main] [com.android.monkeyrunner.MonkeyManager] Monkey Command: wake.
110307 15:33:21.625:I [main] [com.android.monkeyrunner.MonkeyManager] Monkey Command: wake.
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] Error starting command: monkey --port 12345
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice]com.android.ddmlib.ShellCommandUnresponsiveException
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at com.android.ddmlib.AdbHelper.executeRemoteCommand(AdbHelper.java:408)
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at com.android.ddmlib.Device.executeShellCommand(Device.java:276)
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at com.android.monkeyrunner.adb.AdbMonkeyDevice$1.run(AdbMonkeyDevice.java:89)
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.Executors$RunnableAdapter.call(UnknownSource)
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.FutureTask.run(Unknown Source)
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.ThreadPoolExecutor$Worker.run(UnknownSource)
110307 15:33:22.718:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.lang.Thread.run(UnknownSource)
110307 15:33:57.437:I [main] [com.android.monkeyrunner.MonkeyManager] Monkey Command: press KEYCODE_MENU.
110307 15:33:59.171:I [main] [com.android.monkeyrunner.MonkeyManager] Monkey Command: quit.
注:里面exception的提示我们可以忽略,因为我们可以看见 Monkey Command: press KEYCODE_MENU已经执行成功。
4、 可以Android_Root\tools下查看生成的shot1.png的截图。
六、实例扩展
因为ApiDemos首页上按下MENU键没有菜单出现,为了更加形象化,在实例五的基础上继续试验:
1、 在$Android_Root\tools下新建一个monkeyrunnerprogram1.py文件,里面内容为:
2、 将画面定位在Apidemos的首页,并将光标定位在第一项上。
3、 在$Android_Root\tools目录下运行一下命令:
monkeyrunner monkeyrunnerprogram1.py
4、在运行过程中我们可以看见光标不断向下移动,并且可以在当前目录下我们自定义的截图:
运行前:shotbegin.png
运行后(做了五次下移操作):shotend.png
Monkey 的专项测试浅谈
前言
其实去年年底我就说过很多公司功能测试都做的差不多了,接下来就开始折腾什么性能测试啊,安全测试啊,持续集成啊,Hybrid啦等等。果不其然,最近很多测试同学开始问我性能相关的问题。当然我们专业点来讲这个叫做专项测试,那么专项测试其实也是区分什么人去做,工具组的人也在做,业务组的人也在做,只不过大家做的切入点会很不同。也许很多同学也比较好奇我毕竟也去那么多公司撕逼了,到底我平时在做什么,怎么做的。这边那就简单说下吧。
嗯,我想想我做了什么。其实我现在就是,公司做到移动无线的应用专项测试就会想到我。然后之前一年我一个人做了持续集成,BDD,功能回归自动化,接口测试,静动态扫描,从客户端发起的接口测试等等。其实到现在为止我还是觉得我不知道我怎么走上专项测试这条路的,貌似某一天公司说我来做就变成我来做了。
其实别的先不说吧,我们先来说下专项吧。专项这个东西关键在于几点
怎么手动获取数据
怎么自动化获取数据
怎么分析数据
怎么定位问题
怎么优化
当然我这里还是要吐槽一句,大家醒醒吧。花个几年去研究UI 功能的自动化有意义吗?要学的东西太多了,还是醒醒吧。其实很多同学关键是上面我说的这些都不知道,而且不仅如此,还不知道的有
我什么阶段去做
每个阶段做什么
做到什么颗粒度
怎么才算完成了
。。。
也许还有很多,我也不想列了。其实我想说的是,其实也没有什么绝对,还是看你的团队,看你的项目,看你这次的目的,看项目阶段等等。不同的时间都是不同的策略。好吧,我们一个一个来吧。
如果你有时间,并且项目是初级阶段。那么按照周为单位需要去做一次专项评估。那么这个过程中你至少要mvn或者gradle或者pod install等都success,否则搞个毛线。那么这个过程中需要去根据本周代码的修改,从业务和技术角度去给出专项数据。这个数据到底给什么,是根据这次新功能的定位,这个产品的定位来定的,不要去想着有什么固定模式。比如腾讯qq和支付宝这两个产品无论怎么样,在专项的技术上面不会差很多,但是专项测试想到达到的目的和测试的场景肯定是天差地别的,明白?接着随着功能完善之后,最终还是需要做一次类似于所有模块集成之后的专项测试,记住,颗粒度,范围,数据怎么获取,完全根据测试这个owner的策略来。
反过来,如果你没有时间。那么在success的基础之上,你可以在功能完全完成之后直接来做一次集成专项评估。虽然效果不会差太多,但是这个方式的弊端在于,如果有问题,可能开发修改的时间就会很少,而且专项测试本身消耗的时间就会很长,所以最好是循序渐进,而不是集成之后去做,往往时间来不及。
真实场景模拟
比如这次有一个新项目,刚开始的项目,然后代码编译都ok了。那么作为新项目而言,我们专项其实有很多,但是又不可能都做,这个受限于你的团队的大小,以及功能的完整度。那么可能先做最关键的,比如CR,比如功能体验路径的对比,比如内存消耗的对比,比如不同网络下的数据对比,那么这些都是相对一个移动应用来讲最最重要的。剩下的可以在之后的迭代中陆续去评估掉。颗粒度的话,还是那句话,目前除了电量以外,剩下的数据基本上都是可以通过各种方式(插桩,越狱,调用原生API,Hook等)拿到的。
这个的确是一个比较逼人的问题。所以我才会说评估不仅仅是通过打出来的apk或者ipa来做的。而是在在项目迭代中持续去做的,那么直到功能完成度100%的时候就差不多可以做一轮完整的。那么问题又来了,一般应用都还会继续去改,怎么办呢?所以我说要CR啊。要结合业务重要性,功能重要性,代码的修改来一起评估每次修改所造成的影响。我们不可能每次都去做一次专项,所以这个是必须会的技能。
好了,这些解释完了。那么我们继续来看最最上面我提到的专项的关键点怎么办。这个我就拿我在西安写的keynote为例子吧。
好吧。这个不是我,是工具。我先澄清下。
使用不同的策略:其实就是根据自己的策略(各种操作比重不同)来制定脚本,包括也可以简单的二次开发,现在流行的做法就是去读取当前所有的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 } };
和启动性能一样,也有两种。活动状态和静默状态
据不可靠消息,stackoverflow上面其实有人已经说新版本的Android OS monkey就算增加这个参数在/misc/data 下面也不会有了。这个我还在进一步验证中
那么hprof文件主要来源就是ddms了。但是为什么一定要强调场景,是因为我们单独去拿这个数据也没有什么太大意义。MAT分析的时候一般都是做diff的对比。而这个diff的对比必须基于场景之上。
我们在测试之前总要知道标准吧。
同样的,会根据不同场景进行测试和分析。一方面是和自己比,一方面是和竞品去比。一旦有超出标准的,那么继续跟到代码去分析。
同样的需要去根据场景来分析。由于这个功能是动态的。所以我们需要去挑选场景。比如listview的场景,比如有tabbutton的场景,也就是说用户在做滑动啊,滚动啊,切换界面等操作时候进行动态的数据的获取。
基本上是被我放弃的东西了。由于限制比较多。不过效果还是很不错的,也能够发现比较深层次的问题
这个需要自己做一定的二次开发了。IO性能对App整体性能提升会很大。最高可以达到30%之多。
Android主要使用的是Xposed,主要是hook被测应用中针对的方法,而iOS的话在越狱手机上去使用IOStringBuffer这样的方法去监控IO的头文件,进行分析。无详细案例
iOS的话我其实也就不想多说了。其实就两个图
最后还是来说下自动化吧。其实最早我开源过一个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("datasize", size[1]); resultList.put("codesize", size[2]); System.out.println("应用缓存占用量:" + size[0] + "kb" + "应用数据占用量:" + size[1] + "kb" + "应用代码占用量" + size[2] + "kb"); // cpu float cpu = CpuInfo .getCpuData(this.sn, this.PackageName, cpu_index); resultList.put("CPU", cpu + "%"); System.out.println("cpu占用率" + cpu + "%"); // mem float vss = VssInfo .getVssData(this.sn, this.PackageName, vss_index); resultList.put("VssMemory", vss + "kb"); System.out.println("vss内存占用率" + vss + "kb"); float rss = RssInfo .getRssData(this.sn, this.PackageName, rss_index); resultList.put("RssMemory", rss + "kb"); System.out.println("rss内存占用率" + rss + "kb"); try { Thread.sleep(4000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // write fileUtils.writeFile(this.filePath, JsonUtil.toJson(resultList) + "\n", true); } }
好吧,恒温说这篇文章估计长到没有人看了。其实还有很多要说的。以后继续吧。专项是一个很深奥的事情,技术其实是很辅助的,场景和策略以及分析才是最重要的。
references
标签:
原文地址:http://www.cnblogs.com/iloverain/p/5604172.html