[Android 测试] 性能回归测试之 MonkeyRunner使用、插件扩展、结合批处理

一、 MonkeyRunner简介

monkeyrunner也是一款安卓sdk自有的测试工具,开源,位于\sdk\tools下面,它主要做性能测试,回归测试,并且可以自定义测试扩展,和monkey是完全不同的。
monkeyrunner 工具提供了一组API ,通过这些 API 函数可以在Android代码之外(当然也可以直接在源代码直接使用)控制 Android设备和模拟器,通过 monkeyrunner,也可以写出一个Python脚本来安装、运行、测试、发送模拟操作流结果截图对比等等。

api:
http://www.android-doc.com/tools/help/monkeyrunner_concepts.html
http://android-doc.com/tools/help/monkeyrunner.html

Android Studio monkeyrunner使用:
https://developer.android.com/studio/test/monkeyrunner/index.html

源码:
https://android.googlesource.com/platform/sdk/+/6db5720/monkeyrunner/

二、 MonkeyRunner安装

  1. JDK
  2. Python编译器
    http://www.python.org/download
  3. 配置环境变量
    把Monkeyrunner的Tool配置到path

三、 录制、回放功能

monkeyrunner运行在PC上,逐行的去解释Python脚本代码,将命令发送到Android设备上戒者模拟器上执行,monkeyrunner除了支持Python脚本来执行测试,还可以通过录制回放的方式来执行测试。

通过monkeyrunner 脚本录制功能可以实现,录制和回放功能,但该功能目前提供操作徆简单只能运行比较简单的操作,而且要考虑不同机器的执行效率以及操作间的时间间隔

1. 录制

录制操作

1.打开录制工具

在cmd命令行运行命令:

monkeyrunner recorder.py

即可运行recorder.py 脚本,用来启动录制工具,放置到sdk\tool的文件夹下,recorder.py源码如下

from com.android.monkeyrunner import MonkeyRunner as mr  
from com.android.monkeyrunner.recorder import MonkeyRecorder as recorder  
device = mr.waitForConnection()  
recorder.start(device)

运行后将会看到出现这样的界面:
这里写图片描述

2. 录制工具简介

会看到标题栏、手机界面、右边事件列表

按钮描述
Wait设置下一条命令的等待时间
Press a Button发送MENU HOME SEARCH按钮的Press Down Up事件
Type Something发送一些字符串
Fling模拟滑动
Export Action将我们的脚本导出来
Refresh Display刷新当前界面

3. 开始录制

打开了monkeyrunner recorder之后,在左边的手机界面即可操作手机,每一步操作都会在右边的列表生成事件

这里写图片描述

4. 录制处理

要注意的一点是,录制过程中monkeyrunner不会帮你设置等待时间,所以需要等待的界面,要点击标题栏的wait自己添加时间等待。

WAIT|{'seconds':2.0,} 

操作完成之后,点击Export Action,把录制脚本保存为mr文件,放到sdk\tool下

2. 回放

在运行回放脚本playback.py+录制文件,即可在手机上执行录制的操作,
(ps: 这太鸡肋了)
(ps2: 需要先链接好手机,录制关掉)

monkeyrunner playback.py open.mr

这里写图片描述

playback.py源码,也是放到sdk\tools目录下:

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)  
    }  

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() 

四、 API和命令

1. API

三个类:
- MonkeyRunner :此类提供了将monkeyrunner连接到设备或模拟器的方法。它还提供了为monkeyrunner程序创建UI和显示内置帮助的方法。
- MonkeyDevice :表示一个设备或模拟器。此类提供了用于安装和卸载包,启动Activity以及向应用程序发送键盘、触摸事件、运行测试包等方法。
- MonkeyImage :这个类提供了捕获屏幕方法,将位图转换为各种格式,比较两个MonkeyImage对象和保存图像等方法。

2. 命令

monkeyrunner -plugin <plugin_jar> <program_filename> <program_options>
参数说明
-plugin plugin_jar(可选)指定一个内含monkeyrunner的jar文件,如要指定超过一个文件,可以多次使用此参数。
program_filename如果提供此参数, monkeyrunner作为Python程序来运行。 如果未提供参数,则命令将启动交互式会话。
program_options(可选)所指定程序的所需的参数。

具体的看上面的api网址。

五、 手工编写脚本

1. 基础

虽然 monkeyrunner 脚本使用 Python 语法编写,但它实际上是通过 Jython 来解释执行。 Jython 是 Python 的 Java 实现,它将 Python 代码解释成 Java 虚拟机上的字节码并执行,这种做法允许在 Python 中继承一个 Java 类型,可以调用任意的 Java API 。

测试脚本的一般格式:

# 在程序中引入 monkeyrunner 模块
from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice

# 连接到正在运行的设备戒模拟器上,返回一个 MonkeyDevice 对象
device = MonkeyRunner.waitForConnection() 

# 安装待测应用, installPackage 会返回一个布尔值,来说明安装的结果
# device.installPackage ( "./CalcTest.apk") 

# 设置要启动的活动类名,有包名和活动类型组成
runComponent = "com.minstone.mdoctor/.activity.login.WelcomeActivity“ 

# 启动活动组件 
device.startActivity(component = runComponent)

2. UI元素访问

通过坐标是比较快的,通过id定位比较慢。 坐标定位 手机不一样坐标也就不一样,id定位是每个手机都一样。

方式工具说明
控件坐标MR recorder 坐标获取、 其他工具获取脚本中需要对不同同分辨率兼容
控件IDHierarchyViewer来解析控件ID,查看ID方式为:hierarchyviewer.bat工具垃圾,好多手机用不了,现在都用UIAutomatorViewer, 速度慢
控件IDMonkeyDevice只能进行简单的常用动作

3. demo

这里我在网上找了一个例子,可以看看源码学习一下

#导入我们需要用到的包和类并且起别名
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()
if not device:
    print >> sys.stderr,"fail"
    sys.exit(1)

#定义要启动的Activity
componentName="com.sky.jisuanji/.JisuanjizixieActivity"

#启动特定的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/qingchu'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/btn1'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/jia'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/btn2'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/jia'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/btn3'),device.DOWN_AND_UP)
easy_device.touch(By.id('id/jia'),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)

五、 插件扩展

1. 简介

jPython的jar下载: http://www.jython.org/downloads.html
MonkeyRunner.jar: 在sdk\tools 目录下
chimpchat.jar: 在sdk\tools 目录下

步骤

  • 输入:编写一个 插件启动类,需实现com.google.common.base.Predicate,该类在使用MonkeyRunner –plugin加载jar包时,首先启动,可以做一些初始化操作,一般可不实现任何内容。
  • 编写插件所需实现的功能,可引入%android-sdk%\tools\lib下的monkeyrunner,jython ,guava等以及其他的jar包进行编写
  • 将工程打包成.jar 文件,在 .jar文件的manifest中添加键MonkeyRunnerStartupRunner ,值为第一步的启动类,完成打包。

注意事项:

  • 插件包不能使用android SDK中的jar包。
  • 将生成的plugin.jar文件复制到%android-sdk%\tools\lib文件夹下或修改monkeyrunner.bat文件 ,“-Djava.ext.dirs=% frameworkdir%;% swt_path%;”这句中添加上plugin.jar文件所在文件夹路径。如果插件依赖其它jar包,需要跟插件包一起复制到上面的路径中。 否则可能会提在加载或使用插件是提示 ImportError : No module named XXX ,或初始化失败。

1. AS编写扩展插件

这里写一个案例:
工具: Android Studio2.2

1. 新建module

新建一个android library的module,名为testplugin。

2. 导入jar

导入三个jar,放到根目录的libs,右击add as library即可
jPython的jar下载: http://www.jython.org/downloads.html
MonkeyRunner.jar: 在sdk\tools 目录下
chimpchat.jar: 在sdk\tools 目录下

3. 编写Plugin

新建Pugin.java

package tpnet.testplugin;

import com.android.internal.util.Predicate;

import org.python.util.PythonInterpreter;

/**
 * Created by LITP on 2016/10/10.
 */

public class Plugin implements Predicate<PythonInterpreter>{


    @Override
    public boolean apply(PythonInterpreter pythonInterpreter) {
        pythonInterpreter.set("tpnet","Hello world");
        return false;
    }
}

这里写图片描述

新建MyTestPlugin.java

package tpnet.testplugin;

import com.android.chimpchat.core.TouchPressType;
import com.android.monkeyrunner.MonkeyDevice;
import com.android.monkeyrunner.doc.MonkeyRunnerExported;
import com.android.monkeyrunner.easy.By;
import com.android.monkeyrunner.easy.EasyMonkeyDevice;

import org.python.core.PyObject;

/**
 * Created by LITP on 2016/10/10.
 */

public class MyTestPlugin {
    private MonkeyDevice device = null;
    private EasyMonkeyDevice easy_device = null;

    @MonkeyRunnerExported(doc = "根据一个 MonkeyDevice实例创建Plugin.", args = { "device" }, argDocs = { "要扩展的MonkeyDevice实例." })
    public MyTestPlugin(MonkeyDevice device) {
        if(device != null)
        {
            this.device = device;
            easy_device = new EasyMonkeyDevice(device);
        }
    }

    @MonkeyRunnerExported(doc = "Hello Word Test.", args = { "" }, argDocs = { "print \"Hello World!\"." })
    public void test(PyObject[] args) {
        System.out.println("Hello World!");
    }

    @MonkeyRunnerExported(doc = "Test Reboot Phone.", args = { "" }, argDocs = { "Reboot MobilPhone." })
    public void testReboot(PyObject[] args)
    {
        device.reboot(args, null);
    }

    //这个方法可以在脚本调用
    @MonkeyRunnerExported(doc = "Plus 1-9.", args = { "" }, argDocs = { "Reboot MobilPhone." })
    public void plus() {
        easy_device.touch(By.id("id/qingchu"), TouchPressType.DOWN_AND_UP);
        easy_device.touch(By.id("id/btn1"),TouchPressType.DOWN_AND_UP);
        easy_device.touch(By.id("id/jia"),TouchPressType.DOWN_AND_UP);
        easy_device.touch(By.id("id/btn2"),TouchPressType.DOWN_AND_UP);
    }
}

这里写图片描述

3. 修改gadle

修改gradle,添加task,下一步运行这个task,即可打包jar

apply plugin: 'com.android.library'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    lintOptions {
        abortOnError false
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    testCompile 'junit:junit:4.12'
    compile files('libs/chimpchat.jar')
    compile files('libs/jython-standalone-2.7.0.jar')
    compile files('libs/monkeyrunner.jar')
}


task deleteOldJar(type: Delete) {
    delete 'release/AndroidPlugin.jar'
}

//task to export contents as jar 将from(*)该目录下的文件复制到release/下 并更改名称为Bsdiff.jar
task exportJar(type: Copy) {
    from('build/intermediates/bundles/release/')
    into('release/')
    include('classes.jar')
//Rename the jar
    rename('classes.jar', 'AndroidPlugin.jar')

}
exportJar.dependsOn(deleteOldJar, build)

这里写图片描述

4. 打包jar

在右侧的gradle找到exportJar,双击运行,运行完毕即可在module根目录下的realease下看到AndroidPlugin.jar文件,
这里写图片描述

5. 在脚本中使用编写的插件

把生成的AndroidPlugin.jar文件拷贝到sdk\tools\lib目录下

编写脚本,导入自己的jar

# 导入自己的jar
from tpnet.testplugin import MyTestPlugin as tp

运行jar里面的方法

# 初始化自己的类
ttp=tp(device)
# 执行方法
ttq.plus()

六、结合批处理

集合自己编写的jar,利用一个批处理来运行MonkeyRunner,能自动获取当前连接的设备,获取apk安装包,不用修改源码。双击运行这个bat批处理即可

Test.bat

@echo off
cls
rem 获取当前运行设备
adb devices > devices.txt
rem 获取APK文件
dir apk /B > apk.txt
rem 运行monkeyrunner 脚本
monkeyrunner myScript.py -plugin lib/plugin.jar
pause

myScript.py源码

# 导包
from tpnet.testplugin import MyTestPlugin as tp
from com.android.monkeyrunner import MonkeyRunner as mr

# 定义列表
deviceslist = []
snapshot = []
templist = []
devices = []

# 打开文件
f = open("devices.txt")

# 循环读取文件添加到templist
while True:
    line = f.readline()
    if line:
        templist.append(line.strip())
    else:
        break;

# 关闭文件流
f.close()
templist.pop()

# 循环添加设备
for i in range(len(templist)):
    deviceslist.append(templist[i].split('\t'))

# 读取启动activity
fc = open("componentName.txt")
complist = []
while True:
    comp = fc.readline()
    if comp:
        complist.append(comp.strip())
    else:
        break;
fc.close()

# 读取apk包
fp = open("apk.txt")
apklist = []
while True:
    apk = fp.readline()
    if apk:
        apklist.append(apk.strip())
    else:
        break;


#输出结果
print 'apk list :'
print apklist
print 'start componentName list :'
print complist
print 'devices list:'
print deviceslist

# 在手机上执行
for i in range(1,len(deviceslist)):
    print 'current devices:'
    print deviceslist[i]
    devices.append(mr.waitForConnection(1.0,deviceslist[i][0]))
    for j in range(len(apklist)):
        devices[i-1].installPackage('apk/'+apklist[j])
        for k in range(len(complist)):
            print 'current start activity:'
            print complist[k]
            devices[i-1].startActivity(component=complist[k])
            mr.sleep(5.0)
            ttq=tp(devices[i-1])
            ttq.plus()

七、 回归测试

回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。
在MonkeyRunner里面主要是通过获取上次的截图和这次的截图进行对比判断

#从本地加载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已经修改了,false不一样就是已经修好了

ok,谢谢观看

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KeepStudya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值