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打包,請了解的朋友留言!!!)
編譯環(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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

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

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