在Android平台上,如果要实施自动化测试的话,有几个方式,比如使用基于Instrumentation类库及其衍生类,或者是使用monkeyrunner,测试用例运行在PC上,通过USB或者WIFI向Android手机的应用发送测试命令。这两个方案各有优缺点:

  1、Instrumentation技术的优点是编写UI自动化测试用例的技术跟编写Android应用的技术是同一个技术,而且API也是使用Android本身的类库。但是它的缺点是需要待测应用使用调试用密钥进行数字签名,这是因为Android平台强制要求一个应用如果要读取或者修改另外一个应用的状态的话,必须是同一个实体,而同一个实体的证据是使用同一个密钥进行数字签名。因此使用Instrumentation技术来测试第三方应用的话,必须先将第三方应用的数字签名去掉,重新使用调试版密钥签名才能执行测试。如果应用里有资源文件需要使用原先的数字签名才能解密的话,那么启动重新签名过的应用时,会出现问题。例如QQ的Android版是这种情况,重新签名后,再启动会崩溃 – 从预防他人恶意复用知识产权的角度来说,是一个非常好的做法,但对于自动化测试是另一种情况了。

  2、Monkeyrunner的缺点是,只能运行在PC机上,其运作原理是测试用例是在PC机上解释用例脚本,通过网络向Android手机的应用发送消息实现的。如果要求只能在手机上,完全脱离PC机的话,没办法测试。例如要在实际测试运营商的网速和通信质量,测试人员可以拿市面上几个流行的应用,写几个自动化测试用例,然后手持手机设备执行测试。

  曾经碰到类似的测试需求,即要求只能在手机上运行,为类似QQ这样不能重新签名的Android应用编写自动化测试用例……能够满足这个要求的测试技术只有monkey,可是不知道Google是出于什么考虑,只把monkey设计成一个随机发送按键消息的程序 – 跟猴子一样,在手机键盘上乱敲乱点;而没有给monkey加上类似Instrumentation的抓取控件的支持。

  为了满足测试需求,我们对方案是修改了一点monkey的源代码,实现在手机真机上,脱离PC机,无需重新签名测试QQ的用例。修改的原理是:

  1、Android SDK里有一个工具HierarchyViewer可以显示Android手机上各应用的UI布局,我们也实现了支持iOS的HierarchyViewer。

  2、HierarchyViewer的原理是通过与运行Android手机上的ViewServer建立一个socket连接(这个连接可以通过WIFI,也可以通过USB端口),向ViewServer发送命令的形式通信。

  a)例如,当HierarchyViewer发送 DUMP -1 这个指令时,ViewServer 会将整个Android系统的UI树形布局回传给HierarchyViewer。

  b)HierarchyViewer再解析回传的字符串,关于这个过程,我的同事刘斌华写了一篇非常好的文章:http://www.cnblogs.com/vowei/archive/2012/08/08/2627614.html

  3、因为HierarchyViewer是运行在PC机上的,而ViewServer是运行在手机上的,实际的抓取Android UI控件树的过程是由ViewServer来完成的,HierarchyViewer只是一个简单的客户端。

  4、而Android系统又是多任务的,可以由好几个程序同时运行在后台,在手机上抓取UI控件树的过程变得很简单:

  a)建立到本机ViewServer的socket连接。

  b)跟HierarchyViewer一样,向ViewServer发送命令并解析回传的字符串,这个字符串可以用我们的iQuery解析:https://github.com/vowei/iQuery

  c)默认情况下,ViewServer是出于关闭状态,需要显式启动。

  下面是关键代码,打开monkey主函数的源文件,Monkey.java,在其main函数里:

  1、在Monkey调用getSystemInterfaces之后,加上下面这行显式启动ViewServer – 其监听的端口号是4939。

  1:monkey.mWm.startViewServer(4939);

  2、然后在monkey源码里,任意的合适的位置,创建一个socket,连接到本机的ViewServer上:

1:Socket socket = new Socket();
2:socket.connect(new InetSocketAddress("127.0.0.1", 4939));

  3、与ViewServer通信:

1:BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
2:BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
3:out.write("DUMP -1");
4:out.newLine();
5:out.flush();
6:String line = null;
7:int maxCount = 0;
8:while ( (maxCount < 4) && ((line = in.readLine()) != null) ) {
9:... ...
10:}

  因为在手机上启动ViewServer是一个超级用户才能做的操作-需要打开一个网络端口,需要使用一台root过的设备,运行方式是:

  1、找到一个root过的设备,并安装一个Terminator应用。

  2、在手机上启动“终端模拟器”程序,并依次执行下列命令:

1:export CLASSPATH=/sdcard/monkey.jar
2:cd /sdcard
3:su
4:#下面这个命令默认会启动QQ,并打印QQ登录界面上4个TextView控件上的文本
5:app_process /system/bin com.android.commands.monkey.Monkey
6:#下面这个命令运行指定的Android应用,并打印界面上前4个TextView控件上的文本,第一个参数是包名,第二个参数是要启动的Activity全名
7:app_process /system/bin com.android.commands.monkey.Monkey com.android.development com.android.development.Development