因为前段时间业务需求,需要我们测试 Android 软件性能,刚开始我们使用的是 Monkey+Monkeyrunner +python 脚本。网上也有很多资料来讲解如何利用 monkeyrunner 来录制脚本,使用最多的都是通过记录坐标的方式来录制,但因为我们在程序操作中,通常都是在同一块显示区域实现页面的相互跳转,所以这种方法并不能够满足我们的需要,我想到了使用 id 来做,因为 id 能够保证控件的唯一性,并且也可以精确地定位到控件的位置。我是因为这个项目自学 python,所以对 python 这方面很多知识并不了解,于是去网上找了很多资料,但是没有一篇完整的代码可以用来实现,我也是查了好多资料,才渐渐摸索出了实现方法,特此记录。
一. 准备工作
在做测试之前,要保证我们的测试环境可以正确运行,需要我们先去搭建测试环境。
(1)保证电脑上已经安装了 jdk 并且环境变量等的配置已经完成
(2)安装了 eclipse,用于 android 的开发,相信做 android 的人应该这些都是会用到的吧
(3)下载 SDK 并且完成 SDK 的配置(因为我们需要使用到 SDK 文件夹 tools 中的一些工具工具,所以这个是必须的)
(4)安装 python IDLE(python 编辑器,可以很方便的用来编写 python 文件),并在环境变量里面配置 path
(5)android 模拟器(也可以使用 android 自带的模拟器,但是启动实在是太慢了,太费时间,果断放弃)
我使用的是天天模拟器,网上可以下载
二. 测试流程
第一步,打开模拟器,我们可以在 eclipse 的 device 下面看到当前的模拟器显示信息,如下图:
注:一定要确保模拟器连接成功了,要不然接下来会有问题。
我们在使用 android 获取 id 的时候,都是直接 findViewByID(R.id.IDName),系统可以帮助我们识别是在哪一个文档中,但是 python 却不行,就相当于在 android 中你写个相对路径,就 ok 了,但是在 python 中,必须要使用绝对路径(只是打个比方哈),在 python 中,如果我们的控件是嵌套在几层布局之中的话,就必须通过它的根布局元素 ID 去一层一层往下找。通常我们写的界面都很复杂,你要一个一个去找 ID,不得累死,万一找漏了一个,就会功亏一篑了,所以我们要向获取正确的 ID 路径,就要借助于 SDK 给我们提供的一个工具 --hierarchyviewer.bat.
它的路径在 SDK 安装目录 \ tools\hierarchyviewer.bat, 这是我的安装路径:F:\Android_Studio\Android-SDK\tools\hierarchyviewer.bat.
双击它并打来,我们会看到下面这个显示界面,可能你会觉得奇怪,但请不要着急,稍微等一会儿,它会去自动搜索你的模拟器。
如果它没有搜索到你的模拟器,就会是这种现实状态:(不知道什么原因,我的模拟器在 eclipse 中获取不到了):
也就是说下面没有任何关于模拟器的信息显示,此时回过头去 eclipse 中的 device 查看,发现模拟器也不见了。可能很多人会在此时去选择重启模拟器,但是完全不用,我们可以通过命令行去重新连接。
重新连接模拟器:
打开 开始 --> 运行 --> 输入 cmd--> 回车
在命令行中输入 adb devices,点击回车,发现没有任何设备连接,此时就需要我们去重新连接。
在命令行中输入 adb connect 127.0.0.1:6555, 点击回车,显示 connected to 127.0.0.1:6555
然后再次输入 adb devices 去查看我们的设备列表,就能够识别到了。如下图:
在第一条命令中,127.0.0.1 是模拟器的 IP 地址,6555 是端口号,我们可以直接在 eclipse-devices 窗口中看到的哈~ 如果使用的是别的模拟器,可以自行查看。很多模拟器是可以直接连接的,但是天天模拟器的连接很麻烦,我也是摸索了很久,才找到这个解决方法。
然后就可以看到我们连接成功后,hierarchyview.bat 的界面如下显示:
选中我们的项目,会看到 Load View Hierarchy 按钮变为可点击状态,点击该按钮,会出现下面这个界面:
左边这个图中的结构图就是我们当前显示的 activity 的 layout 中定义的布局结构图啦~ 我们可以在左图中的空白区域按住鼠标左键左右拖动来移动图片,也可以通过滚动鼠标来放大缩小图片,当你点击具体的节点时,可以在右边和节点上方看到预览图。并且我们可以很方便的去查看 ID,在每一个节点中都会有显示。
接下来,假如说我们要实现一个按钮点击事件,就需要去找到这个按钮的 ID,那怎么找呢,首先,我们先找到这个按钮,然后从左图最开始的节点,找出所有带有 id 值得节点,直到按钮 ID 所在节点为止,例如,我有一个按钮,id 为 button_dispatcher, 它的路径是:
content->ll_main->linearlayout_side_screensaver->sidebar_fragment->ll_dispatcher->button_dispatcher, 因为这个图的根节点是从 phoneWindow 开始的,我们寻找的时候,只需要从我们的自己的 MainActivity 加载的层开始寻找就可以了,所以我的路径是从 linearlayout_side_screensaver 开始的(这里为什么不是从 phoneWindow 层开始,就要涉及到 Android 的一个层级结构了,有兴趣的自己去查查哈!这里就不细说了)
好了,不说了,我直接贴代码,还有一个层级结构的图,方便大家比对。
(初来乍到,有很多写的不规范的地方,大家主要看思路就好)
代码如下:
- from com.android.monkeyrunner import MonkeyRunner as mr from com.android.monkeyrunner import MonkeyDevice from com.android.monkeyrunner import MonkeyImage as mi from com.android.monkeyrunner.easy import EasyMonkeyDevice,
- By from com.android.chimpchat.hierarchyviewer import HierarchyViewer from com.android.hierarchyviewerlib.models import ViewNode
- try: device = mr.waitForConnection() except Exception: print 'device connect to VM failed!!!'
- try: easyMonkey = EasyMonkeyDevice(device) except Exception: print 'easyMonkey are not available!!!'
- try: hierachy_view = device.getHierarchyViewer() except Exception: print 'hierachy_view are not available!!!'def returnhierarchyview() : return device.getHierarchyViewer() def returneasyMonkey() :
- return EasyMonkeyDevice(device) def returnfinviewbyid(idvalue) :
- #hierachy_view = returnhierarchyview() return hierachy_view.findViewById(idvalue) def returnById(idvalue) : return By.id(idvalue) def action_touch(widges, widgesname) :
- easyMonkey = returneasyMonkey() print widgesname,
- 'is available?(True or Faulse)',
- easyMonkey.visible(widges) if (easyMonkey.visible(widges)) : print 'the ---',
- widgesname,
- '----was touched'easyMonkey.touch(widges, MonkeyDevice.DOWN_AND_UP) mr.sleep(1)
- else: return 0
- def action_type(widges, value) : easyMonkey = returneasyMonkey()
- easyMonkey.type(widges, value) print 'if ',
- value,
- ' is available?(True or Faulse) ',
- easyMonkey.visible(widges)
- print 'input a test case , value = ',
- value mr.sleep(1)#test delivery man#admname username#password password def testlogin(admname, password) : mr.sleep(2)#easyMonkey = returneasyMonkey()#hierachy_view = returnhierarchyview() try: view_node1 = returnfinviewbyid('id/sidebar_fragment') view_node2 = returnfinviewbyid('id/ll_dispatcher') print('touch button delivery man') action1 = returnById("id/button_dispatcher") action_touch(action1, 'button dispatcher')
- view_node3 = returnfinviewbyid('id/content_fragment') view_node4 = returnfinviewbyid('id/account_num') adm = returnById("id/administrator_number") action_type(adm, admname) passw = returnById("id/password_edittext") action_type(passw, password) action4 = returnById("id/login") action_touch(action4, 'button login') print "sleep for 3 seconds!!"mr.sleep(3) except NameError: print 'the id can not be found!!!'
- #########################################################def getgoods() : easyMonkey = returneasyMonkey()#hierachy_view = returnhierarchyview() layoutview01 = returnfinviewbyid('id/linearLayout_body_sreenSaver')
- view_node3 = returnfinviewbyid('id/content_fragment') layoutview01 = returnfinviewbyid('id/linearlayout_cabinet_2_10') if layoutview01 is None: layoutview01 = returnfinviewbyid('id/linearlayout_cabinet_4_10') layoutview02 = returnfinviewbyid('id/linearlayout_cabinet_opendoor_back2') button_back = returnById("id/cabinet_opendoor_back_410") action_touch(button_back, "cabinet_opendoor_back_410")
- else:
- linear_openback_view = returnfinviewbyid('id/linearlayout_cabinet_opendoor_back') button_back = returnById("id/cabinet_opendoor_back_210") action_touch(button_back, "cabinet_opendoor_back_210")
- ##########################################################test the
- function after login page.def parceldelivery(order, phone) : easyMonkey = returneasyMonkey()#hierachy_view = returnhierarchyview()#view_node = returnfinviewbyid('id/content_fragment') dilivePackage = returnById("id/dilivePackage") action_touch(dilivePackage, "dilivePackage") orderId = returnById("id/orderId") textstr = easyMonkey.getText(orderId) if (textstr == '') : action_type(orderId, order)
- else: empty1 = returnById("id/empty") action_touch(empty1, "empty")#easyMonkey.setText(orderId, "") action_type(orderId, order) phoneNum = returnById("id/phoneNum") text = easyMonkey.getText(phoneNum) if (text == '') : action_type(phoneNum, phone)
- else: #easyMonkey.setText(phoneNum, "") empty2 = returnById("id/empty") action_touch(empty2, "empty")
- action_type(phoneNum, phone)
- button_next = returnById("id/button_next") action_touch(button_next, "button_next") print "sleep for 6 seconds!!"mr.sleep(6) getgoods()#button_diliv_back = returnById("id/button_diliv_back")#action_touch(button_diliv_back, "button_diliv_back")
- def takegoods(identifycode) : sider_layou = returnfinviewbyid('linearLayout_side_sreenSaver') tackgood_layout = returnfinviewbyid('id/sidebar_fragment') ll_getpackage = returnfinviewbyid('ll_getpackage') action1 = returnById("id/button_getpackage") action_touch(action1, 'button getpachage') layoutview01 = returnfinviewbyid('id/linearLayout_body_sreenSaver')
- view_node3 = returnfinviewbyid('id/content_fragment') relativeview = returnfinviewbyid('charge_card_number1') edit_getpackage = returnById("id/take_goods_number") action_type(edit_getpackage, identifycode) take_goods_enter = returnById('id/take_goods_enter') action_touch(take_goods_enter, "take_goods_enter") print "sleep for 6 seconds!!"mr.sleep(6) getgoods()
- ##########################################################
- if __name__ == '__main__': import codecs import time time_format = '%Y-%m-%d %X'
- try: codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001'
- else None) print('APP start to test...')
- print('start robot main activity') componentName = '包名/.MainActivity'device.startActivity(component = componentName) print('MainActivity begin to run') mr.sleep(3) except Exception: print 'this is somehing wrong!!!'
- #test delivery activity#test
- case#testlogin('123456', '000000')#testlogin('12345678', '000000')#testlogin('123', '000000')
- #test parcel delivery
- #test
- case#parceldelivery('1234564', '15678909878')#parceldelivery('123456', '156789098')#parceldelivery('1234', '15678909878')#parceldelivery('', '')#parceldelivery('', '15678909878')#parceldelivery('123456', '') try: for i in range(1, 10) : print '<<<<<<<<<<<<<<<<<<<<<<<<<<',
- 'This is the ',
- i,
- ' times to run>>>>>>>>>>'print 'the current time is ',
- time.strftime(time_format, time.localtime()) testlogin('300001', '987654') parceldelivery('1098000000001416801', '15983629282')
- takegoods('123456') print '\nthe end time is ',
- time.strftime(time_format, time.localtime()) print '<<<<<<<<<<<<<<<<<<<<<<<<<<',
- 'the End of ',
- i,
- ' times>>>>>>>>>>>\n\n'except Exception: print 'this is a exception occur...'
下面是我的页面的一个层级结构:(因为图片太大了,可能有点看不清):
大家可以比对这个图,就大概能理清思路了。
注意:可能有人会注意到我在程序中使用了 sleep 来休眠几秒,是因为有时候我们会有弹出框来提示信息,如果在弹出框还没有消失的时候,我们监测到的当前界面就是弹出框界面,而不是原来界面,同样会提示找不到 id 之类的信息,所以一定要休眠,等到弹出框消失之后,再继续运行,就不会报错啦~
三. 运行查看结果
首先,打开命令行:开始 -> 运行 ->cmd
在命令行中将路径切换到你的 android SDK 的 tools 目录所在路径,我是为了方便运行,避免输入路径,直接把 python 文件放在了 tools 目录下。然后运行下面的命令,以下是我的运行结果:
这个是页面启动,如果你的程序并没有运行,他可以找到你的安装路径,并启动。当然,安装路径要在程序中自己设置,通常,我们可以通过在命令行去查看。
这是测试中的输出,我们可以在模拟器中看到自动化测试的过程,就是这样啦~
来源: http://lib.csdn.net/article/python/41928