解放程序猿寶貴的右手(或者是左手)
——Android自動化測試技巧
Google大神鎮(zhèn)樓 : http://developer.android.com/tools/testing-support-library/index.html#UIAutomator
前言:
覺得文章太長不想往后翻的朋友雏逾,你們會后悔的允悦,當然,你也可以選擇先看后面的扎酷,你會覺得很爽咏瑟,但是相信我拂到,你還是會回來看前面的。那么码泞,還是慢慢往后翻吧兄旬。
導(dǎo)入:
人們懶的走路,才創(chuàng)造了汽車余寥;
人們懶的爬樓领铐,才創(chuàng)造了電梯;
人們懶的掃地宋舷,才創(chuàng)造了自動掃地機器人绪撵。
人類的進步,離不開這些喜歡偷懶的人祝蝠,現(xiàn)在音诈,程序猿將偷懶上升到了一個新的高度——利用程序來進行自動化軟件測試幻碱,將測試工程師從繁瑣的測試用例中解脫出來,從此可以一邊喝著咖啡细溅,一邊看著程序自動測試褥傍,不必看著測試用例重復(fù)無數(shù)次的測試步驟,也不必擔心操作失誤而導(dǎo)致不必要的錯誤喇聊,更不用擔心壓力測試而導(dǎo)致的身心俱疲恍风。想了解程序猿是如何實現(xiàn)自動化測試的嗎,這里有你想要的答案誓篱。
聲明
轉(zhuǎn)載真的請注明出處:
http://blog.csdn.net/eclipsexys
順便打個廣告:
我的慕課網(wǎng)視頻: http://www.imooc.com/space/teacher/id/347333
為啥要測試
- 發(fā)現(xiàn)錯誤朋贬、為程序員提供修改意見
- 驗證軟件是否滿足設(shè)計需求和技術(shù)需求
- 驗證生產(chǎn)環(huán)境下真實的用戶使用過程,分析用戶體驗
——總而言之一句話——軟件測試燕鸽,決定著軟件的質(zhì)量兄世。
以前在TCL的時候,每個軟件版本都要不停的跑MonkeyTest啊研,一個是檢測系統(tǒng)ROM的穩(wěn)定性御滩,一個是檢測各種第三方應(yīng)用在ROM上的使用情況,所以經(jīng)常會報出很多Monkey跑出來的Bug党远,這些Bug經(jīng)過我們分析削解,會初步判斷是第三方App的問題還是系統(tǒng)的ROM問題,如果是第三方的問題沟娱,我們也會提交給App的運營商氛驮,但是大部分的運營商給我們的回復(fù)都是,我們的App不支持跑Monkey济似,其實Monkey可以發(fā)現(xiàn)一些潛在的問題矫废,特別是一些很難復(fù)現(xiàn)的問題,我以前的leader曾經(jīng)說過一句話我覺得非常好砰蠢,沒有什么bug是不能復(fù)現(xiàn)的蓖扑,沒有復(fù)現(xiàn),只是沒有找到必先的步驟台舱,所以每一個bug都不是偶然的律杠,我們應(yīng)該盡量嚴謹?shù)姆治雒恳粋€可能存在的bug。
再以前的時候竞惋,對日的公司對測試更是無比看重柜去,各種UT測試式樣書,不僅僅是要寫好怎么測試拆宛、測試什么嗓奢,而且測試的數(shù)據(jù)、中間過程還要截圖胰挑,保留證據(jù)蔓罚。
有哪些測試
- Google CTS測試:兼容性測試椿肩,測試ROM的兼容性標準
- Google GTS測試
- 實驗室機器人測試瞻颂、機械臂自動化模擬測試
- Monkey Test壓力測試
- End User終端用戶測試
對于美國的手機運營商豺谈,例如T-Mobile、Sprite贡这、AT&T茬末,他們都有一系列的手機性能測試,他們的測試項目盖矫、測試方法丽惭、測試過程,其實都是他們的商業(yè)機密辈双,一個是保證測試結(jié)果的嚴謹性责掏,一個也保證了手機廠商能夠不作弊的完成測試,所以湃望,千萬不要學(xué)華X手機换衬,在T-Mobile實驗室偷拍手機測試機器人的軟件、技術(shù)參數(shù)及其他機密信息证芭,而被T-Mobile列入北美黑名單瞳浦。逗比新聞
Android自動化測試工具
自動化測試是把以人為驅(qū)動的測試行為轉(zhuǎn)化為機器執(zhí)行的一種過程
- 將大量重復(fù)的測試步驟用腳本代替,讓機器完成重復(fù)工作
- 規(guī)范測試用例废士,保證測試質(zhì)量
- 高——大——上
自動化測試的工具
MonkeyRunner
monkeyrunner工具提供一個API來控制Android設(shè)備叫潦。可以寫一個python腳本來安裝應(yīng)用官硝,運行應(yīng)用矗蕊,發(fā)送鍵值,截圖氢架。monkeyrunner對python進行了封裝傻咖,加入了一些針對Android設(shè)備的類〈锕浚可以完全用python腳本來實現(xiàn)這些功能没龙。Instrumentation
基于Android單個Activitiy的測試框架。Robotium
一個優(yōu)秀的測試框架缎玫,基于Instrumentation的二次封裝硬纤。QTP
一個Web上的自動化測試工具,通過錄制腳本來實現(xiàn)自動化測試赃磨。UiAutomator
目前最佳的UI自動化測試框架筝家。基于Android 4.X+系統(tǒng)邻辉,專業(yè)UI自動化測試溪王,可以模擬用戶對手機的各種行為腮鞍。編寫快速、可以使用大部分的Android API莹菱、無需簽名移国,無任何Activity限制。
各個測試框架的優(yōu)缺點如下表所示:
測試框架 | 使用語言 | 運行方式 | 限制 | 適用環(huán)境
-------- | ---
MonkeyRunner| Python | ADB道伟、Python | 測試靠坐標 | 壓力測試
Instrumentation | Java | ADB | 只能單個Activity測試迹缀,且需要應(yīng)用相同簽名,代碼量大 | 白盒測試
Robotium | 同上 | 同上 | 同上 | 同上
UiAutomator | Java | ADB或者脫機| Android 4.X+| UI測試
綜上所述蜜徽,我們使用UiAutomator作為我們Android自動化測試的首選框架祝懂。
UiAutomator環(huán)境搭建
開發(fā)環(huán)境:eclipse(非常抱歉,還沒學(xué)會如何使用AS來開發(fā)Java代碼拘鞋、進行jar打包砚蓬,請了解的朋友留言!E枭;彝堋)
編譯環(huán)境:Ant、Java傅事、Android SDK
UiAutomator基本對象之UiDevice
通常用于獲取系統(tǒng)的設(shè)備信息缕允、系統(tǒng)按鍵、全局操作等蹭越。
獲取坐標參數(shù)
返回值 | 方法 | 解釋
-------- | ---
boolean| click(int x, int y) | 在點(x, y)點擊
int | getDisplayHeight() | 獲取屏幕高度
int | getDisplayWidth() | 獲取屏幕寬度
Point | getDisplaySizeDp() | 獲取顯示尺寸大小
系統(tǒng)信息
返回值 | 方法 | 解釋
-------- | ---
void | getCurrentPackageName() | 獲取當前界面包名
void | getCurrentActivityName() | 獲取當前界面Activity
void | dumpWindowHierarchy(fileName) | dump當前布局文件到/data/local/tmp/目錄
滑動障本、拖拽
返回值 | 方法 | 解釋
-------- | ---
boolean | drag(startX, startY, endX, endY, steps)| 拖拽坐標處對象到另一個坐標
boolean | swipe(segments, segmentSteps) | 在Points[]中以segmentSteps滑動
boolean | swipe(startX, startY, endX, endY, steps) | 通過坐標滑動
系統(tǒng)按鍵
返回值 | 方法 | 解釋
-------- | ---
void | wakeUp() | 按電源鍵亮屏
void | sleep() | 按電源鍵滅屏
boolean | isScreenOn() | 亮屏狀態(tài)
void | setOrientationLeft() | 禁用傳感器,并左旋屏幕响鹃,固定
void | setOrientationNatural() | 禁用傳感器驾霜,恢復(fù)默認屏幕方向,固定
void | setOrientationRight() | 禁用傳感器买置,并右旋屏幕粪糙,固定
void | unfreezeRotation() | 啟用傳感器,并允許旋轉(zhuǎn)
boolean | isNaturalOrientation() | 檢測是否處于默認旋轉(zhuǎn)狀態(tài)
void | getDisplayRotation() | 返回當前旋轉(zhuǎn)狀態(tài)忿项,0蓉冈、1、轩触、2寞酿、3分別代表0、90脱柱、180伐弹、270度旋轉(zhuǎn)
void | freezeRotation() | 禁用傳感器,并凍結(jié)當前狀態(tài)
boolean | takeScreenshot(storePath) | 當前窗口截圖榨为、1.0f縮放惨好、90%質(zhì)量保存在storePath
void | takeScreenshot(storePath, scale, quality) | 同上煌茴,但指定縮放和壓縮比率
void | openNotification() | 打開通知欄
void | openQuickSettings() | 打開快速設(shè)置
等待窗口
返回值 | 方法 | 解釋
-------- | ---
void | waitForIdle() | 等待當前窗口處于空閑狀態(tài)、默認10s
void | waitForIdle(long timeout) | 自定義超時等待當前窗口處于空閑狀態(tài)
boolean | waitForWindowUpdate(packageName, timeout) | 等待窗口內(nèi)容更新
示例代碼
// 輸入按鍵
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_A);
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_B);
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_C);
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_A,1);
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_B,1);
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_C,1);
// 點擊
UiDevice.getInstance().click(400, 400);
int h=UiDevice.getInstance().getDisplayHeight();
int w=UiDevice.getInstance().getDisplayWidth();
UiDevice.getInstance().click(w/2, h/2);
// Swipe日川、Drag
int startX, startY, endX, endY, steps;
startX=300;
startY=400;
endX=startX;
endY=startY + 200;
steps=100;
UiDevice.getInstance().drag(startX, startY, endX, endY, steps);
int h=UiDevice.getInstance().getDisplayHeight();
int w=UiDevice.getInstance().getDisplayWidth();
UiDevice.getInstance().swipe(w, h/2, 30, h/2, 10);
Point p1=new Point();
Point p2=new Point();
Point p3=new Point();
Point p4=new Point();
p1.x=250;p1.y=300;
p2.x=600;p2.y=350;
p3.x=800;p3.y=800;
p4.x=200;p4.y=900;
Point[] pp={p1,p2,p3,p4};
UiDevice.getInstance().swipe(pp, 50);
// 滅屏蔓腐、亮屏
UiDevice.getInstance().sleep();
UiDevice.getInstance().wakeUp();
// Notification
UiDevice.getInstance().openNotification();
sleep(3000);
UiDevice.getInstance().openQuickSettings();
UiDevice.getInstance().dumpWindowHierarchy("ui.xml");
送個視頻,讓大家真實體驗下:
<iframe height=498 width=510 src="http://player.youku.com/embed/XOTUzMjI2NzYw" frameborder=0 allowfullscreen></iframe>
視頻代碼:
UiDevice.getInstance().pressBack();
UiDevice.getInstance().pressBack();
UiDevice.getInstance().pressHome();
sleep(1000);
UiDevice.getInstance().pressMenu();
sleep(1000);
UiDevice.getInstance().pressBack();
sleep(1000);
UiDevice.getInstance().pressRecentApps();
sleep(1000);
UiDevice.getInstance().pressHome();
sleep(1000);
UiDevice.getInstance().click(240, 1100);
sleep(2000);
UiDevice.getInstance().click(670, 1100);
sleep(2000);
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_H);
sleep(1000);
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_H, 1);
sleep(1000);
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_J);
sleep(1000);
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_J, 1);
sleep(1000);
UiDevice.getInstance().swipe(30, 400, 600, 400, 10);
sleep(1000);
UiDevice.getInstance().pressHome();
sleep(1000);
UiDevice.getInstance().drag(660, 860, 360, 360, 50);
sleep(1000);
UiDevice.getInstance().sleep();
sleep(1000);
UiDevice.getInstance().wakeUp();
sleep(1000);
UiDevice.getInstance().swipe(370, 1000, 370, 200, 50);
sleep(1000);
UiDevice.getInstance().takeScreenshot(new File("/sdcard/uidevice.png"));
UiAutomator基本對象之UiSelector
通常使用UiSelector逗鸣,通過各種屬性節(jié)點和關(guān)系來定位組件合住,類似SQL語句的where條件绰精。
uiautomatorviewer
要查看界面UI元素的層級關(guān)系撒璧,我們需要使用SDK/tools/下面的uiautomatorviewer工具來幫助我們進行查看,運行uiautomatorviewer笨使,點擊dump卿樱,我們就可以獲取當前界面的UI快照。
下面這張圖就是一個示例:
通過uiautomatorviewer硫椰,我們可以找到很多對象的屬性繁调,上圖右下角的方框中的,都是對象所具有的屬性靶草。我們可以通過這些屬性來定位需要的元素對象蹄胰,這里要注意的是,uiautomator可以使用鏈式查找奕翔,即一個條件無法定位裕寨,那么可以通過多個條件組合,來定位一個元素派继。
通過text宾袜、description屬性定位
返回值 | 方法 | 解釋
-------- | ---
UiSelector | text(text) | 通過text完全定位
UiSelector | textContains(text) | 通過text包含定位
UiSelector | textMatches(regex) | 通過text正則定位
UiSelector | textStartsWith(text) | 通過text起始文字定位
UiSelector | description(text) | 通過text完全定位
UiSelector | descriptionContains(text) | 通過description包含定位
UiSelector | descriptionMatches(regex) | 通過description正則定位
UiSelector | descriptionStartsWith(text) | 通過description起始文字定位
通過resourceId定位
返回值 | 方法 | 解釋
-------- | ---
UiSelector | resourceId(id) | 通過resourceId定位
UiSelector | resourceIdMatches(regex) | 通過resourceId正則定位
通過class、package定位
這種方式適用于當前頁面上只有一種類型的組件的情況驾窟,例如只有一個ListView庆猫。
返回值 | 方法 | 解釋
-------- | ---
UiSelector | className(className) | 通過class定位
UiSelector | classNameMatches(regex) | 通過class正則定位
UiSelector | packageName(name) | 通過package定位
UiSelector | packageNameMatches(regex) | 通過package正則定位
通過index、instance定位
返回值 | 方法 | 解釋
-------- | ---
UiSelector | index(index) | 通過index定位
UiSelector | instance(instance) | 通過instance定位
通過其它屬性定位
返回值 | 方法 | 解釋
-------- | ---
UiSelector | enabled(val) | 通過enabled屬性定位
…… | …… | ……
對象的所有屬性都可以使用绅络,這里不再列舉月培。
示例代碼
// 找到對象 點擊對象
UiSelector l=new UiSelector().text("聯(lián)系人");
UiObject object=new UiObject(l);
object.click();
// 匹配方式
// 完全匹配:聯(lián)系人
// 包含匹配:系人
// 正則匹配:.*系.*
// 起始文字匹配:聯(lián)系
UiSelector l=new UiSelector().textContains("系人");
UiSelector l=new UiSelector().textMatches(".*系.*");
UiSelector l=new UiSelector().textStartsWith("聯(lián)系");
UiObject object=new UiObject(l);
object.click();
UiAutomator基本對象之UIObject
UIObject是UiAutomator的核心屬性之一。它代表了整個UI界面中的所有對象元素恩急。
它的功能包括:獲取UI元素杉畜,點擊、拖拽假栓、滑動寻行、對象屬性判斷、手勢等匾荆。
點擊與長按
返回值 | 方法 | 解釋
-------- | ---
boolean | click() | 點擊對象
boolean | clickAndWaitForNewWindow() | 點擊對象并等待新窗口出現(xiàn)
boolean | clickAndWaitForNewWindow(timeout) | 點擊對象并等待新窗口出現(xiàn)拌蜘,指定延遲
boolean | clickBottomRight() | 點擊對象右下角
boolean | clickTopLeft() | 點擊對象左上角
boolean | longClick() | 長按對象
boolean | longClickBottomRight() | 點擊對象右下角
boolean | longClickTopLeft() | 點擊對象左上角
拖拽與滑動
返回值 | 方法 | 解釋
-------- | ---
boolean | dragTo(destObj, steps) | 以steps拖動對象到destObj
boolean | dragTo(destX, destY, steps) | 以steps拖動對象到坐標
boolean | swipeDown(steps) | 向下拖動
boolean | swipeLeft(steps) | 向左拖動
boolean | swipeRight(steps) | 向右拖動
boolean | swipeTop(steps) | 向上拖動
文本輸入與清除
返回值 | 方法 | 解釋
-------- | ---
boolean | setText(text) | 設(shè)置內(nèi)容為text
boolean | clearTextField() | 清除文本
獲取對象屬性
返回值 | 方法 | 解釋
-------- | ---
Rect| getBounds() | 獲取對象矩形范圍
int | getChildCount() | 獲取子View數(shù)量
……| …… | ……
還有很多杆烁,不列舉了。
獲取對象屬性狀態(tài)
返回值 | 方法 | 解釋
-------- | ---
boolean | isCheckable() | 獲取對象checkable狀態(tài)
……| …… | ……
還有很多简卧,不列舉了兔魂。
獲取對象存在狀態(tài)
返回值 | 方法 | 解釋
-------- | ---
boolean | waitForExists(timeout) | 等待對象出現(xiàn)
boolean | waitUntilGone(timeout) | 等待對象消失
boolean | exists() | 對象是否存在
手勢狀態(tài)
返回值 | 方法 | 解釋
-------- | ---
boolean | performMultiPointerGesture(touches) | 執(zhí)行單指手勢
boolean | performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps) | 執(zhí)行雙指手勢
boolean | pinchIn(percent, steps) | 雙指向內(nèi)收縮
boolean | pinchOut(percent, steps) | 雙指向外張開
示例代碼
// 拖拽
UiObject object1=new UiObject(new UiSelector().text("聯(lián)系人"));
UiObject object2=new UiObject(new UiSelector().text("圖庫"));
object1.dragTo(300,1200, 10);
object1.dragTo(object2, 30);
object1.swipeUp(5);
// 輸入、清空
UiObject edit=new UiObject(new UiSelector()
.resourceId("com.hjwordgames:id/edit_password"));
edit.setText("xuyisheng");
sleep(2000);
edit.clearTextField();
// 判斷
UiObject wlan=new UiObject(new UiSelector()
.resourceId("com.android.settings:id/switchWidget"));
if(!wlan.isChecked()){
wlan.click();
}
// 手勢
UiObject object=new UiObject(new UiSelector()
.resourceId("com.android.gallery3d:id/photopage_bottom_controls"));
object.pinchIn(80, 20);
object.pinchOut(80, 20);
Point startPoint1, startPoint2, endPoint1, endPoint2;
startPoint1=new Point();
startPoint2=new Point();
endPoint1=new Point();
endPoint2=new Point();
startPoint1.x=150;startPoint1.y=200;
startPoint2.x=100;startPoint2.y=500;
endPoint1.x=900;endPoint1.y=200;
endPoint2.x=950;endPoint2.y=500;
object.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, 50);
再送一個視頻举娩、不收費:
<iframe height=498 width=510 src="http://player.youku.com/embed/XOTUzMjI3NDk2" frameborder=0 allowfullscreen></iframe>
視頻代碼:
UiObject word = new UiObject(new UiSelector().text("滬江開心詞場"));
word.clickAndWaitForNewWindow();
UiObject username = new UiObject(new UiSelector().text("滬江用戶名/郵箱/手機"));
username.setText("xuyisheng");
sleep(1000);
UiObject pwd = new UiObject(
new UiSelector().resourceId("com.hjwordgames:id/edit_password"));
pwd.setText("123465");
sleep(2000);
pwd.clearTextField();
sleep(1000);
pwd.setText("123465");
UiDevice.getInstance().pressBack();
sleep(1000);
UiObject login = new UiObject(new UiSelector().text("登 錄"));
login.clickAndWaitForNewWindow();
UiDevice.getInstance().pressBack();
UiDevice.getInstance().pressBack();
word.dragTo(300, 300, 50);
sleep(1000);
word.swipeDown(50);
UiAutomator基本對象之UIScrollable
專業(yè)處理滾動一百年析校。
滾動
返回值 | 方法 | 解釋
-------- | ---
boolean | flingBackward() | 步長為5快速向后滑動
boolean | flingForward() | 步長為5快速向前滑動
boolean | flingToBeginning(maxSwipes) | 不超過maxSwipes滑動到最前,步長為5
boolean | flingToEnd(maxSwipes) | 不超過maxSwipes滑動到最后铜涉,步長為5
boolean | flingToEnd(maxSwipes) | 不超過maxSwipes滑動到最后智玻,步長為5
……| …… | 同樣還可以使用Scroll,不一一列舉
獲取列表子元素
返回值 | 方法 | 解釋
-------- | ---
boolean | getChildByDescription(childPattern, text) | 默認滾動芙代,查找childPattern UiSelector所對應(yīng)的text子元素
boolean | getChildByDescription(childPattern, text, allowScrollSearch) | 是否允許滾動吊奢,查找childPattern UiSelector所對應(yīng)的text子元素
…… | …… | 還有text、instance同樣可以使用纹烹,不一一列舉页滚。
boolean | scrollIntoView(obj) | 滾動到obj所處的位置
boolean | scrollIntoView(selector) | 滾動到條件元素所處的位置
boolean | scrollTextIntoView(text) | 滾動到文本對象所處的位置
boolean | scrollToBeginning(maxSwipes) | 滾動到開始位置
boolean | scrollToBeginning(maxSwipes, steps) | 指定步長,滾動到開始位置
boolean | scrollToEnd(maxSwipes) | 滾動到最后位置
boolean | scrollToEnd(maxSwipes, steps) | 指定步長铺呵,滾動到最后位置
boolean | setMaxSearchSwipes(swipes) | 設(shè)置最大可掃動次數(shù)
boolean | getMaxSearchSwipes() | 獲取最大可掃動次數(shù)裹驰、默認30
UiScrollable | setSwipeDeadZonePercentage(swipeDeadZonePercentage) | 設(shè)置滑動無效區(qū)域(到頂部的百分比)
double | getSwipeDeadZonePercentage() | 獲取滑動無效區(qū)域(到頂部的百分比)
滾動方向
返回值 | 方法 | 解釋
-------- | ---
boolean | setAsHorizontalList() | 設(shè)置水平滾動
boolean | setAsVerticalList() | 設(shè)置垂直滾動
示例代碼
// 滑動
UiScrollable scroll=new UiScrollable(new UiSelector().className("android.widget.ListView"));
scroll.flingBackward();
scroll.flingForward();
scroll.flingToBeginning(20);
scroll.flingToEnd(30);
// 滑動到某元素
UiScrollable scroll=new UiScrollable(new UiSelector().className("android.widget.ListView"));
UiObject baiQiang=scroll.getChildByText(new UiSelector().className("android.widget.TextView"), "zhujia");
baiQiang.click();
scroll.getChildByInstance(new UiSelector().className("android.widget.TextView"), 25).click();
// 滑動到某元素
UiScrollable scroll=new UiScrollable(new UiSelector().className("android.widget.ListView"));
UiSelector selector=new UiSelector().text("zhujia");
UiObject object=new UiObject(selector);
scroll.scrollIntoView(selector);
scroll.scrollIntoView(object);
scroll.scrollTextIntoView("zhujia");
scroll.scrollDescriptionIntoView("zhujia");
scroll.scrollToBeginning(50,5);
scroll.scrollToEnd(50,5);
// 滑動方向
UiScrollable scroll=new UiScrollable(new UiSelector().className("android.support.v4.view.ViewPager"));
scroll.setAsHorizontalList();
scroll.scrollBackward();
sleep(2000);
scroll.scrollForward();
sleep(2000);
scroll.setAsVerticalList();
scroll.scrollForward();
視頻大放送:
<iframe height=498 width=510 src="http://player.youku.com/embed/XOTUzMjI3ODIw" frameborder=0 allowfullscreen></iframe>
視頻代碼:
UiScrollable scrollable = new UiScrollable(
new UiSelector().className("android.widget.ListView"));
scrollable.flingForward();
sleep(500);
scrollable.flingBackward();
sleep(500);
scrollable.flingForward();
UiObject target = new UiObject(new UiSelector().text("德國工業(yè)就是這么強大!不得不服"));
scrollable.scrollIntoView(target);
target.click();
UiAutomator基本對象之UICollection
通常用于獲取滿足某種搜索條件的組件集合片挂,通過鏈式搜索確定最終需要的組件幻林。
先按照一定的條件枚舉容器內(nèi)的子元素,再從符合條件的子元素中進一步定位宴卖。
一般使用容器類組件作為父類滋将,用于尋找不好定位的子元素。
示例代碼
UiCollection collection=new UiCollection(new UiSelector().className("android.widget.ListView"));
UiSelector childPattern=new UiSelector().className("android.widget.TextView");
String text="Music";
UiObject music=collection.getChildByText(childPattern, text);
music.click();
UiAutomator基本對象之UiWatcher
通常我們會讓腳本來按照我們所需要的順序來執(zhí)行症昏,但有時候随闽,總有一些天災(zāi)人禍,比如10086發(fā)短信來了肝谭。
所以掘宪,我們的腳本必須要有一定的容錯性。
UiWatcher正是這樣一個容錯的對象攘烛,當我們在順序執(zhí)行腳本時魏滚,如果中間突然插入了一些不明事件,我們可以使用UiWatcher來攔截異常坟漱,處理完異常后鼠次,再返回原來的腳本執(zhí)行順序。
UiAutomator基本對象之Configuration
Configuration,自然是對默認操作的配置腥寇,通常情況下成翩,我們使用默認的Configuration就足夠了,當然赦役,如果你有一些特殊需求麻敌,就可以通過Configuration類來設(shè)置。它能更改我們前面提到的所有默認屬性的設(shè)置掂摔。包括默認延遲术羔、輸入延遲、等待超時等等乙漓。
UiAutomator基本對象之查看報告
下面是一個典型的UiAutomator測試報告:
INSTRUMENTATION_STATUS: numtests=1
INSTRUMENTATION_STATUS: stream=
com.hj.autotest.AutoTest:
INSTRUMENTATION_STATUS: id=UiAutomatorTestRunner
INSTRUMENTATION_STATUS: test=testDevice
INSTRUMENTATION_STATUS: class=com.hj.autotest.AutoTest
INSTRUMENTATION_STATUS: current=1
INSTRUMENTATION_STATUS_CODE: 1
INSTRUMENTATION_STATUS: numtests=1
INSTRUMENTATION_STATUS: stream=.
INSTRUMENTATION_STATUS: id=UiAutomatorTestRunner
INSTRUMENTATION_STATUS: test=testDevice
INSTRUMENTATION_STATUS: class=com.hj.autotest.AutoTest
INSTRUMENTATION_STATUS: current=1
INSTRUMENTATION_STATUS_CODE: 0
INSTRUMENTATION_STATUS: stream=
Test results for WatcherResultPrinter=.
Time: 31.489
OK (1 test)
INSTRUMENTATION_STATUS_CODE: -1
這些報告被INSTRUMENTATION_STATUS_CODE分為了三個部分级历,1表示運行前,-1表示運行完成簇秒。
如果出錯了鱼喉,你可以在報告中找到相應(yīng)的錯誤信息。
你同樣需要知道的是趋观,UiAutomator也是JUnit工程,你同樣可以在里面使用斷言來進行某些變量锋边、結(jié)果值的測試皱坛,這些同樣會在報告中體現(xiàn)出來。
最后豆巨,UiAutomator大部分內(nèi)容都講完了剩辟,最后一個視頻:
<iframe height=498 width=510 src="http://player.youku.com/embed/XOTUzMjI3OTg0" frameborder=0 allowfullscreen></iframe>
視頻代碼:
UiDevice.getInstance().pressHome();
new UiObject(new UiSelector().description("Apps"))
.clickAndWaitForNewWindow();
UiScrollable scrollable = new UiScrollable(
new UiSelector()
.resourceId(
"com.google.android.googlequicksearchbox:id/apps_customize_pane_content"));
scrollable.setAsHorizontalList();
UiObject word = new UiObject(new UiSelector().text("滬江開心詞場"));
while (!word.exists()) {
scrollable.scrollForward();
}
word.clickAndWaitForNewWindow();
UiObject username = new UiObject(new UiSelector().text("滬江用戶名/郵箱/手機"));
username.setText("xys10086");
sleep(1000);
UiObject pwd = new UiObject(
new UiSelector().resourceId("com.hjwordgames:id/edit_password"));
pwd.setText("Aa123465");
sleep(1000);
UiObject login = new UiObject(new UiSelector().text("登 錄"));
login.clickAndWaitForNewWindow();
if (new UiObject(new UiSelector().className(
"android.widget.FrameLayout").index(1)).exists()) {
new UiObject(new UiSelector().text("注冊"))
.clickAndWaitForNewWindow();
new UiObject(
new UiSelector()
.resourceId("com.hjwordgames:id/registerEditUsername"))
.setText("xys100861");
new UiObject(
new UiSelector()
.resourceId("com.hjwordgames:id/registerEditPassword"))
.setText("Aa123456");
new UiObject(
new UiSelector()
.resourceId("com.hjwordgames:id/regiserEditEmail"))
.setText("35998151@qq.com");
new UiObject(new UiSelector().text("確認注冊"))
.clickAndWaitForNewWindow();
UiObject ok = new UiObject(
new UiSelector().resourceId("com.hjwordgames:id/btnOK"));
if (ok.waitForExists(500)) {
ok.clickAndWaitForNewWindow();
}
}
如何使用UiAutomator
配置工程環(huán)境
在Eclipse中創(chuàng)建一個java工程,并添加platforms文件夾下面的android.jar和uiautomator.jar 兩個引用往扔。如下圖:
創(chuàng)建測試用例
UiAutomator中的測試類都要繼承UiAutomatorTestCase贩猎,每個測試用例的方法的方法名都要以test開頭。如下圖:
在測試用例的方法中萍膛,我們就可以編寫測試腳本代碼吭服。
生成build.xml文件
在終端中,輸入:
android create uitest-project -n <name> -t <android-sdk-ID> -p <path>
這里的android sdk id指的是在終端中蝗罗,輸入android list返回的你使用的sdk的id艇棕。
這里還要PS下,一定要配置好環(huán)境變量串塑,這是我們后面一鍵自動化的基礎(chǔ)沼琉。
例如:
android create uitest-project -n Demo -t 30 -p "F:\EclipseWorkSpace\AutoTest"
如下圖:
修改build.xml文件
生成的build.xml文件我們還無法直接使用,我們需要修改它的一個屬性桩匪,打開build.xml文件打瘪,將help改為build,如下圖:
打包Jar
使用Ant,我們利用build.xml打包生成jar闺骚,命令如下:
ant -buildfile "F:\EclipseWorkSpace\AutoTest"
編譯過程如下圖:
Push Jar包到手機
我們需要將jar包push到手機中的/data/local/tmp/目錄才能啟動測試桃移。如下圖:
adb push "F:\EclipseWorkSpace\AutoTest\bin\Demo.jar" /data/local/tmp/
執(zhí)行測試用例
在終端中輸入啟動測試命令(#后如果不指定具體的用例名,則測試所有的方法)葛碧,如下:
adb shell uiautomator runtest Demo.jar --nohup -c com.hj.autotest.AutoTest#testBrowser
到此為止借杰,整個測試用例的測試就全部結(jié)束了。
讓自動化測試自動起來
看完前面的步驟进泼,相信很多人已經(jīng)不想再看下去了蔗衡,好吧,那你們損失大了乳绕,所謂自動化測試绞惦,就是為了減少人工的操作,像這樣反復(fù)的編譯洋措、修改济蝉、push、運行菠发,這跟手動去測試又有什么區(qū)別呢王滤?
OK,讓自動化再上升一個境界滓鸠。
我們可以發(fā)現(xiàn)雁乡,其實這些操作,與我們進行測試一樣糜俗,也是一些機械動作踱稍,ok,那么我們完全可以使用同樣的思路——使用腳本來解決這些問題悠抹。
我們創(chuàng)建一個腳本工具——UiAutomatorTool珠月,來封裝這些機械的步驟。代碼非常簡單楔敌,無非是使用Java調(diào)用終端命令啤挎,來執(zhí)行前面的各種操作。
代碼如下:
package com.hj.autotest;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class UiAutomatorTool {
// 工作空間目錄
private static String WORKSPACE_PATH;
/**
* 指定自動測試的參數(shù)
*
* @param jarName
* 生成jar的名字
* @param testPackageclass
* 測試包名+類名
* @param testFunction
* 測試方法名梁丘,空字符串代表測試所有方法
* @param androidId
* SDK id
*/
public UiAutomatorTool(String jarName, String testPackageclass,
String testFunction, String androidId) {
System.out.println("*******************");
System.out.println(" --AutoTest Start--");
System.out.println("*******************");
// 獲取工作空間目錄路徑
WORKSPACE_PATH = getWorkSpase();
System.out.println("自動測試項目工作空間:\t\n" + getWorkSpase());
// ***********啟動測試*********** //
// 創(chuàng)建Build.xml文件
creatBuildXml(jarName, androidId);
// 修改Build.xml文件中的Build類型
modfileBuild();
// 使用Ant編譯jar包
antBuild();
// push jar到手機
pushJarToAndroid(WORKSPACE_PATH + "\\bin\\" + jarName + ".jar");
// 測試方法侵浸,為空則測試全部方法
if (androidId.equals("")) {
runTest(jarName, testPackageclass);
} else {
runTest(jarName, testPackageclass + "#" + testFunction);
}
// ***********啟動測試*********** //
System.out.println("*******************");
System.out.println("---AutoTest End----");
System.out.println("*******************");
}
/**
* 創(chuàng)建build.xml文件
*/
public void creatBuildXml(String jarName, String androidID) {
System.out.println("--------創(chuàng)建build.xml 開始---------");
execCmd("cmd /c android create uitest-project -n " + jarName + " -t "
+ androidID + " -p " + "\"" + WORKSPACE_PATH + "\"");
System.out.println("--------創(chuàng)建build.xml 完成---------");
}
/**
* 修改build.xml文件位build type
*/
public void modfileBuild() {
System.out.println("--------修改build.xml 開始---------");
StringBuffer stringBuffer = new StringBuffer();
try {
File file = new File("build.xml");
if (file.isFile() && file.exists()) {
InputStreamReader read = new InputStreamReader(
new FileInputStream(file));
BufferedReader bufferedReader = new BufferedReader(read);
String lineTxt;
while ((lineTxt = bufferedReader.readLine()) != null) {
if (lineTxt.matches(".*help.*")) {
lineTxt = lineTxt.replaceAll("help", "build");
}
stringBuffer = stringBuffer.append(lineTxt).append("\t\n");
}
read.close();
} else {
System.out.println("找不到build.xml文件");
}
} catch (Exception e) {
System.out.println("讀取build.xml內(nèi)容出錯");
e.printStackTrace();
}
// 重新寫回build.xml
rewriteBuildxml("build.xml", new String(stringBuffer));
System.out.println("--------修改build.xml 完成---------");
}
/**
* 使用Ant編譯jar包
*/
public void antBuild() {
System.out.println("--------編譯build.xml 開始---------");
execCmd("cmd /c ant -buildfile " + "\"" + WORKSPACE_PATH + "\"");
System.out.println("--------編譯build.xml 完成---------");
}
/**
* adb push jar包到Android手機
*
* @param localPath
* localPath
*/
public void pushJarToAndroid(String localPath) {
System.out.println("--------push jar 開始---------");
localPath = "\"" + localPath + "\"";
System.out.println("jar包路徑:" + localPath);
String pushCmd = "adb push " + localPath + " /data/local/tmp/";
execCmd(pushCmd);
System.out.println("--------push jar 完成---------");
}
/**
* 測試方法
*
* @param jarName
* jar包名
* @param testName
* testName
*/
public void runTest(String jarName, String testName) {
System.out.println("--------測試方法 開始---------");
String runCmd = "adb shell uiautomator runtest ";
String testCmd = jarName + ".jar " + "--nohup -c " + testName;
execCmd(runCmd + testCmd);
System.out.println("--------測試方法 完成---------");
}
/**
* 獲取WorkSpace目錄
*
* @return WorkSpace目錄
*/
public String getWorkSpase() {
File directory = new File("");
return directory.getAbsolutePath();
}
/**
* Shell命令封裝類
*
* @param cmd
* Shell命令
*/
public void execCmd(String cmd) {
System.out.println("ExecCmd:" + cmd);
try {
Process p = Runtime.getRuntime().exec(cmd);
// 執(zhí)行成功返回流
InputStream input = p.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(
input, "GBK"));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 執(zhí)行失敗返回流
InputStream errorInput = p.getErrorStream();
BufferedReader errorReader = new BufferedReader(
new InputStreamReader(errorInput, "GBK"));
String eline;
while ((eline = errorReader.readLine()) != null) {
System.out.println(eline);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 重新寫回Build.xml
*
* @param path
* path
* @param content
* content
*/
public void rewriteBuildxml(String path, String content) {
File dirFile = new File(path);
if (!dirFile.exists()) {
dirFile.mkdir();
}
try {
BufferedWriter bw1 = new BufferedWriter(new FileWriter(path));
bw1.write(content);
bw1.flush();
bw1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
那我們怎么使用呢?拿一個測試類來說:
public class AutoTest extends UiAutomatorTestCase {
public static void main(String[] args) {
new UiAutomatorTool("Demo", "com.hj.autotest.AutoTest", "testUiSelector", "30");
}
……
}
我們只需要在測試類中new一個UiAutomatorTool氛谜,并指定jar包名掏觉、包名、用例名值漫、Android id即可澳腹。
接下來,只需要運行這樣Java程序,就完成了整個過程的自動化酱塔,一鍵編譯沥邻、一鍵運行。
好吧羊娃,再來一個視頻:
<iframe height=498 width=510 src="http://player.youku.com/embed/XOTUzMjcyMDg4" frameborder=0 allowfullscreen></iframe>
讓偷懶更進一步
前面我們已經(jīng)讓編譯唐全、push、運行自動化了蕊玷,但是說到底邮利,就連編寫腳本也是一件非常繁瑣的事情啊。OK垃帅,我們同樣可以創(chuàng)建一個H5的頁面延届,通過編寫圖形化的頁面,來替代我們每個動作腳本的編寫贸诚,畢竟這些腳本也是死的啊方庭。
讓偷懶發(fā)揚光大
這些腳本可不僅僅能做測試。
經(jīng)過前面一系列的代碼酱固、演示械念,我們已經(jīng)可以通過腳本來進行測試用例的自動化測試,但是媒怯,自動化不僅僅可以用來測試订讼,當我們在調(diào)試程序的時候,經(jīng)常需要登陸App以后才能進行測試扇苞,我們同樣可以把這些操作放到腳本中,啟動調(diào)試后寄纵,直接運行腳本鳖敷,完成這樣繁瑣的輸入、登陸步驟程拭。