Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 【騰訊TMQ】5小時搞定谷歌原生自動化框架UiAutomator1.0

【騰訊TMQ】5小時搞定谷歌原生自動化框架UiAutomator1.0

編輯:關於Android編程

前言

谷歌對UI測試(UI Tetsting)的概念是:確保用戶在一系列操作過程中(例如鍵盤輸入、點擊菜單、彈出對話框、圖像顯示以及其他UI控件的改變),你的應用程序做出正確的UI響應。

UI測試(功能測試、黑盒測試)的好處是不需要測試者了解應用程序的內部實現細節,只需要知道當執行了某些特定的動作後是否會得到其預期的輸出。這種測試方法,在團隊合作中可以更好地分離的開發和測試角色。然而常見的UI測試多是以手動方式去執行,然後去驗證程序是否達到的預期的效果,很顯然這種方法耗時、繁瑣並且很容易出錯。因此我們需要一種可靠的方法來進行UI測試,通過測試框架,我們可以完成針對具體使用場景的測試用例,然後可以循環的、自動的來運行我們的測試case。

所以谷歌推出了下面的UI自動化測試框架

初探

在Android的SDk提供了以下的工具來支持我們進行UI自動化測試:

  uiautomatorviewer:一個用來掃描和分析Android應用程序的UI控件的GUI工具。

  uiautomator:一個包含創建測試、執行自動化測試API的java庫。(照例送上谷歌Uiautomator文檔:http://android.toolib.net/tools/help/uiautomator/index.html )

要使用這些工具,你必須安裝Android開發工具以下版本:

   Android SDK Tools:API 21 版本或者21以上版本;

   Android SDK Platform:API 16 版本或者16以上版本.

Uiautomator測試框架的工作流程

   下面是自動UI測試所需的步驟的簡短概述:

   1、安裝待測應用到手機,通過uiautomatorviewer分析應用程序界面的控件,並確保應用程序的控件可以被自動化框架訪問。

   2、創建自動化測試用例來模擬你和應用程序之間交互的步驟。

   3、將測試用例編譯成一個JAR文件,並發動到應用程序安裝的那台測試設備上。

   4、運行測試,查看測試結果。

   5、修改測試過程中發現的bug。

分析控件

  在你開始寫測試用例之前,使用uiautomatorviewer可以幫助你熟悉你的UI組件(包括視圖和控件)。你可以使用它對當前連接到你電腦上的手機屏幕進行一個快照,然後可以看到手機當前頁面的層級關系和每個控件的屬性。利用這些信息,你可以寫出針對特定UI控件的測試用例。

  在 ..\sdk\tools\ 目錄下打開 uiautomatorviewer.bat (打開前請手機連接電腦)
\vce1xLXatv649s28seqwtMWlIERldmljZSBTY3JlZW5zaG90o6y147v31q6687vhvau1scewyta7+r3nw+a1xL/s1dW4/NDCtb3V4sDvwLSho6GhoaGhoTwvcD4NCjxwPjKhotKzw+ay47y2o7qhoaGhoaGhoTwvcD4NCjxwPtPSyc+3vbXE1fu49sf40/KjrL7Nyse1scew0rPD5rK8vta1xLLjvLa52M+1oaPI57n7ttRBbmRyb2lkzuW087K8vtaxyL3PyuzPpLXEu7CjrMDtveLV4tK7suPTprjDsrvKx87KzOKho6GhoaE8L3A+DQo8cD4zoaKyu7/J08PH+NPyo7qhoaGhoaGhoTwvcD4NCjxwPtPSyc+3vbXE1fu49sf40/LW0LXEtdq2/rj2sLTFpVRvZ2dsZSBOQUYgTm9kZXOjrLC0z8K687P2z9a1xLvGyavH+NPytPqx7aOs1eLQqb/YvP7Kx7K7sbtVaWF1dG9tYXRvcrmkvt/KtrHwo6zO3reou/HIobW91eLQqb/YvP61xMq1wP2hozwvcD4NCjxwPtLUUVHK19KzzqrA/aGjPGJyIC8+DQo8aW1nIGFsdD0="" src="/uploadfile/Collfiles/20160624/20160624092332606.gif" title="\" />

我們可以看到,當按下該按鈕的時候,下方的三個tab出現黃色區域,這就代表這三個區域的控件,如果你想通過Uiautomator提供的API來獲得他們的屬性,或者對其進行點擊操作,是做不到的,因為你沒辦法拿到這些控件的實例。

4、屬性詳情:

右下方的整個區域,是當前選中的頁面或者是控件的屬性信息。這部分比較重要,我們以後寫代碼的時候就是需要通過查看屬性中的控件的id或者是text等來獲取控件的實例,然後點擊操作它。

以QQ左上角的頭像控件為例:

點擊左上角的頭像控件之後,右下方區域就會顯示這個控件的詳細信息。比如這裡我們可以得知它的resource-id就是com.tencent.mobileqq:id/conversation_head。

然後利用Uiautomator的API方法就可以得到該控件的實例。

`// 通過id來創建出UiSelector 對象
UiSelector =  new UiSelector().resourceId("com.tencent.mobileqq:id/conversation_head");
// 通過UiSelector 對象    創建出 UiObject  對象    
UiObject switcher = new UiObject(uiSelector );        
// 判斷該控件是否存在
if (switcher.exists())
{
 //點擊該控件  
switcher.click();
}`

上面的方法就是知道了該控件的id之後,模擬點擊該控件的過程,當然Uiautomator還提供了根據text來獲取控件。

這種點擊的方法比起Monkeyrunner來說它的好處就是:Monkeyrunner是坐標點擊,當一個腳本寫好後,換一個分辨率的手機去執行,點擊的位置可能就會出錯,而Uiautomator點擊是先找到該控件,然後再點擊該控件,因此可移植性比Monkeyrunner要好;另外代碼的易讀性也更好一些。

環境搭建

網上關於環境搭建的內容很多,也非常的詳細,這裡我就簡單的說下大體流程和注意事項吧。

1、在Eclipse中建立一個Java的工程。

2、右鍵選中你建立的工程,在Properties > Java Build Path中:

  a、點擊 Add Library > JUnit 添加JUnit3/4;

  b、點擊Add External JARs… 導入 uiautomator.jar and android.jar 這兩個jar包。

這裡需要注意,導入這兩個jar包的時候,注意Android的版本號,後面生成build.xml的時候需要知道你導入的這兩個jar是哪個sdk版本的。

3、導入成功之後,就可以寫代碼了。代碼的格式參考下面:

`package com.uia.example.my;

 // Import the uiautomator libraries
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiScrollable;
import com.android.uiautomator.core.UiSelector;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;

public class yourclass extends UiAutomatorTestCase {   

public void testDemo() throws UiObjectNotFoundException { 

     // 測試代碼

}
}`

4、使用 android create uitest-project -n %工程名% -t 5 -p %工程目錄% 來生存build.xml文件。需要注意的就是 這裡的 -t 後面的5 就是Android list後對應的你當初引入兩個jar包的sdk版本對應的id。我的是 id: 5 or “android-19”,所以我這裡是5  
\

5、生成的build.xml 用ant工具進行編譯。編譯後會生成“工程名.jar”包(注意這裡需要使用 ant build命令來打包,這樣有錯誤可以看到);  

6、將該jar包push到手機的 /data/local/tmp 目錄下  

7、在adb shell 中執行: uiautomator runtest 工程名.jar -c 包名.類名

簡單的例子

以一個簡單的例子開始吧。我們完成一個 ” 打開QQ,進入QQ空間,然後退出 ” 的case。

代碼如下:

`package QQ;

import java.io.IOException;

import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiSelector;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;

public class Test_qq extends UiAutomatorTestCase
{
public void testDemo() throws IOException, UiObjectNotFoundException {

    // 啟應用
    Runtime.getRuntime().exec("am start com.tencent.mobileqq/com.tencent.mobileqq.activity.SplashActivity");
    sleep(3000);

    // 點擊 "動態" tab
    UiDevice device = getUiDevice();
    int height = device.getDisplayHeight();
    int width = device.getDisplayWidth();
    device.click(width -50, height-50);
    sleep(1000);

    // 點擊 "好友動態" 按鈕
    UiObject obj_1 = new UiObject(new UiSelector().description("點擊進入好友動態"));
    obj_1.click();
    sleep(2000);

    // 點擊 左上角返回 "動態"按鈕
    UiObject obj_2 = new UiObject(new UiSelector().resourceId("com.tencent.mobileqq:id/ivTitleBtnLeft"));
    obj_2.click();
    sleep(1000);

    // 點擊菜單鍵
    device.pressMenu();
    sleep(1000);

    // 點擊退出qq        
    UiObject obj_3 = new UiObject(new UiSelector().text("退出QQ"));
    obj_3.click();
    sleep(1000);

    // 點擊確定
    UiObject obj_4 = new UiObject(new UiSelector().text("確定"));
    obj_4.click();
}
}`

腳本的運行效果如下:

\

代碼詳解

針對上面的例子的代碼,我對每一句代碼都做個詳細的解釋吧。

第一部分:啟動應用

\

exec() 這個函數的意思,相當於是在你在輸入adb shell 命令後,在Android手機系統的命令行下運行。所以上面這句話的意思和我們打開cmd框輸入” adb shell am start * ” 是一樣的的效果。

一般來說我們做App的自動化的時候,第一步都是把App打開,這個am start命令的就可以幫我們實現,類似與Monkeyrunner API中的startActivity() 函數。

第二部分:點擊 “動態” tab

\

UiDevice對象會在API部分詳細講解,它是一個我們在Uiautomator中經常使用的一個對象。

這裡我們首先用它獲取到當前手機的寬和高的像素。然後觀察到 “動態” tab位於右下方,因此在取得右下角的坐標點後,又進行了一個大概的坐標變化(這裡為了簡單只是向左和向上移動了50像素,如果要精確的可以進行等比轉化),然後點擊該坐標。

這裡之所以用點擊坐標的方法,一方面是因為這個控件Uiautomator不支持用API獲得實例(上一節所說的NAF Nodes,如下圖),另一方面也是想說明在一些控件沒有固定的id、text和desc的時候,我們應該怎麼處理。

第三部分:點擊 “好友動態”

\

要想操作一個控件(例如),首先得獲得一個UiObject對象,而UiObject對象可以通過UiSelector來構造,而UiSelector可以根據控件的id、text、content-desc來進行構造,這裡就是用content-desc來構造。

第四部分:點擊左上角返回按鈕

同第三部分的方法,找到id後直接獲得到UiObject對象,進行點擊。

第五部分:點擊菜單鍵

\

UiDevice 可以模擬點擊home、back、menu 這三個鍵,代碼應該大家都懂的怎麼變化了吧。

第六部分:退出

這一部分也是先通過獲取出控件屬性中的text值,然後構造出UiObject對象,完成點擊。   

以上部分內容就是整個操作QQ這個小例子的全部代碼講解,看完之後對寫Uiautomator代碼有了更進一步的了解了吧。接下來寫看看還有哪些API可以支持我們做更多的事情

API 列舉

UiDevice

概述: UiDevice用與訪問關設備狀態的信息,也可以使用這個類來模擬用戶在設備上的操作。可以通過下面的方法得到實例: UiDevice mdevice = getUiDevice();

摘要:

\

UiSelector

概述: 按照一定的條件(例如控件的text值,資源id),定位界面上的元素。UiSelector對象的最終目的是去構造一個UiObject對象。

摘要: 這裡由於篇幅問題只列出根據text構造:

\

比較常用,准確度也比較高,中文查找的時候,如果遇到 “UiOjbectNotFoundException” 的時候,記得把項目的編碼格式改為utf-8。

其他的方式的構造方法也基本上可次方法差不多,大家直接查看API即可。

UiCollection

概述: 篇幅所限,直接參考文檔   

摘要:http://android.toolib.net/tools/help/uiautomator/UiCollection.html

UiScrollable

概述:  篇幅所限,直接參考文檔

摘要:http://android.toolib.net/tools/help/uiautomator/UiScrollable.html

UiObject

概述:可以理解為 直接操作界面ui元素的實例。篇幅所限,直接參考文檔

摘要:http://android.toolib.net/tools/help/uiautomator/UiObject.html

如何更高效

到此為止,我們已經了解Uiautomator的基本知識,並且學習了API的用法,因此對於我們來說完成一個UI自動化測試腳本並不難,但是如何將UI自動化應用在實際的項目中,幫我們提高測試的效率呢?本節我們就說說,UI自動化應該怎麼去完成。

這裡以微信”小視屏”這個功能為例,假設我們要實現一個微信小視頻錄制的自動化測試的代碼。(鑒於隱私原因,默認在執行腳本前,微信已經是登錄狀態)

分析

當我們要完成一個自動化時,需要考慮這個用例需要怎麼設計,需要測試哪些項,怎麼驗證,出現錯誤時應該如何處理。

首先需要明確一點,並不是所有需求文檔上提到的功能,我們都必須用自動化方式去驗證,由於UI自動化本身的局限性,UI自動化的可行度不是100%的准確,因此我們只對“小視屏”的賣點功能進行自動化驗證,你也可以理解為對該功能做一個冒煙測試。

小視屏功能的入口一共是三個,分別是下面這三個地方:

\

我們除了要驗證這地方的入口外,還需要在其中一處完成對小視屏的發送,並且驗證小視屏發送成功。因此我們可以按照下面流程來進行測試腳本的編寫,流程圖如下所示:

編碼前准備

有了流程圖之後,不要迫不及待的編碼。編碼之前也需要考慮考慮,是否有一些公共的方法可以提取出來做為一個單獨的函數呢?

1、點擊操作

首先,點擊的操作是Uiautomator中用的最多的,而根據控件id和text來做為索引則是更多的。因此我們封裝如下的內容:

`/* 定義“通過哪種方式來獲得uiselector”的int標識,
   如果以後想添加別的方法(例如 通過description 來獲取),則可以參考此形式進行擴充 */

final int CLICK_ID = 2000;
final int CLICK_TEXT = 2001;

/* 實現具體的外部可以調用的函數 */

// 通過id來進行點擊操作
public boolean ClickById(String id)
{
    return ClickByInfo(CLICK_ID, id);
}
// 通過text來進行點擊操作
public boolean ClickByText(String text)
{
    return ClickByInfo(CLICK_TEXT, text);
}

/* 封裝出通用的點擊方法,供上面的public函數調用
   如果以後想添加別的方法(例如 通過description 來獲取),則可以在switch中擴充 */
private boolean ClickByInfo(int CLICK, String str)
{
    UiSelector uiselector = null;
    // switch根據不同的CLICK標識,創建出UiSelector的對象
    switch(CLICK)
    {
    case CLICK_ID:   uiselector = new UiSelector().resourceId(str); break;
    case CLICK_TEXT: uiselector = new UiSelector().text(str);       break;
    default: return false;
    }
    // 根據UiSelector對象構造出UiObject的對象
    UiObject uiobject = new UiObject(uiselector);
    // 判斷該控件是否存在
    if(!uiobject.exists())
    {
        return false;
    }
    // 點擊
    try
    {
        uiobject.click();
    } catch (UiObjectNotFoundException e)
    {
        e.printStackTrace();
    }
    return true;
}`

使用上面我的方法封裝之後,你只需要調用 ClickByText(“通訊錄”); 即可完成對”通信錄” 這個控件的點擊,並且在因為異常情況獲取不到該控件的時候,也不會報出異常。

然而,我們去點擊一個控件的時候,當它出現找不到的情況的時候,這有可能就是bug了,我們需要將其記錄下來,並且記錄下當時的現場,一般采用截圖的方法,以便我們查問題時候能更直觀的了解到當時機器一個運行情況。因此接下來,我要說說截圖和異常處理。

2、截屏和異常處理

上面的代碼中,當UiObject對象找不到的時候,我們只是返回了一個false,告訴調用者這次調用失敗了,但是為什麼失敗,怎麼避免這樣的失敗,並沒有記錄下來。因此在這段代碼中,我們需要加以下的內容:

 `private boolean ClickByInfo(int CLICK, String str)
{
    ....
    // 判斷該控件是否存在
    if(!uiobject.exists())
    {
        TakeScreen(getUiDevice(), str+"-not-find");
        return false;
    }
   ....
}

/* 保存屏幕截圖
   參數descrip 為 描述該截圖的內容 */
public void TakeScreen(UiDevice device, String descrip) 
{
    // 取得當前時間
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    String datestr = calendar.get(Calendar.HOUR_OF_DAY) + "_" + 
    calendar.get(Calendar.MINUTE) + "_" + calendar.get(Calendar.SECOND);

    // 保存文件
    File files = new File("/mnt/sdcard/"+datestr+"_"+descrip+".jpg");    
    device.takeScreenshot(files);        
   }`

這樣當我們在調用 ClickByText(“通訊錄”); 找不到控件的時候,我們的腳本就會自動截取當時屏幕的圖像保存在我們的手機中(如下圖),這樣我們只需打開圖片,就知道當時發生了什麼,為什麼沒有找到該控件。     

看似完美的方案,其實在實際運行中只是幫我們記錄了這個控件這一時刻點擊失敗的原因,而我們想要的是,腳本在調用了這個方法後,盡最大的可能幫我們點擊成功。舉一個簡單的例子:

\

這是我們寫腳本中經常遇到的一個問題,我們需要 ‘在A頁面上點擊“進入”按鈕,跳轉到B頁面,然後點擊B頁面上的“保存”按鈕’ 完成我們的操作。
  

一般我們的寫法是:

`ClickByText("進入");

 ClickByText("保存");`

然而當我們的手機特別卡,或者是頁面承載太多東西的時候,當你調用了點擊“進入”按鈕後,B頁面沒有及時的跳轉出來,這個時候調用B頁面上的“保存”按鈕,就會出現異常,而如果你沒有按照我上面的方案去實現的話,系統就會拋出異常,而使用了我上面的方案之後,系統雖然不會拋出異常,而且會在你找不到B頁面的“保存”按鈕時截取當前的屏幕,你完全可以根據截圖來判斷出來:當是沒有找到“保存”按鈕的原因是,當時的B頁面還沒有跳轉出來。然而在這個時候,我最希望的並不是看到日志告訴我說哪裡哪裡失敗了,而是想讓這次的點擊效果生效。

那麼怎麼解決這個問題呢?相信很多親手寫過Uiautomator腳本的朋友都知道,在兩個操作直接加如sleep,沒錯,這是解決方案,那麼究竟應該slepp多久呢?因為不同的手機響應時間是不一樣的,如果sleep太短就依然存在上述問題;如果sleep太長的話,無疑使得腳本的運行變的緩慢,多出寫無用的sleep。因此我們需要去掉if判斷的代碼,改為在while循環中等待這個控件的出現,一共等待5次,如果到了第五次,它還沒有出現的話,那麼我們就認為它真的不會出現了,這個時候去截屏比第一次就沒有找到更加的有意義。當然如果你還想提高你的UI自動化的健壯性,那麼這裡還可以加一個類似這樣的函數:

`/* 封裝出通用的點擊方法,供上面的public函數調用
   如果以後想添加別的方法(例如 通過description 來獲取),則可以在switch中擴充 */
private boolean ClickByInfo(int CLICK, String str)
{
    ....
    // 判斷該控件是否存在      
    int i = 0;
    while (!uiobject.exists() && i<5)
    {
        SolveProblems();
        sleep(500);
        if (i== 4)
        {
            TakeScreen(getUiDevice(), str+"-not-find");
            return false;
        }
        i++;        
    }
    ....
}

/**
 * 當進不下去的時候,使用該方法,例如可能是出現了一些對話框遮擋,該方法會把對話框干掉*/
private void SolveProblems()
{        
    ....
}`

這個 SolveProblems() 函數主要是用來解決一些“麻煩”的,例如我們在操作地圖的時候,當gps信號不好的時候,就會彈出下面的對話框:

\

由於出現的對話框,遮擋住了我們的Activity,影響我們對界面上ui元素的獲取,這個時候,我們就可以在SolveProblems() 加入這樣一斷邏輯:當出現“開啟gps”對話框的時候,就點擊“殘忍的拒絕”,將此對話框給關掉,這樣while的判斷條件再次執行的時候,就可以成功獲取到你想要的元素。
  

所以說這個SolveProblems()才是提高UI自動化成功率的關鍵,因為每個App都有自己的特征,因此這部分的內容,需要你們在平時的日積月累中才能總結出來,當你有了一個足夠多的經驗庫之後,你的App幾乎不會再因為外界因素而導致失敗了。經過我自己在我項目上的嘗試,效果非常的顯著。

3、日志
  

日志的重要性不言而喻,當我們在自動化執行的過程中,肯定不會一直盯著屏幕觀察,因此日志使我們最依靠的東西。關於日志的記錄方法多種多樣,我這裡提供下我是怎麼在Uiautomator中打印日志的:

`public String m_logpathString = "/mnt/sdcard/PerformanceLog.txt";
   public void UiAutomationLog(String str) 

    {
    // 取得當前時間
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    String datestr = calendar.get(Calendar.HOUR_OF_DAY) + ":" + 
    calendar.get(Calendar.MINUTE) + ":" + 
    calendar.get(Calendar.SECOND) + calendar.get(Calendar.MILLISECOND) + ":";

    FileWriter fwlog = null;
    try
    {
        fwlog = new FileWriter(m_logpathString, true);
        fwlog.write(datestr + str + "\r\n");
        System.out.println(datestr + str);
        fwlog.flush();

    } catch (IOException e)
    {
        e.printStackTrace();
    } finally
    {
        try
        {
            fwlog.close();
        } catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}`

接下來就是把這個函數加在一些關鍵的地方,當出錯的時候,方便我們排查問題即可。

總結

將上面的代碼全部整理之後,我們可以放到一個單獨的類中,這樣將測試腳本和幫助處理其他功能的腳本進行分離,這樣可以更加便捷我們維護測試代碼。其次這樣寫出來的代碼可讀性高,並且會隨著時間的增加,容錯性越來越強,最終將行成一個文檔的UI自動化測試框架。

本章完~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved