Android自动化测试之MonkeyRunner

继上一篇文章讲到Android自动化测试之Monkey测试之后,今天继续介绍MonkeyRunner这一个工具。我们看看官网怎么介绍的:

MonkeyRunner简介

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的常量,类和方法。


从这里也能看出来:

  1. Monkey是通过adb shell 命令发送随机命令,主要用来做压力测试;而MonkeyRunner可以通过API来产生特定的命令或事件控制设备。
  2. MonkeyRunner是一类api集合,我们可以使用这些api开发我们想要的效果
  3. 虽然脚本使用 Python 语法编写,但它实际上是通过 Jython 来解释执行。 Jython 是 Python 的 Java 实现,它将 Python 代码解释成 Java 虚拟机上的字节码并执行,这种做法允许在 Python 中继承一个 Java 类型,可以调用任意的 Java API

MonkeyRunner API

  1. MonkeyRunner:这个类提供了一个将monkeyrunner连接到设备或模拟器的方法。它还提供了为monkeyrunner程序创建用户界面和显示内置帮助的方法
  2. MonkeyDevice:表示设备或者模拟器,该类提供了安装和卸载软件包,启动Activity以及将键盘或触摸事件发送给应用程序的方法。你也可以使用这个类来运行测试包
  3. MonkeyImage:该类提供了捕获屏幕,将位图图像转换为各种格式,比较两个MonkeyImage对象以及将图像写入文件的方法

    我们可以在Python程序中使用 from com.android.monkeyrunner import 来导入这些类

具体使用方法介绍可以查看官方API

非Android开发者环境搭建参考

Monkeyrunner环境搭建

看一个简单例子


#导入我们需要用到的包和类并且起别名
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

执行monkeyRunner脚本

  1. 打开dos窗口,进入sdk目录的tools目录,有的可能在tools目录下的次级目录bin目录下,反正是monkeyrunner.bat的目录
  2. 输入命令 monkeyrunner monkey.py 然后敲回车执行命令

MonkeyRunner脚本录制

这个功能是真的有用,不需要你磨磨唧唧写脚本了,你直接按测试用例对app操作一遍,然后把这些操作录制成脚本,以后就跑这个脚本就可以了。

编写如下脚本进行录制

import sys
from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner.recorder import MonkeyRecorder as recorder

device = mr.waitForConnection();
device = mr.waitForConnection(1.0,'30d9w256');
if not device:
    print >> sys.stderr,"fail"
    sys.exit(1)
recorder.start(device);

然后执行这个脚本 monkeyrunner recorder.py

注意:如果有人执行这句话之后有报错

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这个脚本了,这里面有错误,用编辑器打开它

一       old: 
if exist %frameworkdir%\%jarfile% goto JarFileOk
    set frameworkdir=lib

modify:
if exist %frameworkdir%\%jarfile% goto JarFileOk
    set frameworkdir=..\lib

二      old:
call "%java_exe%" -Xmx512m "-Djava.ext.dirs=%frameworkdir%;%swt_path%" -Dcom.android.monkeyrunner.bindir=..\framework -jar %jarpath% %*

modify:
call "%java_exe%" -Xmx512m "-Djava.ext.dirs=%frameworkdir%;%swt_path%" -Dcom.android.monkeyrunner.bindir=..\..\platform-tools -jar %jarpath% %*

当我们执行完录制脚本后,稍微等待一段时间,弹出一个设备操作模拟窗口,如下
这里写图片描述

顶部栏按钮简介:

  1. Wait 设置执行下一条命令的等待时间
  2. Press a Button 发送MENU HOME SEARCH按钮的Press Down Up事件
  3. Type Something 输入并发送字符串到设备
  4. Fling 用东南西北四个方向的虚拟键盘来滑动屏幕
  5. Export Actions 导出操作录制脚本
  6. Refresh Display 刷新屏幕

接下来我们就可以打开需要测试的app,然后按照用例点击测试,右边空白部分就会显示我们点击操作;最后导出脚本并保存,脚本文件可以打开修改,这个录制操作有个不好的地方就是录制相当卡顿

TOUCH|{'x':938,'y':796,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':60,'y':912,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':536,'y':1364,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':745,'y':1332,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':87,'y':1160,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':435,'y':1136,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':985,'y':1152,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':995,'y':1152,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':556,'y':296,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':540,'y':304,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TYPE|{'message':'dsadsad',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':519,'y':580,'type':'downAndUp',}
WAIT|{'seconds':2.0,} 
TOUCH|{'x':540,'y':1828,'type':'downAndUp',}

MonkeyRunner脚本回放

这个意思就是将上面录制的脚本进行回放,也就是让设备自动按照录制的脚本进行自动化测试

我们编写一个回放脚本


import sys
from com.android.monkeyrunner import MonkeyRunner

CMD_MAP = {
    'TOUCH': lambda dev, arg: dev.touch(**arg),
    'DRAG': lambda dev, arg: dev.drag(**arg),
    'PRESS': lambda dev, arg: dev.press(**arg),
    'TYPE': lambda dev, arg: dev.type(**arg),
    'WAIT': lambda dev, arg: MonkeyRunner.sleep(**arg)
    }

# Process a single file for the specified device.
def process_file(fp, device):
    for line in fp:
        (cmd, rest) = line.split('|')
        try:
            # Parse the pydict
            rest = eval(rest)
        except:
            print 'unable to parse options'
            continue

        if cmd not in CMD_MAP:
            print 'unknown command: ' + cmd
            continue

        CMD_MAP[cmd](device, rest)


def main():
    file = sys.argv[1]
    fp = open(file, 'r')

    device = MonkeyRunner.waitForConnection()

    process_file(fp, device)
    fp.close();


if __name__ == '__main__':
    main()

脚本意思一看就能懂,就不解释了,然后执行命令;如果是其它目录,这里要使用绝对路径

monkeyrunner playback.py action.mr
monkeyrunner e://monkey//playback.py action.mr

接下来app就会自动按照我们上一步点击的操作重新走一遍了;现在脚本文件是执行一遍就结束了,如果要循环执行,改下main方法就行了

def main():
    file = sys.argv[1]

    device = MonkeyRunner.waitForConnection()

    while True :
        fp = open(file, 'r')
        process_file(fp, device)
        fp.close();

UI元素访问

我们测试的时候要访问指定的view,一般通过view的id或者屏幕坐标;通过坐标访问会受屏幕大小影响,不同的坐标在不同的手机上是指在不同的位置;通过坐标访问能做一些常规动作,做不了复杂操作;通过HierarchyViewer来解析控件ID,查看ID方式为:hierarchyviewer.bat工具,在tools目录下,缺点就是很多手机用不了。

看下面这个通用脚本


#导入我们需要用到的包和类并且起别名
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回归验证

意思就是修改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已经修改

猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/80872808