天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写《深入理解 MonkeyRunner》书籍“。但因为诸多原因,没有如愿。所以这里把草稿分享出来,所以错误在所难免。有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息。
上一节我们学习了如何通过MonkeyRunner这个类的静态方法waitForConnection来把后台和设备建立好连接,且看到了在建立连接成功后,该方法会返回来一个MonkeyDevice的实例对象。那么这一节我们就通过编写一些脚本代码示例来了解学习下MonkeyDevice这个类的使用方法。
MonkeyDevice这个类,从类名我们就可以知道它所做的事情基本上都是跟目标安卓设备这个device相关的,事实上也是如此,比如上一节的示例中就是通过Device对象的getSystemProperty方法来获得系统环境变量中保存的目标设备的序列号的。
当然,除了getSystemProperty这个方法之外,MonkeyDevice还提供了很多设备相关的操作方法,且这些方法很大一部分我们在编写测试脚本的时候都会经常使用。下面我们就把我们常用的且本节会用到的关键方法的使用描述给列出来供大家参考:
表3-2-1 示例代码所用关键方法列表
从上表我们可以看到在调用MonkeyDevice的press按键方法和touch触控方法时需要指定一个动作类型的参数。根据不同的动作类型可以控制向目标设备发送不同类型的事件,且这些动作在MonkeyDevice都有对应的定义:
- DOWN: 通过press或者touch方法往设备发送一个按下事件,模拟的是用户按下一个按键或者在触摸屏触控按下手指的动作
- UP: 通过press或者touch方法往设备发送一个释放事件,模拟的是用户在按下一个按键后放手释放该按键或者用户在屏幕上按下手指然后释放手指的动作
- DOWN_AND_UP: 通过press或者touch方法往设备发送一个按下DOWN事件,然后紧跟着立刻发送一个释放UP事件,模拟的是用户按下一个按键后立刻释放手指和用户触控屏幕后立刻释放手指的动作
- MOVE:往目标安卓设备发送一个移动的触控事件,该事件通常会和DOWN以及UP组合使用来模拟一个拖动Drag的手势动作。注意这个动作只对MonkeyDevice的触控方法touch起效,因为按键只有模拟触摸屏幕的触控动作能做拖动的动作,按键是没有办法做拖动动作的
那么下面我们就通过两个示例来看一下在monkeyrunner脚本中我们应该如何调用这些方法来做事情。同时这里需要重申一点的是,这些示例代码都是为了没有接触过monkeyrunner脚本编写的读者准备的,为了方便描述,这些代码都没有封装成任何方法。
我们首先来看第一个示例,该示例的作用是在NotePad应用中创建一个日记,然后把它删除,整个过程会涉及我们上面提及的大部分方法的调用。
第一步:连接上目标安卓机器。
代码3-2-1 增加日记 - 连接设备
1 from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice 2 3 #Step 1: Connect to the target device 4 device = MonkeyRunner.waitForConnection(30,"192.168.1.102:5555")
这一步我们在上一节已经看过了,所以不详谈,如果不记得的请大家返回上一节。
第二步:确定连接上的目标安卓设备就是我们想要的设备。
代码3-2-2 增加日记-确保目标设备是想要的
6 #Step 2: Make sure that the device connected is as what we expected 7 ret = device.getSystemProperty("ro.serialno") 8 assert ret == "HT21ATD05099"
通过MonkeyDevice提供的getSystemProperty方法来获得序列号,然后在第8行使用断言来判断该序列号是否是预期的序列号。
第三步:确保目标安卓设备已经安装好NotePad这个应用,如果没有的话就安装上。
代码3-2-3 增加日记-安装应用
10 #Step 3: Check whether the NotePad application has already been installed 11 retPackage = device.shell("pm list packages | grep " + "com.example.android.notepad") 12 #value return for "pm list packages" is with format like "package:com.example.android.notepad" 13 if retPackage !="package:" + "com.example.android.notepad": 14 ret = device.installPackage("/Users/apple/Documents/workspace-luna/MonkeyRunnerDemo/app/NotePad.apk") 15 assert ret == True
这里主要涉及了MonkeyDevice的两个方法的使用。第一个就是shell这个方法。这里使用该方法的目的是发送”pm list packages”命令到目标安卓机器去查询需要的应用的包名是否已经在系统上存在,如果已经存在的话就代表该应用已经在系统上安装上了,否则就需要调用MonkeyDevice的第二个方法installPackage来把主机指定路径的应用包推到目标机器然后进行安装。这里检查NotePad应用的包名是否存在完整命令是”pm list packages|grep “com.example.android.notepad”,其中pm就是安卓上面专门用来对应用包进行管理的命令工具,管道前一部分的意思是用pm命令把所有的已经安装的应用的包名给列出来;管道后一部分的意思是在所有的包名中查找目标应用NotePad的包名”com.example.android.notepad”。其实这个命令的返回跟我们在主机命令行中执行”adb shell pm list pakcages|grep “com.example.android.notepad”是一样的。下面我们比较没有管道后面一部分把其他包过滤的功能时候的输出和有管道后面一部分进行过滤之后的输出。 
图3-2-1 列出所有已安装包名
图3-2-2 只列出目标包名
从上图的对比大家可以看到如果没有加上过滤功能的话列出来的是一大堆已安装的应用的包名,加上管道后面的过滤功能后就只列出我们的目标应用NotePad对应的包名了。这些其实都是类Linux操作系统的基本命令使用知识了。
这里需要注意的是pm命令返回来的目标应用的包名信息前面是多了一个标识字串”package:”的,然后才是真正的包名。所以在13行判断返回来的包名和预期的包名是否是一样的时候就需要对这个标识字符做一些处理。
如果目标应用的包名不存在于目标系统上面的话,那么就在14行调用MonkeyDevice的installPackage方法来把参数指定的主机本地路径的apk安装到目标安卓机器上面。
最后15行就用断言判断该命令执行是否成功,也就是安装是否成功,如果失败的话会抛出异常。
第四步:启动应用。
代码3-2-4 增加日记-启动应用
17 #Step 4: Start the NotePad apk and direct to activity com.example.android.notepad.NotesList 18 device.startActivity(component="com.example.android.notepad/com.example.android.notepad.NotesList") 19 20 MonkeyRunner.sleep(3)
在确定目标应用已经在目标设备安装好后,下一步需要做的事情就是去把目标应用打开,并且定位到目标应用的NotesList这个入口Activity里面。就像每个代码都有一个入口main函数,安卓里面的每个应用也都会有一个入口Activity。至于一个Activity是否是入口是通过项目的AndroidManifest.xml指定的,具体示例请看下图来自NotePad项目的AndroidManifest.xml的描述:
图3-2-3 AndroidManifest.xml指定入口Actvity
第五步:进入NoteEditor Activity
代码3-2-5 增加日记-打开NoteEditor Activity
22 #Step 5: Direct to the NoteEditor activity to add a note 23 device.press('KEYCODE_MENU', MonkeyDevice.DOWN_AND_UP); 24 25 MonkeyRunner.sleep(3) #Wait a bit for the new page to get ready 26 27 #Touch on the "Add note" menu entry by coordinate 28 device.touch(250,750,MonkeyDevice.DOWN_AND_UP) 29 30 MonkeyRunner.sleep(3) #Wait a bit for the new page to get ready
当进入到入口Activity NotesList后,下一步需要做的事情就是去打开NoteEditor这个Activity,以便往后添加一个新的日记了。进入到该Activity的方式是先去点击系统菜单按键调出选项菜单,然后触控菜单项”Add note”就会进入到目标Activity了。这里23行的按键方法press的第一个参数代表按键键值,它的写法有多个选择,比如这里的系统菜单键可以写成”KEYCODE_MENU”,也可以写成“MENU”,甚至可以写成真正的键值”82”。今后我们在原理分析的时候都会看到为什么会是这样,这里就先卖个关子。然后我们看到press的第二个参数就是本小节前面描述的案件动作,这里是DOWN_AND_UP,代表一个普通的按下按键然后释放按键动作。打开菜单选项后,就需要模拟触控事件来对其中的”Add note”这个菜单项进行点击了,这里调用的是MonkeyDevice的touch触控方法,前面两个参数代表需要触碰的屏幕的绝对坐标。我们可以通过uiautomator viewer这个工具来找到该坐标值,在上一章中我们已经有过这方面的描述,这里我们就不累述了,大家不清楚怎么做的请返回上一章进行查看。当触碰该菜单项后应用就会打开NoteEditor这个Activity了。
第六步:输入新建日记内容
代码3-2-6 增加日记-输入日记内容
#Step 6: Type in the text for the note 33 device.type("MyFirstNote")
通过上一章的学习,我们知道进入NoteEditor这个Activity之后,安卓的软键盘就会自动弹出来方便用户输入日记的内容。所以这一步要做的事情就是调用MonkeyDevice.type方法来把参数指定的日记内容,同时也是标题,写入到NoteEditor的日记控件里面,最终效果如下图所示。
图3-2-4 增加日记内容
第七步:保存新增加日记
代码3-2-7 增加日记-保存日记
35 #Step7: Save the note by touch on the "save" menu entry by coordinate 36 device.press('KEYCODE_MENU', MonkeyDevice.DOWN_AND_UP); 37 38 MonkeyRunner.sleep(3) #Wait a bit for the new page to get ready 39 40 device.touch(200,750,MonkeyDevice.DOWN_AND_UP)
保存日记的流程跟前面打开NoteEditor的流程是一样的,都是先用press方法模拟点击系统菜单按键,然后使用touch触控方法触碰菜单选项的“save”选项来保存新增日记。
第八步:删除日记
代码3-2-8 删除日记
42 #Step 8:Touch on the "delete" menu entry of the context menu options to delete the note 43 MonkeyRunner.sleep(3) 44 device.drag((240,120),(240,120),3,1) 45 device.touch(84,450,MonkeyDevice.DOWN_AND_UP)
在保存好日记后,NotePad应用会自动跳回到NotesList这个Activity来显示所有的包括新增加的这个日记。上面的代码是在这个页面中长按该新增加的日记然后在弹出的上下文菜单选项中选择“delete”选项来把该日记删除掉。该过程中用到了一个MonkeyDevice的drag方法。从前面对drag这个方法的描述我们可以知道它的作用是模拟拖动这个动作,指定的前两个参数分别是拖动开始的坐标和结束的坐标,这里我们把开始坐标和结束坐标都设置成一样的,再把拖动时常设置成3,最有一个代表拖动步骤的参数设不设再这里都没有关系。这样的设置组合代表的其实就是一个长按的动作,这里坐标指定的其实就是新增日记在屏幕中的坐标,所以这里代表的就是对这个新增的日记执行一个长按的动作。长按后就会弹出上下文菜单,其中有一项就是删除,第45行就是通过触控方法touch来把该选项触碰一下以删除该日记。
图3-2-5 删除日记
第九步:卸载应用
代码3-2-9 卸载应用
47 #Uninstall the application
48 device.removePackage("com.example.android.notepad")
整个流程最后一步就是调用MonkeyDevice的removePackage方法来把参数指定的包名所代表的应用给删卸载掉,把系统还原成测试前的状态。
通过上一个示例我们基本上把MonkeyDevice常用的方法都走了一遍了,下面我们将会通过另外一个简单的示例去熟悉下getProperty和getPropertyList两个方法的使用。
该示例的目的是通过getPropertyList把所有支持的系统环境变量属性列出来,然后通过getProperty把每一个环境变量的值给打印出来。因为代码不多,所以这里将全部代码列出来然后进行相应的解析。
代码3-2-10 获取环境变量列表和值
1 from com.android.monkeyrunner import MonkeyRunner 2 3 device = MonkeyRunner.waitForConnection() 4 5 varList = device.getPropertyList() 6 assert len(varList) > 0 7 print "Property and value List:" 8 print "--------------------------------------------------" 9 i = 1; 10 for var in varList: 11 print i," ",var + " :" + device.getProperty(var) 12 i += 1
上一节我们说过MonkeyDevice的getSystemProperty和getProperty是不一样的,虽然官方网站说它们是同一回事,只是getSystemProperty是给开发者使用的。但经过我们的实践,比如上一节我们可以通过调用getSystemProperty来根据参数”ro.serialno”这个属性来获得测试机器的序列号“HT21ATD05099”,但从下图的输出结果大家可以看到getPropertList列出来所有支持的环境变量列表其实并没有”ro.serialno”这一项,也没有”HT21ATD05099”这一个序列号的值给列出来。至于它们各自的实现原理,我们今后的原理分析章节会进行详尽描述。我们返回上面代码,该代码就是在第5行调用getPropertyList来列出所有的系统支持的环境变量列表,然后10-12行循环取出每个环境变量名,并把它作为参数调用getProperty方法来获得该环境变量的值,最后格式化输出到屏幕。输出结果请看下图。
图3-2-6 获取环境变量列表和值输出结果
——— 未完待续———
作者:天地会珠海分舵
微信公众号:TechGoGoGo
微博:http://weibo.com/techgogogo
CSDN:http://blog.csdn.net/zhubaitian