Android手機QQ的UI自動化實踐

於果alpha發表於2021-08-24

本文首發於果的部落格園,原文連結:https://www.cnblogs.com/yuxiuyan/p/14992682.html
轉載請註明出處。

UI自動化

我們為什麼要搞UI自動化

可能很多同學都有疑問:我們寫了這麼多單元測試,為什麼還需要UI自動化測試呢?
按照測試金字塔理論,其實每種型別的測試都有自己的意義,UI自動化的意義就在於更貼近使用者真實場景的校驗,比如對於手機QQ來說,我們需要確保主流程的真實鏈路是通暢的,而單元測試和介面測試很難做到這一點。

我們需要多少用例

UI自動化的意義是驗證使用者主流程場景,所以UI自動化一定是最最核心的冒煙用例,針對UI自動化追求覆蓋率是沒有意義的。
我們在公司內部的實踐表明:

  • 對於增長型業務,這種業務產品變化快,重構需求多,那麼程式碼類UI自動化建議佔比5%—20%
增長業務
  • 對於穩定型業務,這類業務產品功能穩定,大改版需求少,那麼程式碼類UI自動化建議佔比10—30%
穩定業務

Android手機QQ的手工系統測試用例有7000多條,我們對這些用例優先順序進行了排序,針對P0用例,再排除掉一些難以自動化的用例,定下了400餘條的目標,約為6%。

自動化框架

調研Android自動化框架

調研自動化框架
按照部門的要求,我們本次的自動化需要用到同源(同語言同工程)的形式,而且針對手Q很多複雜的場景,我們需要穩定復現,所以我們排除了QTA、Appium、AirTest等框架,最終選擇了UiAutomator。

UiAutomator是Google官方提供的同源測試框架,它的底層使用了Android的系統級服務AccessibilityService,關於這一塊的介紹,可以看文章:《從Android手機的搶紅包外掛說起 》
官方文件傳送門:UI Automator | Android Developers

封裝模式

在上一步環節中,我們雖然確定了自動化框架,但是框架只提供底層的驅動能力,如果無統一封裝模式進行規範,隨著用例的增多會變得難以維護,所以我們需要一個統一模式來封裝細節,可以使 testcase 更穩健,不需要大改動。即我們需要對UiAutomator API進行二次封裝。

業界最常見的一種封裝模式是Page Object模式,“頁面即物件”。這種封裝模式把一個頁面看做一個物件,把頁面上的控制元件(按鈕、圖片等)元素當做物件的屬性,把對頁面上的控制元件操作(如點選某按鈕)當做物件的方法。這種封裝模式的優勢是維護簡單,對於頁面的某些改動,只需要維護該頁面對應的物件。劣勢是程式碼複用率較低,不利於大規模鋪量

還有一種常見的封裝模式是Scene模式,“場景化封裝”。這種封裝模式就是按照用例的場景,也不需要API的二次封裝,簡單粗暴去實現。這種封裝模式的優勢是簡單粗暴,可讀性高。劣勢是程式碼複用率低,十分冗長

我們的痛點是,需要快速鋪量,那按照用例場景,所見即所得的程式碼方式,的確是很快,但是我們需要對該模式規範化。結合測試用例的3A原則(Arrange、Act、Assert),我們創造了一種新的封裝模式QTS(QQ Testcase Service)。

自動化框架QTS

我們在寫測試用例的時候,是按照使用者角度,從一個個控制元件元素觸發,經過一個個場景頁面,最終驗證某一個結果。那為什麼不直接把上面的元素觸發(Action)、場景頁面(View)、驗證(Check or Assert)自動化呢?這樣就可以快速實現任意一個用例了,因為自動化程式碼和測試用例的文字描述是一一對應的。

QTS是Scene模式的進一步改進。我們抽象出測試用例的3A,抽象出控制元件動作(Action)、頁面元素(View)、斷言(Check)這三個最基本介面,同時因地制宜,結合手Q複雜的環境,又抽象出場景流(Workflow)、環境(Env)介面。

TestBase是全部測試用例類的基類,包含了測試用例的一些通用屬性和方法。它的屬性包括單例模式的action,env,check,qqAcount等,方法包括登入QQ,初始化手Q環境等。所有的測試用例類都繼承這個類。
QTS

  • Action:基本操作事件的介面,在該介面中,負責封裝實現所有的動作事件,比如點選、長按、輸入框輸入、滑動等事件。底層裝置驅動能力由UiAutomator框架的uiDevice類提供。
void click(String control);   // 點選某個控制元件
void longClick(String control, long clickTime);   // 長按某個控制元件
void swipeUp(int steps, int swipePixelLength);   // 頁面上滑
 …… ……
  • View:控制元件定義與查詢介面,在該介面中,負責封裝所有的裝置可檢索的控制元件元素,並提供檢索方法。底層的定義能力由UiAutomator的BySelector類提供。
/**
 * 獲取control的物件
 *
 * @param controlName control的中文定義
 * @return UiObject2物件
 */
UiObject2 getControl(String controlName);
/**
 * 獲取指定instance以及id的Object(適用於頁面中有多個控制元件擁有相同id的情況)
 *
 * @param controlName control的中文定義
 * @param instance 控制元件的數值順序
 * @return UiObject物件
 */
UiObject getInstanceControl(String controlName, int instance);
…… ……
  • Check:斷言介面,之所以沒有叫Assert,是為了和Juint的Assert做一個區分。在該介面中,提供了對於檢測元素存在、不存在、判真、判假等方法。底層斷言能力由Junit的Assert提供。
/**
 * 檢查控制元件是否存在
 *
 * @param control view層封裝的控制元件中文描述或者控制元件唯一text
 */
void exist(String control);

/**
 * 檢查控制元件是否不存在
 *
 * @param control view層封裝的控制元件中文描述或者控制元件唯一text
 */
void notExist(String control);

/**
 * 檢查是否成功登陸
 *
 * @param id 訊息tab的feeds數id,用來判斷訊息tab是否完全家在成功
 */
void loginSuccess(String id);
…… ……
  • Env:環境介面,因為手Q複雜的環境,所以特意把環境相關的方法抽象出來,同時在該介面中,還有直接實現後臺介面的方法,比如加好友的介面、刪除好友的介面、一鍵建群的介面、一鍵銷燬群的介面等。
/**
 * 建立群(無需驗證資訊), 測試賬號需要新增oidb
 *
 * @param uin       需要建立群的群主uin
 * @param groupName 需建立群的名字
 */
Response createGroup(String uin, String groupName);
/**
 * 一鍵加好友
 *
 * @param fromUin   申請加好友的號碼
 * @param friendUin 需要新增的uin
 */
Response addFriend(String fromUin, String friendUin);
…… ……
  • Workflow:場景流介面。這個理解比較困難,可以認為它是一個多view的操作集合,主要提供場景流的一些方法,比如一鍵進入聊天頁面(手Q中稱之為“AIO”)等。
/**
 * 通過介面進入AIO
 *
 * @param uin     群或C2C的uin
 * @param uinName 名稱
 * @return 執行狀態
 */
void openAIO(ActivityTestRule<splashactivity> activityTestRule, QQAppInterface app, Long uin, String uinName);

此外,QTS還提供了一些通用能力,比如log等。

編寫測試用例

有了QTS,根據測試同學提供的用例來自動化,就變得簡單明瞭。比如某一個測試用例,需要開啟手Q錢包頁面,檢查充值記錄。那麼利用QTS,就可以完全模擬使用者的手工操作,一步步實現用例。

public class AccountTest extends QTSBase {
    // 1. 實現手Q錢包進入的workflow
    private final QWalletHomeWorkflow qWalletHomeWorkflow = new QWalletHomeWorkflow();
    @Override
    public void setView(ArrayList<view> viewList) {
	// 2. 新增這個用例涉及到的view頁面
        viewList.add(new MsgTabView());
        viewList.add(new QWalletHomeView());
        viewList.add(new QWalletAccountView());
    }
	// 3. 登入手Q
    @Before
    public void setUp() throws Exception {
        loginQQ(activityTestRule, "qq_uitest");
    }
	// 4. 編寫用例
    @Test
    @CaseAdditionInfo(tags = {"FT=UI自動化", "模組=QQ錢包", "功能=點選Q幣", "測試分類=功能",
            "測試階段=全用例", "管理者=neoyu", "用例等級=P0", "用例型別=1",
            "被測函式=null", "用例描述=在賬戶頁點選Q幣", "版本=850", "手工用例ID=748878937694273536"
    })
    public void testShowQCoinRechargeRecord() {
	// 5. 這裡的程式碼所見即所得,簡單明瞭,即使不註釋,也可以很快讀懂用例的步驟與含義,也方便責任人交接
        qWalletHomeWorkflow.openQWalletAccount();    
        action.waitThenClick("賬戶頁Q幣個數");
        check.exist("充值記錄標題");
        check.exist("全部交易");
    }
}

錄製回放工具

背景

光有上面的QTS框架,雖然寫自動化用例已經很快了,但是還是達不到部門的目標(畢竟部門要實現在半年內把手Q欠了多年的技術債補齊),在這種壓力下,就必須引入新的工具、新的方法。我們調研了公司內外的方案,最終使用了公司內部開源的錄製回放工具。錄製回放工具是一個通過手工錄製,然後回放校驗斷言的自動化測試工具。

基本原理

錄製回放原理
其實核心原理並不複雜,在錄製的時候記錄下元素、對應的操作、網路與IO資料,在回放的時候mock資料並回放操作。這裡要注意,因為涉及到複雜mock與元素的處理,這個工具是侵入式的,需要維護一個手機QQ(錄製回放版本)的打包流水線。

實踐

1.手工測試用例

目前手Q的全部測試用例都託管在公司內部的tcm平臺上,我們的目標就是把tcm平臺上,部分P0用例實現自動化。
對於新功能,由外包同學或者測試同學來錄入手工用例,確定優先順序。我們之後會針對P0用例,考慮自動化。

2. 編寫自動化用例

直接利用QTS編寫。需要注意的是,這裡的CaseAdditionInfo是利用了公司內部的終端自動化測試平臺的能力,在CI系統上配置了流水線,會掃描程式碼倉庫,當掃描到這個註解的時候,就會認為這是一個測試用例,然後把這個測試用例的相關資訊上傳到該平臺。

@Test
@CaseAdditionInfo(tags = {"FT=UI自動化", "模組=群", "功能=群活躍排行榜", "測試分類=功能",
        "測試階段=全用例", "管理者=stancheng", "用例等級=P0", "用例型別=1",
        "被測函式=null", "用例描述=活躍排行榜為空", "版本=850", "手工用例ID=730700977215709184"
})
public void testEmptyGroupRankingList() {
    // 開啟群設定
    aioWorkflow.openGroupAio(activityTestRule, app, "272329539", "UITest群4");
    // 等待一下,群榮譽需要拉取配置
    delay(5);
    // 開啟群榮譽
    aioWorkflow.enterChatSetting();
    boolean isSuccess = groupSettingWorkflow.enterHonorSetting();
    // 如果失敗,那就返回再進入
    while (!isSuccess) {
        action.back();
        aioWorkflow.enterChatSetting();
        isSuccess = groupSettingWorkflow.enterHonorSetting();
    }
    // 檢測是否存在虛位以待
    delay(5);
    check.exist("虛位以待");
}

3. 管理自動化測試用例

自動化用例的管理主要依託終端自動化測試平臺,在這裡實現了用例的解析、上傳、流水線繫結、測試用例集管理、資料看板等操作。

提升穩定性的一些方法

1. 後臺介面代替UI操作

手Q裡面很多場景都是超級複雜的,比如加好友後自動發訊息,加好友這個操作本身就很複雜,如果場景累加的話,那UI自動化的執行時間將大大延長,並且每多一個view就增加檢索失敗的風險。所以這時候,就需要和開發同學協商,把加好友這一步,做成介面,在客戶端可以直接呼叫。這些介面,我們統一封裝在Env介面類中。

2. 重試機制

UI自動化用例中,偶現某個元素或操作事件沒有生效的情況,這和裝置有很大關係。在沒有更多預算的情況下,就需要增加一些重試,比如下列用例,其中的retryCount就是一個重試機制:

public void clickFile() {
    clickPlusBtn();
    UiObject2 object2 = uiDevice.findObject(By.clazz(Constants.CLASS_RADIO_BUTTON));
    int retryCount = 0;
    while ( object2 == null && retryCount < 5) {
        retryCount ++;
        clickPlusBtn();
        object2 = uiDevice.findObject(By.clazz(Constants.CLASS_RADIO_BUTTON));
    }
    plusAreaClick("檔案");
}

3. 等待

遠端裝置經常會出現網路慢的情況,這時候載入元素就很慢,對於UI自動化來說,就會報“Contorl not found”的錯誤。這種非人力、非程式碼問題,我們一般是使用wait()或sleep()方法增加對載入頁面的等待時間。

結語

限於筆者的經驗,以及部門時間壓力,很多東西沒有時間好好雕琢。UI自動化是一個很廣大的領域,但是近些年,業界對於UI自動化的研究並沒有很深入,在我們實際的生產活動中,也是發現了UI自動化的很多缺點:維護成本高、裝置依賴程度高等,所以每個專案都需要因地制宜,思考UI自動化對於專案的意義


我的部落格即將同步至騰訊雲+社群,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=1fthcjmf2bsx2

相關文章