1. 程式人生 > >[Android 測試] 效能迴歸測試之 MonkeyRunner使用、外掛擴充套件、結合批處理

[Android 測試] 效能迴歸測試之 MonkeyRunner使用、外掛擴充套件、結合批處理

一、 MonkeyRunner簡介

monkeyrunner也是一款安卓sdk自有的測試工具,開源,位於\sdk\tools下面,它主要做效能測試,迴歸測試,並且可以自定義測試擴充套件,和monkey是完全不同的。
monkeyrunner 工具提供了一組API ,通過這些 API 函式可以在Android程式碼之外(當然也可以直接在原始碼直接使用)控制 Android裝置和模擬器,通過 monkeyrunner,也可以寫出一個Python指令碼來安裝、執行、測試、傳送模擬操作流結果截圖對比等等。

二、 MonkeyRunner安裝

三、 錄製、回放功能

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 座標獲取、 其他工具獲取 指令碼中需要對不同同分辨率相容
控制元件ID HierarchyViewer來解析控制元件ID,檢視ID方式為:hierarchyviewer.bat工具 垃圾,好多手機用不了,現在都用UIAutomatorViewer, 速度慢
控制元件ID MonkeyDevice 只能進行簡單的常用動作

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,謝謝觀看