Android自动化测试初探(三): 架构实现

来源:互联网 发布:为知笔记会员 编辑:程序博客网 时间:2024/05/29 08:07

  前两节讲了用Android SDK自带的tool-hierarchyviewer来捕获Activity上Element,并分析了其中的原理。对于要实现GUI自动化,还有哪些工作没有完成呢?

  *   Invoke界面上的Element,如点击按钮,在文本框中输入内容等

  *   Press手机自身所有的按键,如HOME键,Menu键,左右上下方向键,通话键,挂机键等

  *  判断测试结果

  前面说过,直接从Emulator内部获取当前Activity上的Element这条路已经断了,同理,探索像UI Automation上一样Invoke Element的操作估计是行不通了,因为你拿不到Element的对象实例,所以实例所支持的方法当然也没有办法拿到。

  怎么办?实在不行,基于坐标来对Element进行触发总可以吧。在Windows中发送基于坐标发送键盘和鼠标事件一般是在无法识别Element的情况下,想的最后一招,这使我想起起了Android中的monkey测试,对着屏幕就是一通乱点,压根就不管点的是什么。所幸的是,当前Android系统中我们得到了Element的属性信息,其中就包括坐标信息,而且这种信息是具有弹性的,也就是说即使Element的坐标随着开发的改变而有所变化,也不用担心,因为当前的坐标是实时获得的。

  那么怎样才能给Element发送模拟按键等操作呢?总不能用Windows当前的键盘和鼠标事件吧,那样一旦模拟器的位置改变或失去焦点,啥都白搭,风险太大了。看来给Emulator内部发送模拟按键等操作比较靠谱。查了一下SDK,其中确实有这样的方法存在,但是我们当前的测试基础架构程序位于Emulator外部,怎么办?突然想起了hierarchyviewer的实现机制,通过Socket来发送信息。Hierarchyviewer有系统自带的进程给予答复响应(具体是哪个进程进行的响应不清楚,没有研究过)。那么我们也来模拟做一个Listener总可以吧。

  其实对于模拟按键发送,网上的帖子很多,但大部分是基于一种方式实现的,IWindowManager接口。不巧的是,SDK并没有将该接口提供为public,所以需要用android源码替代android.jar包进行编译的方式进行绕行,感觉方法有点复杂。在后面另一篇系列文章中我会列出我在网上看到的另一种基于Instrumentation和MessageQueue的机制实现方法。

  最后就剩下判断测试结果了。判断测试结果一般分为如下两种:外部条件是否满足,如文件是否产生,数据是否生成等;内部条件是否满足,如对应的Element是否出现或消失,Element上内容如字符串是否有变化。对于前一种本文不予讨论,后一种情况下,Element出现或消失可以通过hierarchyviewer来获取。仔细研究过hierarchyviewer会发现,它并没有提供Element界面上内容(Text)的属性。这可有点晕了,好像又要回到实现捕获Activity实例的老路上来了。考虑图像识别?这好像不靠谱。突然想到,4939端口上发送DUMP命令后的返回结果中会不会有此类hierarchyviewer没有显示出来的信息呢,万幸,还真有。在我上一篇博文(Hierarchyviewer 捕获Element的实现原理)中查询mText字段,会发现mText=1,5这样的信息,其实就是代表了计算器Button5上显示的内容5,逗号前的1表示后跟一位信息。

  至此,问题似乎都解决掉了。画个基础架构图做个总结:



Android自动化测试初探(四): 模拟键盘鼠标事件(Socket+Instrumentation实现)


  通过Socket + Instrumentation实现模拟键盘鼠标事件主要通过以下三个部分组成:

  *   Socket编程:实现PC和Emulator通讯,并进行循环监听

  *   Service服务:将Socket的监听程序放在Service中,从而达到后台运行的目的。这里要说明的是启动服务有两种方式,bindService和startService,两者的区别是,前者会使启动的Service随着启动Service的Activity的消亡而消亡,而startService则不会这样,除非显式调用stopService,否则一直会在后台运行因为Service需要通过一个Activity来进行启动,所以采用startService更适合当前的情形

  *   Instrumentation发送键盘鼠标事件:Instrumentation提供了丰富的以send开头的函数接口来实现模拟键盘鼠标,如下所述:

  sendCharacterSync(int keyCode)            //用于发送指定KeyCode的按键

  sendKeyDownUpSync(int key)                //用于发送指定KeyCode的按键

  sendPointerSync(MotionEvent event)     //用于模拟Touch

  sendStringSync(String text)                   //用于发送字符串

  注意:以上函数必须通过Message的形式抛到Message队列中。如果直接进行调用加会导致程序崩溃。

  对于Socket编程和Service网上有很多成功的范例,此文不再累述,下面着重介绍一下发送键盘鼠标模拟事件的代码:

  1.  发送键盘KeyCode:

  步骤1. 声明类handler变量

private static Handler handler;

  步骤2. 循环处理Message

//在Activity的onCreate方法中对下列函数进行调用
private void createMessageHandleThread(){
    //need start a thread to raise looper, otherwise it will be blocked
        Thread t = new Thread() {
            public void run() {
                Log.i( TAG,"Creating handler ..." );
                Looper.prepare();
                handler = new Handler(){
                    public void handleMessage(Message msg) {
                           //process incoming messages here
                    }
                };
                Looper.loop();
                Log.i( TAG, "Looper thread ends" );
            }
        };
        t.start();
}

  步骤3. 在接收到Socket中的传递信息后抛出Message

handler.post( new Runnable() {
            public void run() {
Instrumentation inst=new Instrumentation();
inst.sendKeyDownUpSync(keyCode);
}
} );

  2. Touch指定坐标,如下例子即touch point(240,400)

Instrumentation inst=new Instrumentation();
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 240, 400, 0));
inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 240, 400, 0));

  3. 模拟滑动轨迹

  将上述方法中间添加 MotionEvent.ACTION_MOVE


原创粉丝点击