輔助程序實現黑盒自動化測試的特點
輔助功能相較於測試框架在部分場景下有一定的優勢,可以做更多場景的測試:
1. 運行輔助程序過程中可以執行Uiautomator1.0或Uiautomator2.0測試用例,執行測試用例過程中輔助程序會被中斷,當測試用例執行結束後輔助程序會恢復運行;
2. 大部分機型重啟設備,服務會自動運行,可以測試部分app開機自啟功能等功能;
3. 由於是App方案,所以可以直接讀取手機簡訊,設備插上手機卡後,可以在邏輯數據層攔截簡訊驗證碼,實現程序的驗證碼登陸;
4.輔助程序有較高的優先級,程序保活能力非常強;
由於測試方式是基於Accessibility服務的app,所以也有很多限制:
1. 和被測應用不在同一進程內,相較於Uiautomator2.0隻能做黑盒測試;
2. 由於是App實現方案,所以在各系統上存在權限問題,不能像Uiautomator1.0一樣獲取系統運行信息(最近運行的應用、當前顯示的activity)
3. 執行點擊動作需要使用AccessibilityNodeInfo#performAction方法,但是此方法只能在clickable=true時生效;
4. 無法直接操作屏幕,需要藉助AccessibilityService的手勢功能實現滑動屏幕、點擊屏幕等動作;
5. 由於系統限制,高版本系統中啟動其他App失敗;
6. 發送鍵盤事件受限制,只能發送Home、back、打開通知、打開快捷設置、打開電源彈窗等有限功能;
7. 輔助程序異常退出時會關閉輔助程序配置,需要在設置頁面手動配置;
輔助程序服務配置與黑盒自動化測試實現
自定義AccessibilityService服務,在服務內重載系統方法,
初始化測試服務:重載onServiceConnected方法
當服務激活時調用此方法,在這裡進行服務的初始化工作,主要配置onAccessibilityEvent方法感知的事件類型和服務配置。
1、獲取AccessibilityServiceInfo 對象:
getServiceInfo()方法獲取當前使用的AccessibilityServiceInfo對象,如果方法返回null,直接通過AccessibilityServiceInfo()構造方法創建。 AccessibilityServiceInfo accessibilityServiceInfo = getServiceInfo();
if (accessibilityServiceInfo == null) {
accessibilityServiceInfo = new AccessibilityServiceInfo();
setServiceInfo(accessibilityServiceInfo);
}
2、設置AccessibilityServiceInfo#eventTypes標記過濾AccessibilityEvent事件類型:
直接通過或運算符(|)添加標記,下面代碼配置過濾控制項點擊、控制項選擇、控制項獲取焦點和文本變化事件,發生配置的事件時會調用onAccessibilityEvent方法。accessibilityServiceInfo.eventTypes |= AccessibilityEvent.TYPE_VIEW_CLICKED;
accessibilityServiceInfo.eventTypes |= AccessibilityEvent.TYPE_VIEW_SELECTED;
accessibilityServiceInfo.eventTypes |= AccessibilityEvent.TYPE_VIEW_FOCUSED;
accessibilityServiceInfo.eventTypes |= AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED;
3、 設置AccessibilityServiceInfo#flags配置測試能力:
下面的代碼在默認配置基礎上增加WebView測試能力。accessibilityServiceInfo.flags |= AccessibilityServiceInfo.DEFAULT;
accessibilityServiceInfo.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY;
AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS標記慎用,如果增加此標記,輔助程序會攔截設備按鍵事件,會導致點擊返回鍵、home鍵無效。
4、更新配置信息:
需要調用
AccessibilityServiceInfo#setServiceInfo(AccessibilityServiceInfo info)
方法更新配置。
重載
onAccessibilityEvent(AccessibilityEvent accessibilityEvent)
方法,感知頁面變化。
當發生頁面變化時回調此方法,可用於觸發自動處理彈窗的業務
重載onInterrupt()方法,釋放資源
當系統中斷輔助服務時調用(例如執行自動化測試用例),可以在這個方法內釋放資源。
配置測試服務
配置清單文件:
<service
android:label="輔助按鍵服務"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true"
android:directBootAware="true"
android:name=".service.TaskAccessibilityService">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
accessibilityservice配置輔助程序屬性:
android:description 配置輔助程序描述信息,在設置頁面中顯示
android:accessibilityFeedbackType 反饋方式
android:canPerformGestures 模擬手勢配置,輔助程序滑動屏幕時必須配置此屬性為true
android:accessibilityFlags
等同AccessibilityServiceInfo#flags的設置,可以在代碼中動態配置
res/xml/accessibility_service_config.xml文件信息:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:accessibilityFeedbackType="feedbackAllMask"
android:canRetrieveWindowContent="true"
android:canPerformGestures="true"
android:notificationTimeout="100"
android:accessibilityFlags="flagDefault"
/>
performGlobalAction方法參數為AccessibilityNodeInfo的靜態常量:
dispatchGesture實現點擊、滑動滑動屏幕
系統SDK<24時無法點擊和滑動屏幕,需要通過AccessibilityNodeInfo的performAction(int action)方法實現UI操作,參數指定ACTION_ACCESSIBILITY_FOCUS獲取焦點、ACTION_LONG_CLICK執行長點擊、執行ACTION_CLICK點擊。
屏幕滑動和點擊方法是藉助dispatchGesture方法模擬手勢實現的,通過Path指定屏幕滑動路徑,調用dispatchGesture方法實現Path指定的滑動方式。具體實現代碼如下:Path mPath = new Path();
mPath.moveTo(startX, startY);//滑動的起始點
mPath.lineTo(endX, endY);//滑動終點。不指定lineTo的坐標,只配置moveTo坐標時執行點擊動作,點擊位置為moveTo指定的坐標。
dispatchGesture(new GestureDescription.Builder().addStroke(new GestureDescription.StrokeDescription
(mPath, 50, 500)).build(), new GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
System.out.println("模擬手勢成功");
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
super.onCancelled(gestureDescription);
System.out.println("模擬手勢失敗");
}
}, null);
} else {
System.out.println("系統不支持" + Build.VERSION.SDK_INT);
}
高版本系統通過intent啟動APP失敗
Android高版本系統對後臺程序啟動應用做了嚴格限制,解決這個問題有下面兩種方案
1. 輔助程序實現懸浮窗功能,在整個測試流程中App屬於前臺應用,具有啟動其他App的權限。缺點:各版本系統適配麻煩,測試過程中會檢測到懸浮窗。
2. 通過UI操作啟動App,在桌面滑動尋找被測應用的Launcher圖標,然後通過點擊操作打開App。
AccessibilityNodeInfo執行performAction失敗
performAction方法執行失敗是因為對應的AccessibilityNodeInfo不具有對應的操作屬性,比如執行點擊操作時AccessibilityNodeInfo#isClickable()方法返回false,此時點擊操作就會失敗。有兩種解決方案:
1. 尋找響應事件的AccessibilityNodeInfo執行performAction操作
由於當前AccessibilityNodeInfo無法響應點擊事件,那麼尋找AccessibilityNodeInfo#isClickable()返回true的父控制項執行操作。public boolean clickNode(AccessibilityNodeInfo nodeInfo){
AccessibilityNodeInfo actionNode=null;
while (true){
actionNode=nodeInfo.getParent();
//循環時防止空指針異常
if(actionNode==null){
return false;
}
if(actionNode.isClickable()){
break;
}
}
//可能存在所有父控制項均不可點擊情況
if(actionNode==null){
return false;
}
actionNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return false;
}
public boolean clickNode(AccessibilityNodeInfo nodeInfo){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
//獲取控制項的屏幕坐標
Rect rect=new Rect();
nodeInfo.getBoundsInScreen(rect);
//點擊屏幕坐標
clickScreen(rect.centerX(),rect.centerY());
return true;
}else {
return false;
}
}
public void clickScreen(int x, int y) {
Path mPath = new Path();
mPath.moveTo(x, y);//配置點擊坐標
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
dispatchGesture(new GestureDescription.Builder().addStroke(new GestureDescription.StrokeDescription
(mPath, 50, 50)).build(), new GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
System.out.println("點擊成功");
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
super.onCancelled(gestureDescription);
System.out.println("點擊失敗");
}
}, null);
} else {
System.out.println("系統不支持" + Build.VERSION.SDK_INT);
}
}
以上就是通過App實現黑盒測試的簡單介紹,再具體的信息可以查閱Android源碼,下面介紹一下幾種測試方案的使用場景:
權限:Shell權限,可以反射系統API實現常用功能,可以繞過Android安全機制,讀取最近運行APP、正在運行進程、強殺其他應用;
保活:保活能力較強,不會被Android系統釋放,可以用nohup模式異步運行,即使拔掉數據線也不影響服務(諾基亞手機除外);
場景推薦: 適用多App通用測試腳本,便於對腳本做項目擴展;提供遠程手機服務接口。Android R(11)模擬器不支持,兼容Android R系統的腳本需要注意。
權限:與被測App運行在相同進程內部,與被測APP具有相同權限;
保活:保活能力差,被測App進程結束時,腳本即停止運行;
場景推薦:可以直接操作被測App的方法,可以用於白盒測試,對長期維護的重點項目可以用來進行單元測試和複雜業務測試。
權限:單獨的App,跨進程操作,需單獨申請app權限;
保活:由於是Android專用場景的服務,所以保活能力非常強,部分機型即使重啟後服務依然存在,且不影響UiAutomator測試用例的執行,用例執行結束後服務自動恢復;
場景推薦:殘障人士輔助APP;設備維護服務;自動初始化程序運行環境等