編輯:關於Android編程
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運行在PC上,逐行的去解釋Python腳本代碼,將命令發送到Android設備上戒者模擬器上執行,monkeyrunner除了支持Python腳本來執行測試,還可以通過錄制回放的方式來執行測試。
通過monkeyrunner 腳本錄制功能可以實現,錄制和回放功能,但該功能目前提供操作徆簡單只能運行比較簡單的操作,而且要考慮不同機器的執行效率以及操作間的時間間隔
錄制操作
在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)
運行後將會看到出現這樣的界面:
會看到標題欄、手機界面、右邊事件列表
打開了monkeyrunner recorder之後,在左邊的手機界面即可操作手機,每一步操作都會在右邊的列表生成事件
要注意的一點是,錄制過程中monkeyrunner不會幫你設置等待時間,所以需要等待的界面,要點擊標題欄的wait自己添加時間等待。
WAIT|{'seconds':2.0,}
操作完成之後,點擊Export Action,把錄制腳本保存為mr文件,放到sdk\tool下
在運行回放腳本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()
三個類:
- MonkeyRunner :此類提供了將monkeyrunner連接到設備或模擬器的方法。它還提供了為monkeyrunner程序創建UI和顯示內置幫助的方法。
- MonkeyDevice :表示一個設備或模擬器。此類提供了用於安裝和卸載包,啟動Activity以及向應用程序發送鍵盤、觸摸事件、運行測試包等方法。
- MonkeyImage :這個類提供了捕獲屏幕方法,將位圖轉換為各種格式,比較兩個MonkeyImage對象和保存圖像等方法。
monkeyrunner -plugin
具體的看上面的api網址。
雖然 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)
通過坐標是比較快的,通過id定位比較慢。 坐標定位 手機不一樣坐標也就不一樣,id定位是每個手機都一樣。
這裡我在網上找了一個例子,可以看看源碼學習一下
#導入我們需要用到的包和類並且起別名 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)
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 ,或初始化失敗。這裡寫一個案例:
工具: Android Studio2.2
新建一個android library的module,名為testplugin。
導入三個jar,放到根目錄的libs,右擊add as library即可
jPython的jar下載: http://www.jython.org/downloads.html
MonkeyRunner.jar: 在sdk\tools 目錄下
chimpchat.jar: 在sdk\tools 目錄下
新建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{ @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); } }
修改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)
在右側的gradle找到exportJar,雙擊運行,運行完畢即可在module根目錄下的realease下看到AndroidPlugin.jar文件,
把生成的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,謝謝觀看
Android開發之Fragment學習 1.簡介: Fragment是Android 3.0引入的新API。 Fragment代表了 Activity的子模塊,因此可
一、概述現在大多數的電商APP的詳情頁長得幾乎都差不多,幾乎都是上面一個商品的圖片,當你滑動的時候,會有Tab懸浮在上面,這樣做用戶體驗確實不錯,如果Tab滑上去,用戶可
最近做的類似於微博的項目中,有個Android功能要使用到listview的向下拉刷新來刷新最新消息,向上拉刷新(滑動分頁)來加載更多。新浪微博就是使用這種方式的典型。當
API IntroductionContent providers are one of the primary building blocks(構件) of Andro