Android自動化測試技巧

解放程序猿寶貴的右手(或者是左手)

——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

通過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 兩個引用往扔。如下圖:

引用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)試后寄纵,直接運行腳本鳖敷,完成這樣繁瑣的輸入、登陸步驟程拭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末定踱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子恃鞋,更是在濱河造成了極大的恐慌崖媚,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恤浪,死亡現(xiàn)場離奇詭異畅哑,居然都是意外死亡,警方通過查閱死者的電腦和手機水由,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門荠呐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事泥张『腔郑” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵媚创,是天一觀的道長渗钉。 經(jīng)常有香客問我,道長钞钙,這世上最難降的妖魔是什么鳄橘? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮歇竟,結(jié)果婚禮上挥唠,老公的妹妹穿的比我還像新娘。我一直安慰自己焕议,他們只是感情好宝磨,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盅安,像睡著了一般唤锉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上别瞭,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天窿祥,我揣著相機與錄音,去河邊找鬼蝙寨。 笑死晒衩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的墙歪。 我是一名探鬼主播听系,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼虹菲!你這毒婦竟也來了靠胜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤毕源,失蹤者是張志新(化名)和其女友劉穎浪漠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霎褐,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡址愿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瘩欺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片必盖。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡拌牲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出歌粥,到底是詐尸還是另有隱情塌忽,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布失驶,位于F島的核電站土居,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嬉探。R本人自食惡果不足惜擦耀,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涩堤。 院中可真熱鬧眷蜓,春花似錦、人聲如沸胎围。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽白魂。三九已至汽纤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間福荸,已是汗流浹背蕴坪。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敬锐,地道東北人背传。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像台夺,于是被迫代替她去往敵國和親续室。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,074評論 25 707
  • 作者:Ringoyan谒养,騰訊測試開發(fā)工程師。先后為植物大戰(zhàn)僵尸Online明郭,糖果傳奇等游戲擔任測試經(jīng)理买窟,其負責的“...
    飯盒閱讀 2,792評論 2 41
  • 不知該如何說起,亦不知該怎樣表述才較為妥當薯定,內(nèi)心五味雜陳始绍。有些事即使你忘了,也會造成陰影话侄,在你不經(jīng)意間一件類...
    她有刺閱讀 365評論 0 0
  • 春雨如酒 醉人心頭 翠枝蕩漾迎風起 點點競開且嬌羞 年年歲歲 難忘六月初六 青青槐豆 經(jīng)不起涼風颼颼 熟落的槐豆 ...
    李阿歡閱讀 604評論 0 1