自動(dòng)化測(cè)試 | UI Automator進(jìn)階指南
- 跨應(yīng)用的用戶界面自動(dòng)化測(cè)試
- 包含在 AndroidX Test(https://developer.android.com/training/testing) 中
- 支持的 Android 系統(tǒng):>= Android 4.3 (API level 18)
- 基于 instrumentation,依賴于 AndroidJUnitRunner 測(cè)試運(yùn)行器
設(shè)置 UI Automator(Set up UI Automator)
在編寫(xiě)測(cè)試代碼前,先確保以下兩個(gè)配置:
1、測(cè)試代碼存放位置
2、項(xiàng)目依賴(https://developer.android.com/training/testing/set-up-project)
(1) 添加 Gradle 依賴(Add Gradle dependencies)
- app 目錄下的 build.gradle 添加:
allprojects {
repositories {
jcenter()
google()
}
}
- dependencies 添加需要的 AndroidX Test Package, 比如:
dependencies {
...
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}
- 如果測(cè)試代碼需要基于 junit 的類(lèi),比如 Assert 和 TestSuiteLoader,在 android 區(qū)塊中添加(只需要添加需要用到的 library:https://developer.android.com/training/testing/set-up-project#junit-based-libs):
android {
...
// Gradle automatically adds 'android.test.runner' as a dependency.
useLibrary 'android.test.runner'
useLibrary 'android.test.base'
useLibrary 'android.test.mock'
}
(2) 添加 manifest 聲明(Add manifest declarations)
此步驟可選,具體請(qǐng)看 https://developer.android.com/training/testing/set-up-project#add-manifest-declarations
當(dāng)前面的配置完成后,進(jìn)行其他配置:
app下的build.gralde:
dependencies {
...
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
當(dāng)所有配置都完成后,進(jìn)行被測(cè)應(yīng)用的 UI 組件分析,確保能被識(shí)別以及接入控制。
檢查設(shè)備上的用戶界面(Inspect the UI on a device)
uiautomatorviewer:
(1) 啟動(dòng)手機(jī)上的被測(cè)應(yīng)用
(2) 手機(jī)連接電腦
(3) 打開(kāi) Terminal, 進(jìn)入目錄
(4) 運(yùn)行:uiautomatorviewer
查看應(yīng)用的用戶界面屬性:
(1) 點(diǎn)擊左上角 "Device Screenshot" 按鈕
(2) 左邊是 UI 組件,右下半部分是屬性,右上半部分是布局層級(jí)
(3) 可選功能:點(diǎn)擊右上角 "Toggle NAF Nodes" 按鈕(黃色三角形,內(nèi)有感嘆號(hào)),查看無(wú)法被識(shí)別/訪問(wèn)的UI組件。---這個(gè)功能我都沒(méi)搞懂怎么用,點(diǎn)擊后貌似沒(méi)效果
確保 activity 可訪問(wèn)(Ensure your activity is accessible)
Android 原生元素具有更好的訪問(wèn)性,利于測(cè)試代碼的編寫(xiě),無(wú)需額外的支持
如果是自定義 UI 元素,需要(1)創(chuàng)建一個(gè)繼承自 ExploreByTouchHelper 的實(shí)體類(lèi)(2)通過(guò)調(diào)用 setAccessibilityDelegate() 將新創(chuàng)建的類(lèi)的實(shí)例和特定的自定義 UI 元素相關(guān)聯(lián)
給自定義視圖元素添加無(wú)障礙功能的其他參考資料:https://developer.android.com/guide/topics/ui/accessibility/custom-views.html
學(xué)習(xí)資料 for 提高 Android 的無(wú)障礙性/可訪問(wèn)性:https://developer.android.com/guide/topics/ui/accessibility/apps.html
創(chuàng)建一個(gè) UI Automator 測(cè)試類(lèi)(Create a UI Automator test class)
UI Automator 測(cè)試類(lèi)的寫(xiě)法和 JUnit 4 測(cè)試類(lèi)的寫(xiě)法是一樣的。
JUnit 4 測(cè)試類(lèi)的學(xué)習(xí)資料:https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests.html#build
在測(cè)試類(lèi)開(kāi)頭添加注解:@RunWith(AndroidJUnit4.class)
同時(shí),明確 AndroidX Test 中的 AndroidJUnitRunner 類(lèi)為默認(rèn)的測(cè)試運(yùn)行器。這個(gè)步驟的詳細(xì)描述:https://developer.android.com/training/testing/ui-testing/uiautomator-testing.html#run
在 UI Automator 測(cè)試類(lèi)中執(zhí)行以下編程模型:
- 獲取一個(gè) UiDevice 對(duì)象去接入測(cè)試設(shè)備,調(diào)用 getInstance() 方法,傳入 Instrumentation 對(duì)象作為參數(shù)。
- 通過(guò) UiObject 對(duì)象調(diào)用 findObject() 方法接入顯示在設(shè)備上的 UI 組件(例如,當(dāng)前手機(jī)屏幕顯示的用戶界面)。
- 通過(guò)調(diào)用 UiObject 方法在 UI 組件上模擬一個(gè)交互的動(dòng)作。例如,調(diào)用 performMultiPointerGesture() 方法模擬多指觸控,調(diào)用 setText() 方法編輯文本框。當(dāng)測(cè)試包含多個(gè) UI 組件或者更加復(fù)雜的操作序列時(shí),在第二步和第三步中可重復(fù)調(diào)用各種 API.
-
當(dāng)執(zhí)行完這些用戶交互的動(dòng)作后,檢查返回的結(jié)果是否符合預(yù)期。
這些步驟在以下章節(jié)會(huì)講的更加詳細(xì)。
訪問(wèn)用戶界面組件 (Access UI components)
UiDevice: 接入和控制設(shè)備狀態(tài)的首要方法,可執(zhí)行設(shè)備級(jí)別的行為,例如改變屏幕旋轉(zhuǎn)方向、按下硬件按鈕、以及點(diǎn)擊 home 和 menu 鍵。
從設(shè)備的主屏幕開(kāi)始測(cè)試是一個(gè)好的實(shí)踐。在主屏幕(或者其他你在設(shè)備上選定的開(kāi)始位置),可以調(diào)用 UI Automator API 提供的方法和指定的 UI 元素進(jìn)行交互。
以下代碼片段展示了如何獲取一個(gè) UiDevice 的實(shí)例以及模擬按下 home 鍵的操作:
import org.junit.Before;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
...
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class ChangeTextBehaviorTest {
private static final String BASIC_SAMPLE_PACKAGE
= "com.example.android.testing.uiautomator.BasicSample";
private static final int LAUNCH_TIMEOUT = 5000;
private static final String STRING_TO_BE_TYPED = "UiAutomator";
private UiDevice device;
@Before
public void startMainActivityFromHomeScreen() {
// Initialize UiDevice instance
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Start from the home screen
device.pressHome();
// Wait for launcher
final String launcherPackage = device.getLauncherPackageName();
assertThat(launcherPackage, notNullValue());
device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
LAUNCH_TIMEOUT);
// Launch the app
Context context = ApplicationProvider.getApplicationContext();
final Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
// Clear out any previous instances
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
// Wait for the app to appear
device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
LAUNCH_TIMEOUT);
}
}
示例代碼中的聲明:@SdkSuppress(minSdkVersion = 18), 幫助確定測(cè)試只運(yùn)行在 Android4.3(API level 18)或更高級(jí)別的設(shè)備上。(UI Automator 框架要求的)
- findObject()
- UiObject
UiObject cancelButton = device.findObject(new UiSelector()
.text("Cancel")
.className("android.widget.Button"));
UiObject okButton = device.findObject(new UiSelector()
.text("OK")
.className("android.widget.Button"));
// Simulate a user-click on the OK button, if found.
if(okButton.exists() && okButton.isEnabled()) {
okButton.click();
}
指定一個(gè)選擇器(Specify a selector)
UiSelector 類(lèi):在當(dāng)前顯示的用戶界面中查詢一個(gè)特定的元素。
- childSelector()
- UiAutomatorObjectNotFoundException
UiObject appItem = device.findObject(new UiSelector()
.className("android.widget.ListView")
.instance(0)
.childSelector(new UiSelector()
.text("Apps")));
tips:
- 如果在一個(gè)頁(yè)面上找到一個(gè)以上的相同元素,自動(dòng)返回第一個(gè)匹配的元素作為目標(biāo) UiObject.
- 可以通過(guò)整合多個(gè)屬性來(lái)縮小搜索范圍。
- 如果沒(méi)有找到目標(biāo)元素,拋出 UiAutomatorObjectNotFoundException 異常。
- 可以使用 childSelector() 方法縮小多個(gè) UiSelector 實(shí)例范圍。
- 如果有 Resource ID, 用這個(gè)代替 text 和 content-descripter.
-
text 元素比較脆弱,有多種原因可能導(dǎo)致測(cè)試失敗。(比如:多語(yǔ)言)
在選擇區(qū)域中去明確一個(gè)對(duì)象狀態(tài)是非常有用的。比如:選擇一個(gè)已選中的列表以進(jìn)行取消選中狀態(tài),調(diào)用 checked() 方法,將參數(shù)設(shè)為 "true".
執(zhí)行動(dòng)作(Perform actions)
當(dāng)獲取 UiObject 對(duì)象后,可以調(diào)用 UiObject 類(lèi)中的方法在其上執(zhí)行相應(yīng)操作:
- click(): 點(diǎn)擊
- dragTo(): 拖動(dòng)
- setText(): 設(shè)置文本
- clearTextField(): 清空文本
- swipeUp(): 向上滑動(dòng)
- swipeDown(): 向下滑動(dòng)
- swipeLeft(): 向左滑動(dòng)
- swipeRight(): 向右滑動(dòng)
通過(guò) getContext() 方法獲取到 Context 后,可以進(jìn)行發(fā)送 Intent 或者啟動(dòng) Activity 的操作。
public void setUp() {
...
// Launch a simple calculator app
Context context = getInstrumentation().getContext();
Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(CALC_PACKAGE);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
// Clear out any previous instances
context.startActivity(intent);
device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT);
}
在集合上執(zhí)行動(dòng)作(Perform actions on collections)
-
UiCollection 類(lèi):在一個(gè) item 的集合上模擬用戶操作(例如,歌曲列表或者郵件列表)。
如何創(chuàng)建一個(gè) UiCollection 對(duì)象:明確一個(gè)搜索UI容器或者其他子 UI 元素集合的 UiSelector. 例如,包含子 UI 元素的 layout 視圖。
UiCollection videos = new UiCollection(new UiSelector()
.className("android.widget.FrameLayout"));
// Retrieve the number of videos in this collection:
int count = videos.getChildCount(new UiSelector()
.className("android.widget.LinearLayout"));
// Find a specific video and simulate a user-click on it
UiObject video = videos.getChildByText(new UiSelector()
.className("android.widget.LinearLayout"), "Cute Baby Laughing");
video.click();
// Simulate selecting a checkbox that is associated with the video
UiObject checkBox = video.getChild(new UiSelector()
.className("android.widget.Checkbox"));
if(!checkBox.isSelected()) checkbox.click();
在可滾動(dòng)視圖中執(zhí)行動(dòng)作(Perform actions on scrollable views)
- UiScrollable 類(lèi):在手機(jī)屏幕上模擬垂直或者水平的滾動(dòng)操作。這個(gè)類(lèi)適用于當(dāng)需要找到屏幕外的 UI 元素時(shí),可以通過(guò)滾動(dòng)操作將這個(gè) UI 元素帶到屏幕內(nèi)。
UiScrollable settingsItem = new UiScrollable(new UiSelector()
.className("android.widget.ListView"));
UiObject about = settingsItem.getChildByText(new UiSelector()
.className("android.widget.LinearLayout"), "About tablet");
about.click();
驗(yàn)證結(jié)果(Verify results)
InstrumentationTestCase 繼承自 TestCase,可以使用標(biāo)準(zhǔn)的 JUnit Assert 方法進(jìn)行結(jié)果驗(yàn)證。
以下代碼片段展示了如何驗(yàn)證計(jì)算器加法:
private static final String CALC_PACKAGE = "com.myexample.calc";
public void testTwoPlusThreeEqualsFive() {
// Enter an equation: 2 + 3 = ?
device.findObject(new UiSelector()
.packageName(CALC_PACKAGE).resourceId("two")).click();
device.findObject(new UiSelector()
.packageName(CALC_PACKAGE).resourceId("plus")).click();
device.findObject(new UiSelector()
.packageName(CALC_PACKAGE).resourceId("three")).click();
device.findObject(new UiSelector()
.packageName(CALC_PACKAGE).resourceId("equals")).click();
// Verify the result = 5
UiObject result = device.findObject(By.res(CALC_PACKAGE, "result"));
assertEquals("5", result.getText());
}
在設(shè)備或虛擬機(jī)上運(yùn)行 UI Automator 測(cè)試用例(Run UI Automator tests on a device or emulator)
可以通過(guò) Android Studio 或者命令行運(yùn)行 UI Automator tests. 確保項(xiàng)目的默認(rèn) instrumentation runner 是 AndroidJUnitRunner.
