Android自动化测试环境部署及adb sdkmanager avdmanager Monitor DDMS工具使用及命令详解
Android自动化测试之Monkey使用及monkey脚本编写
Android自动化测试之MonkeyRunner API脚本编写及脚本录制
继上一篇文章讲到Android自动化测试之Monkey测试之后,今天继续介绍MonkeyRunner这一个工具,该工具位于SDK的tools\bin目录下,有一个monkeyrunner.bat文件,我们看看官网怎么介绍的:
The monkeyrunner tool provides an API for writing programs that control an Android device or emulator from outside of Android code. With monkeyrunner, you can write a Python program that installs an Android application or test package, runs it, sends keystrokes to it, takes screenshots of its user interface, and stores screenshots on the workstation. The monkeyrunner tool is primarily designed to test applications and devices at the functional/framework level and for running unit test suites, but you are free to use it for other purposes.
The monkeyrunner tool provides these unique features for Android testing:
Multiple device control: The monkeyrunner API can apply one or more test suites across multiple devices or emulators. You can physically attach all the devices or start up all the emulators (or both) at once, connect to each one in turn programmatically, and then run one or more tests. You can also start up an emulator configuration programmatically, run one or more tests, and then shut down the emulator.
Functional testing: monkeyrunner can run an automated start-to-finish test of an Android application. You provide input values with keystrokes or touch events, and view the results as screenshots.
Regression testing - monkeyrunner can test application stability by running an application and comparing its output screenshots to a set of screenshots that are known to be correct.
Extensible automation - Since monkeyrunner is an API toolkit, you can develop an entire system of Python-based modules and programs for controlling Android devices. Besides using the monkeyrunner API itself, you can use the standard Python os and subprocess modules to call Android tools such as Android Debug Bridge.
You can also add your own classes to the monkeyrunner API. This is described in more detail in the section Extending monkeyrunner with plugins.
The monkeyrunner tool uses Jython, an implementation of Python that uses the Java programming language. Jython allows the monkeyrunner API to interact easily with the Android framework. With Jython you can use Python syntax to access the constants, classes, and methods of the API.
大概意思是:
monkeyrunner工具提供了一个API,用于编写从Android代码之外控制Android设备或模拟器的程序。 使用monkeyrunner,你可以编写一个Python程序,安装Android应用程序或测试包,运行它,向其发送击键,截取其用户界面,并在工作站上存储屏幕截图。 monkeyrunner工具主要用于测试功能/框架级别的应用程序和设备以及运行单元测试套件,但您可以将其用于其他目的。
Monkeyrunner工具为Android测试提供了以下独特功能:
多设备控制:monkeyrunner API可以在多个设备或仿真器上应用一个或多个测试套件。您可以物理连接所有设备或一次启动所有模拟器(或两者),以编程方式依次连接每个模拟器,然后运行一个或多个测试。您也可以以编程方式启动仿真器配置,运行一个或多个测试,然后关闭仿真器。
功能测试:monkeyrunner可以运行Android应用程序的自动化开始至结束测试。您可以使用按键或触摸事件提供输入值,并将结果视为截图。
回归测试: monkeyrunner可以通过运行应用程序并将其输出截图与一组已知正确的截图进行比较来测试应用程序的稳定性。
可扩展的自动化:由于monkeyrunner是一个API工具包,因此您可以开发一整套基于Python的模块和程序来控制Android设备。除了使用monkeyrunner API本身,您可以使用标准的Python操作系统和子流程模块来调用Android工具,如Android调试桥。您也可以将您自己的类添加到monkeyrunner API。这在扩展带有插件的monkeyrunner一节中有更详细的描述。
monkeyrunner工具使用Jython,这是一种使用Java编程语言的Python实现。 Jython允许monkeyrunner API与Android框架轻松交互。使用Jython,您可以使用Python语法来访问API的常量,类和方法。
从这里也能看出来:
我们可以在Python程序中使用 from com.android.monkeyrunner import 来导入这些类
具体使用方法介绍可以查看官方API
非Android开发者环境搭建参考Monkeyrunner环境搭建
使用这些api可以直接在dos中操作,也可以直接在PyCharm中编写python脚本,这个跟使用python一样的,这里先从dos窗口里操作讲解,其中的代码写法跟Python的写法一样的
直接输入monkeyrunner命令
SWT folder '..\framework\x86_64' does not exist.
Please set ANDROID_SWT to point to the folder containing swt.jar for your platform
这时候需要修改下monkeyrunner.bat内容:
使用这些模块前需要导入上面说的几个模块,命令如下
from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice,MonkeyImage
先使用下MonkeyRunner的命令,比如查看帮助,使用help命令,接收一个参数,表示输出格式,有两种选择:“text”、“html”;这里我选择text
可以看到报错了,这是因为SDK集成monkeyrunner工具时没有将这些资源一起集成进来,所以需要我们将monkeyrunner源码中的resources目录拷贝到SDK的tools\lib目录,接下来你就能看到如下内容
这样可读性比较差,我们可以将这些内容保存到文件中,进行如下操作
>>> content = MonkeyRunner.help("html")
>>> f = open("help.html","w")
>>> f.write(content)
>>> f.close()
>>>
这些是常用的Python语法;这里没有指定文件目录,而monkeyrunner命令工具是在tools\bin目录,所以这个文件默认保存在这里
接下来使用alert方法
MonkeyRunner.alert('MonkeyRunner alert','mango','ok')
MonkeyRunner.choice('MonkeyRunner choice',['a','b','c'],'mango')
同时该方法有一个返回值,是你选择项的index值,比如我选a,那么就会返回0
MonkeyRunner.input('MonkeyRunner input','input','mango','ok','cancle')
接下来一个方法sleep,就是中断脚本执行一段时间
最后一个方法很重要,有两个参数,第一个是超时时间,第二个是设备id,如果不指定,就默认连接adb已经连接上的设备;通过该方法可以跟设备建立连接
可以看到它返回了一个MonkeyDevice对象
这个模块是MonkeyRunner中的一个核心模块,看名字就知道,它负责和设备的一个交互,控制设备执行相应的操作:包括安装应用、启动应用、进行点击操作、对设备截图、获取设备上的一些系统变量等功能;接下来看下这些方法的具体使用
惯例先导入这些模块
其中的as意思是起别名,要不然后面每次使用这些模块都用MonkeyRunner这些名词,有点太麻烦了
第一步就是与设备建立连接:
>>> device = mr.waitForConnection()
第二步就是安装应用
>>> device.installPackage("G:\Android Video\TestActivity.apk")
>True
返回值是true说明应用安装成功
第三步要启动应用,但是先查看下应用包名,可以通过如下命令
>>> device.shell("pm list package |grep testing")
u'package:com.example.android.testingfun\n'
再通过如下命令启动应用
>>> package = "com.example.android.testingfun"
>>> activity = "com.example.android.testingfun.ClickFunActivity"
>>> mCompontent = package + "/" + activity
>>> device.startActivity(component=mCompontent)
接下来可以发送一个按键事件
按下HOME键 device.press('KEYCODE_HOME',md.DOWN_AND_UP)
按下BACK键 device.press('KEYCODE_BACK',md.DOWN_AND_UP)
一些常用的按键有
Home键:KEYCOD_HOME
Back键:KEYCODE_BACK
send键:KEYCODE_CALL
end键:KEYCODE_ENDCALL
上导航键:KEYCODE_DPAD_UP(现在手机已经没有这个键)
下导航键:KEYCODE_DPAD_DOWN(现在手机已经没有这个键)
左导航:KEYCODE_DPAD_LEFT 现在手机已经没有这个键
右导航键:KEYCODE_DPAD_RIGHT 现在手机已经没有这个键
ok键:KEYCODE_DPAD_CENTER
上音量键:KEYCODE_VOLUME_UP
下音量键:KEYCODE_VOLUME_DOWN
power键:KEYCODE_POWER
camera键:KEYCODE_CAMERA
menu键:KEYCODE_MENU
search键:KEYCODE_SEARCH
call键:KEYCODE_CALL
第四步我们试下touch方法,点击屏幕,但是它需要接受坐标,所以我们需要知道要点击的view的具体坐标,这时候需要使用uiautomatorviewer工具,直接打开一个新的dos窗口输入uiautomatorviewer,就可以在弹出的界面中选中想要点击的view,看看它所在的范围
我这里是点中一个输入框,接下来可以使用type方法输入字符串到输入框
>>> device.touch(300,900,md.DOWN_AND_UP)
>>> device.type('s')
要知道一般点击输入框的时候都会将系统输入法调出来,所以接下来需要将输入框隐藏,发送一个返回键事件
>>> device.press('KEYCODE_BACK',md.DOWN_AND_UP)
接下来就可以继续点击其它View了
接下来还可以通过getProperty获取设备的一些参数信息,但是该方法接收一个参数,就是属性名,可以通过如下方法获取系统所有的属性名
>>> device.getPropertyList()
>>> device.getProperty('display.width')
u'1080'
>>> device.getProperty('display.height')
u'1920'
>>> device.getProperty('display.density')
u'3.0'
MonkeyDevice还有一个非常重要的方法在官网没有写出来,就是getHierarchyViewer(),返回一个View层级对象,可以使用它根据view的id找到对应的View及其它一些操作
viewer = device.getHierarchyViewer()
note = viewer.findViewById('id/title')
text = viewer.getText(note)
print text.encode('utf-8')
point = viewer.getAbsoluteCenterOfView(note)
x = point.x
y = point.y
这也是monkeyrunner中一个比较重要的模块,它是一个图像处理的模块,主要是对截屏生成的图片进行一些操作,具体哪些操作在上面介绍过了,具体使用如下
首先需要得到一个MonkeyImage对象
>>> image = device.takeSnapshot()
使用convertToBytes (string format)方法
返回字节码数据,默认是png格式,还有其它参数
image.convertToBytes('jpg')
image.convertToBytes('gif')
获取指定坐标的像素值
>>> image.getRawPixel(0,0)
(-1, 48, 63, 159)
>>> image.getRawPixelInt(0,0)
-13615201
两个方法都是获得指定一个坐标的argb值,但前者返回的是一个元组,后者返回的是整型。那么两个类型的值是怎么相应起来的呢?事实上就是第一个方法的元组的返回值(a,r,g,b)中的每一个值转换成8个bit的二进制值然后按顺序从左到右排列起来再转换成十进制整型就是第二个方法的返回值了。
接下来看看获取子图像的方法
>>> newImage = image.getSubImage((0,0,720,1280))
>>> print (newImage.sameAs(image,1.0))
False
sameAs的第二个参数:1.0代表必须100%同样,0.5代表能够有50%的Error Tolerance.
最后就是保存图像了
>>> newImage.writeToFile('v.png','png')
True
#导入我们需要用到的包和类并且起别名
import sys
from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner import MonkeyDevice as md
from com.android.monkeyrunner import MonkeyImage as mi
# 连接设备
#第一个参数为等待连接设备时间
#第二个参数为具体连接的设备
device = mr.waitForConnection(1.0,'30d9w256');
if not device:
print >> sys.stderr,"fail"
sys.exit(1)
# 安装APP installPackage 会返回一个布尔值,来说明安装的结果
apkFilePath = "D:/monkey.apk";
device.installPackage(apkFilePath);
# 启动 APP
# 自行 google package 和 activity 是什么
package = "com.mango.monkey";
activity = "com.mango.monkey.MainActivity";
runComponent = package + "/" + activity;
device.startActivity(component=runComponent);
# 这里一般让脚本暂停一段时间,使得APP成功启动起来
mr.sleep(3);
# 下面开始模拟界面事件
# 1. 点击事件
device.touch(100, 100, "DOWN_AND_UP");
mr.sleep(1);
# 2. 输入事件
# 先把焦点聚焦到输入控件上,然后 type 内容
device.touch(x, y, "DOWN_AND_UP");
mr.sleep(1);
device.type("value");
mr.sleep(1);
# 3. 模拟按键按下,如后退,home按键按下
# 这里第一个参数值的定义可以
devie.press("KEYCODE_BACK", "DOWN_AND_UP");
mr.sleep(1);
# 4. 界面滑动
device.drag((x1, y1), (x2, y2));
mr.sleep(1);
# 点击事件触发,界面开始变化,截屏比较
imagetobecompared = MonkeyRunner.loadImageFromFile([image file path]);
screenshot = device.takeSnapshot();
if screenshot.sameAs(imagetobecompared, 0.9):
print "2张图片相同";
else:
print "2张图片不相同";
# 保存截图
result.writeToFile('./shot1.png','png')
#卸载app
device.removePackage(package)
从上面的实例中我们可以看出使用monkeyrunner测试应用程序的具体步骤为:
1、导入需要的包
2、连接设备,等待设备连接并返回连接的设备
3、安装测试程序包(可写绝对路径)
4、设置启动程序包名和Activity名
5、执行一系列的touch、drag等事件
6、截图保存
7、卸载APP
这个功能是真的有用,不需要你磨磨唧唧写脚本了,你直接按测试用例对app操作一遍,然后把这些操作录制成脚本,以后就跑这个脚本就可以了。只不过这玩意官网上也没提,要想了解怎么使用需要去看源码,在monkeyrunner\scripts目录下有这么几个文件
其中monkey_recorder.py是录制程序,monkey_playback.py是回放程序,要想方便使用这两个Python程序,需要将它们放到与monkeyrunner脚本所在的目录,脚本很简单,如下
#!/usr/bin/env monkeyrunner
# Copyright 2010, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner.recorder import MonkeyRecorder as recorder
device = mr.waitForConnection()
recorder.start(device)
然后输入如下命令即可执行该脚本
D:\AndroidSDK\tools\bin>monkeyrunner monkey_recorder.py
顶部栏按钮简介:
接下来我们就可以打开需要测试的app,然后按照用例点击测试,右边空白部分就会显示我们点击操作一番操作后如下图
最后导出脚本为test.mr并保存,脚本文件可以打开修改
TOUCH|{'x':546,'y':300,'type':'downAndUp',}
WAIT|{'seconds':1.0,}
PRESS|{'name':'BACK','type':'downAndUp',}
TOUCH|{'x':546,'y':440,'type':'downAndUp',}
WAIT|{'seconds':1.0,}
TOUCH|{'x':519,'y':1088,'type':'downAndUp',}
WAIT|{'seconds':1.0,}
TOUCH|{'x':550,'y':584,'type':'downAndUp',}
WAIT|{'seconds':1.0,}
TOUCH|{'x':553,'y':1824,'type':'downAndUp',}
WAIT|{'seconds':1.0,}
TOUCH|{'x':540,'y':736,'type':'downAndUp',}
WAIT|{'seconds':2.0,}
TOUCH|{'x':590,'y':1804,'type':'downAndUp',}
WAIT|{'seconds':2.0,}
PRESS|{'name':'MENU','type':'downAndUp',}
PRESS|{'name':'HOME','type':'downAndUp',}
脚本内容跟前面讲的MonkeyRunner三大模块的api不一样,因为这是录制工具自定义的一套api
这个就是将上面录制的脚本进行回放,也就是让设备自动按照录制的脚本进行自动化测试,这里以上面录制的脚本为例,输入如下命令
monkeyrunner monkey_playback.py D:\AndroidSDK\tools\bin\test.mr
其中第二个参数是脚本路径,注意一定要是绝对路径
通过前面的讲解,可以知道MonkeyRunner进行自动化测试主要是通过touch方法,传入的参数是坐标,这样就带来了一个很严重的问题,Android厂家这么多,各种分辨率的屏幕数不胜数,你在这块屏幕上写的坐标在另一块屏幕上并不能起作用,那基于坐标的同一份脚本就不能通用,那这是不是就意味着GG了呢?
不是的,monkeyrunner还有两个模块就是easymonkeydevice和by,可以实现基于控件的自动化测试脚本,但是这两个模块在官方文档也没有任何的资料,所以我们也只有去它的源码查看
在其源码的src\com\android\monkeyrunner\easy目录下有这两个文件
里面有个README文件,打开看看
开头就说了easy模块包含了一些更容易使用MonkeyRunner的代码,使用控件的id代替了xy坐标去找到控件;下面也给出了使用样例
其中By模块主要提供了一个id(String id)方法,返回By对象;而EasyMonkeyDevice模块主要提供了这么几个方法
接下来看看具体的使用
使用前需要导入这几个模块,如下
>>> from com.android.monkeyrunner import MonkeyRunner as mr,MonkeyDevice as md,MonkeyImage as mi
>>> from com.android.monkeyrunner.easy import EasyMonkeyDevice as emd,By
然后连接设备启动应用
>>> device = mr.waitForConnection()
>>> device.startActivity('com.android.mangodialog/.MainActivity')
接下来就需要获取EasyMonkeyDevice对象了,通过构造方法获取
easy = emd(device)
但是你可能会遇到下面的错误
java.lang.RuntimeException: Could not connect to the view server
这是因为我们的设备没有启动view server功能,同时这个功能一般只在模拟器或者开发版本的设备上才会提供,一般商用版本需要root才能使用
在模拟器上启动view server命令如下
adb shell service call window 1 i32 4939
如果返回值是
Result: Parcel(00000000 00000001 '........')
说明启动成功,查看view server状态命令是adb shell service call window 3
然后就可以通过如下命令进行点击操作
easy.touch(By.id('id/clear'),md.DOWN_AND_UP)
可以通过HierarchyViewer模块来解析控件ID,获取对应的View,它支持的方法有
通常使用device.getHierarchyViewer()方法获取该对象,常用方法如下
content = hViewer.findViewById('id/text') # 通过id查找对应元素返回viewnode对象来访问属性
value = hViewer.getText(content)
但是会出现跟EasyMonkeyDevice同样的问题,需要启动view service
下面是一个计算器的自动化测试脚本
#导入我们需要用到的包和类并且起别名
import sys
from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner import MonkeyDevice as md
from com.android.monkeyrunner import MonkeyImage as mi
from com.android.chimpchat.hierarchyviewer import HierarchyViewer #根据ID找到ViewNode,对viewnode的一些操作等
from com.android.monkeyrunner.easy import EasyMonkeyDevice #提供了根据ID进行访问方法touch、drag等
from com.android.monkeyrunner.easy import By #根据ID返回PyObject的方法
from com.android.hierarchyviewerlib.models import ViewNode as vn #代表一个控件,可获取控件属性
#connect device 连接设备
#第一个参数为等待连接设备时间
#第二个参数为具体连接的设备
device = mr.waitForConnection(1.0,'34ca2501')
if not device:
print >> sys.stderr,"fail"
sys.exit(1)
#定义要启动的Activity
componentName="com.android.mangodialog/.MainActivity"
#启动特定的Activity
device.startActivity(component=componentName)
mr.sleep(5.0)#延时时间结合自身机器环境需要调整
easy_device = EasyMonkeyDevice(device)#初始化EasyMonkeyDevice模块,必须放在startActivity之后,用来通过ID访问控件
hViewer = device.getHierarchyViewer() # 对当前UI视图进行解析
#执行1到9的累加操作
#1、通过坐标方式来获取
device.touch(93,241,device.DOWN_AND_UP) #1
mr.sleep(2.0)
device.touch(238,490,device.DOWN_AND_UP) #+
mr.sleep(2.0)
device.touch(249,235,device.DOWN_AND_UP) #2
mr.sleep(2.0)
device.touch(238,490,device.DOWN_AND_UP) #+
mr.sleep(2.0)
device.touch(370,231,device.DOWN_AND_UP) #3
mr.sleep(2.0)
device.touch(238,490,device.DOWN_AND_UP) #+
mr.sleep(2.0)
device.touch(106,315,device.DOWN_AND_UP) #4
mr.sleep(2.0)
device.touch(238,490,device.DOWN_AND_UP) #+
mr.sleep(2.0)
device.touch(253,323,device.DOWN_AND_UP) #5
mr.sleep(2.0)
device.touch(238,490,device.DOWN_AND_UP) #+
mr.sleep(2.0)
device.touch(397,328,device.DOWN_AND_UP) #6
mr.sleep(2.0)
device.touch(238,490,device.DOWN_AND_UP) #+
mr.sleep(2.0)
device.touch(96,411,device.DOWN_AND_UP) #7
mr.sleep(2.0)
device.touch(238,490,device.DOWN_AND_UP) #+
mr.sleep(2.0)
device.touch(270,406,device.DOWN_AND_UP) #8
mr.sleep(2.0)
device.touch(238,490,device.DOWN_AND_UP) #+
mr.sleep(2.0)
device.touch(402,423,device.DOWN_AND_UP) #9
mr.sleep(2.0)
device.touch(387,670,device.DOWN_AND_UP) #=
mr.sleep(2.0)
#takeSnapshot截图,获取程序运行界面截图
result0 = device.takeSnapshot()
#save to file 保存到文件
result0.writeToFile('./shot1.png','png');
#2、通过控件ID来获取
easy_device.touch(By.id('id/clear'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/btn1'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/tv'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/btn2'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/view'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/btn3'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/img'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/btn4'),device.DOWN_AND_UP)
mr.sleep(3.0)
#takeSnapshot截图,获取程序运行界面截图
result1 = device.takeSnapshot()
#save to file 保存到文件
result1.writeToFile('./shot2.png','png');
if(result1.sameAs(result0,1.0)):#截图对比
print("pic true")
else:
print("pic false") #全图100%对比 因为时间不同会输出false
#对比局部图片(去掉状态栏,因为状态栏时间会改变)
pic0= result0.getSubImage((4,41,400,700)) #局部结果图形对比
pic1= result1.getSubImage((4,41,400,700))
print (pic1.sameAs(pic0,1.0)) #输出true
#通过HierarchyViewer
content = hViewer.findViewById('id/text') # 通过id查找对应元素返回viewnode对象来访问属性
text0 = hViewer.getText(content)
print text0.encode('utf-8')#打印结果
#通过By来获取
text1=easy_device.getText(By.id('id/text'))
print text1.encode('utf-8')#打印结果
device.press('KEYCODE_BACK', device.DOWN_AND_UP)
意思就是修改bug后再进行验证是否修改正确,monkeyrunner中是通过比对两次截图来判断是否正确修改bug
#从本地加载shot1-1.png,上一次的截图
result0 = mr.loadImageFromFile('./shot1-1.png')
#对比局部图片
pic0= result0.getSubImage((4,41,400,700)) #局部结果图形对比
pic1= result1.getSubImage((4,41,400,700))
print (pic1.sameAs(pic0,1.0)) #输出true就是bug已经修改