使用 UI Automator 編寫自動化測試

UI Automator 是一種 UI 測試架構,適用於跨應用程式功能 UI 並跨系統和安裝的應用程式進行測試透過 UI Automator API 裝置上的可見元素 (無論位於哪一個 Activity 為何) 讓您執行各項操作,例如開啟「設定」選單 測試裝置上的應用程式啟動器測試可以查詢 UI 元件,方法是 使用方便的描述詞, 例如該元件中顯示的文字 內容說明。

UI Automator 測試架構是一種檢測式 API,可正常運作 呼叫 AndroidJUnitRunner 測試執行器。非常適合用於寫作 不透明方塊樣式的自動化測試,這類測試程式碼不需依賴內部內部程式碼 目標應用程式的實作詳細資料。

UI Automator 測試架構的主要功能包括:

  • 提供 API,用於擷取狀態資訊並在目標上執行作業 裝置。詳情請參閱「存取裝置狀態」。
  • 支援跨應用程式 UI 測試的 API。詳情請參閱「UI Automator API
,瞭解如何調查及移除這項存取權。

存取裝置狀態

UI Automator 測試架構提供用於存取的 UiDevice 類別 並在執行目標應用程式的裝置上執行作業。你可以 呼叫方法以存取裝置屬性,例如目前的螢幕方向,或是 顯示大小UiDevice 類別也可讓您執行下列操作 動作:

  1. 變更裝置旋轉設定。
  2. 按下硬體鍵,例如「調高音量」。
  3. 按下「返回」、「主畫面」或「選單」按鈕。
  4. 開啟通知欄。
  5. 擷取目前視窗的螢幕截圖。

舉例來說,如要模擬按下主畫面按鈕的動作,請呼叫 UiDevice.pressHome() 方法。

UI Automator API

UI Automator API 可讓您編寫完善的測試,而不必瞭解 指定應用程式的實作詳細資料。別擔心!您可以使用 這些 API 可擷取及操控多個應用程式的 UI 元件:

  • UiObject2:代表裝置可見的 UI 元素。
  • BySelector:指定比對 UI 元素的條件。
  • By:以精簡的方式建構 BySelector
  • Configurator:您可以設定執行 UI Automator 測試的重要參數。
,瞭解如何調查及移除這項存取權。

舉例來說,下列程式碼顯示如何編寫測試指令碼 在裝置上開啟 Gmail 應用程式:

Kotlin


device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.pressHome()

val gmail: UiObject2 = device.findObject(By.text("Gmail"))
// Perform a click and wait until the app is opened.
val opened: Boolean = gmail.clickAndWait(Until.newWindow(), 3000)
assertThat(opened).isTrue()

Java


device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.pressHome();

UiObject2 gmail = device.findObject(By.text("Gmail"));
// Perform a click and wait until the app is opened.
Boolean opened = gmail.clickAndWait(Until.newWindow(), 3000);
assertTrue(opened);

設定 UI Automator

使用 UI Automator 建構 UI 測試之前,請務必先設定測試 如設定專案所述,原始碼位置和專案依附元件 。

在 Android 應用程式模組的 build.gradle 檔案中,您必須設定依附元件 UI Automator 程式庫的參照:

Kotlin

dependencies {
  ...
  androidTestImplementation('androidx.test.uiautomator:uiautomator:2.3.0-alpha03')
}

Groovy

dependencies {
  ...
  androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.3.0-alpha03'
}

如要最佳化 UI Automator 測試,您應先檢查目標應用程式的 UI 元件,並確保這些元件可供存取。這些最佳化提示 下述兩節的說明。

在裝置上檢查 UI

設計測試之前,請檢查 裝置。為確保 UI Automator 測試能存取這些元件,請 查看這些元件是否具有可見的文字標籤 android:contentDescription 值,或同時指定兩者。

uiautomatorviewer 工具提供便利的視覺化介面供您檢查 版面配置階層,並查看顯示 UI 元件的屬性 放進裝置前景您可以運用這項資訊 使用 UI Automator 進行精細的測試舉例來說,您可以建立 UI 選取器 與特定可見屬性相符

如要啟動 uiautomatorviewer 工具:

  1. 在實體裝置上啟動目標應用程式。
  2. 將裝置連線至開發機器。
  3. 開啟終端機視窗,然後前往 <android-sdk>/tools/ 目錄。
  4. 使用下列指令執行工具:
 $ uiautomatorviewer

如要查看應用程式的 UI 屬性:

  1. uiautomatorviewer 介面中,按一下「Device Screenshot」按鈕。
  2. 將滑鼠遊標懸停在左側面板的快照上,查看 UI 元件 uiautomatorviewer 工具識別出。該屬性會列在 低於 右側面板和版面配置階層。
  3. 您也可以點選「Toggle NAF Nodes」按鈕,查看 UI 元件 也就是 UI Automator 中無法存取的內容只有部分資訊 這些元件可用的程式庫

如要瞭解 Android 提供的常見 UI 元件類型,請參閱 介面

確保你的活動可供存取

UI Automator 測試架構在已實作的應用程式上成效較佳 Android 無障礙功能。使用 View 類型的 UI 元素時 來自 SDK 的 View 子類別,因此您不需要導入無障礙功能 但這些課程已經為您完成這些步驟。

不過,有些應用程式會使用自訂 UI 元素來提供更豐富的使用者體驗。 這類元素不會提供自動支援。如果您的應用程式 包含非來自 SDK 的 View 子類別執行個體,請 請依序完成 步驟如下:

  1. 建立擴充 ExploreByTouchHelper 的具體類別。
  2. 透過下列方式,將新類別的例項與特定自訂 UI 元素建立關聯: 呼叫 setAccessibilityDelegate()

如需在自訂檢視畫面中新增無障礙功能的其他指引 元素,請參閱建構可存取的自訂檢視區塊。如要進一步瞭解 針對 Android 上的無障礙功能提供一般最佳做法,請參閱讓應用程式更臻完善 易於存取

建立 UI Automator 測試類別

您的 UI Automator 測試類別應與 JUnit 4 測試相同的編寫方式 類別進一步瞭解如何建立 JUnit 4 測試類別及使用 JUnit 4 斷言和註解,請參閱「建立檢測設備單元測試類別」。

在測試開始時新增 @RunWith(AndroidJUnit4.class) 註解 類別定義。您也需要指定 AndroidJUnitRunner 類別, 做為預設測試執行器。此步驟會說明 詳情請參閱「在裝置或模擬器上執行 UI Automator 測試」。

在 UI Automator 測試類別中實作下列程式設計模型:

  1. 呼叫,取得要測試的裝置的 UiDevice 物件,以便存取 getInstance() 方法並傳送 Instrumentation 物件, 引數。
  2. 取得 UiObject2 物件,存取顯示在畫面上的 UI 元件 裝置 (例如在前景的目前檢視畫面),方法是呼叫 findObject() 方法。
  3. 模擬特定使用者互動來執行該 UI 元件: 呼叫 UiObject2 方法;例如呼叫 可捲動 scrollUntil() 來捲動;使用 setText() 編輯文字欄位。 您可以按照步驟 2 和 3 中的 API 呼叫 視需要重複執行,測試更複雜的使用者互動,例如: 多個 UI 元件或使用者動作序列
  4. 檢查 UI 在使用者離開後,是否反映了預期的狀態或行為 過程中會執行互動

下文將詳細說明相關步驟。

存取 UI 元件

UiDevice 物件是您存取及操控 裝置狀態。在測試中,您可以呼叫 UiDevice 方法進行檢查 各種屬性的狀態,例如目前的螢幕方向或顯示大小。 測試可以使用 UiDevice 物件執行裝置層級的操作。 例如強制讓裝置旋轉特定角度、按下 D-Pad 硬體 按鈕,並按下主畫面和選單按鈕。

建議您在裝置的主畫面中開始測試。最低價格: 主畫面 (或你在裝置上選擇的其他開始位置)、 您可以呼叫 UI Automator API 所提供的方法,藉此選取和互動 搭配特定 UI 元素

下列程式碼片段顯示測試如何取得 UiDevice 並模擬按下主畫面按鈕的動作:

Kotlin


import org.junit.Before
import androidx.test.runner.AndroidJUnit4
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
...

private const val BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample"
private const val LAUNCH_TIMEOUT = 5000L
private const val STRING_TO_BE_TYPED = "UiAutomator"

@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = 18)
class ChangeTextBehaviorTest2 {

private lateinit var device: UiDevice

@Before
fun startMainActivityFromHomeScreen() {
  // Initialize UiDevice instance
  device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

  // Start from the home screen
  device.pressHome()

  // Wait for launcher
  val launcherPackage: String = device.launcherPackageName
  assertThat(launcherPackage, notNullValue())
  device.wait(
    Until.hasObject(By.pkg(launcherPackage).depth(0)),
    LAUNCH_TIMEOUT
  )

  // Launch the app
  val context = ApplicationProvider.getApplicationContext<Context>()
  val intent = context.packageManager.getLaunchIntentForPackage(
  BASIC_SAMPLE_PACKAGE).apply {
    // Clear out any previous instances
    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
    )
  }
}

Java


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) 陳述式有助於確保 只能在搭載 Android 4.3 (API 級別 18) 以上版本的裝置上執行測試。 按照 UI Automator 架構的要求

使用 findObject() 方法擷取代表UiObject2 符合指定選擇器條件的資料檢視。您可以重複使用 UiObject2 視需要在應用程式測試其他部分建立的執行個體。 請注意,UI Automator 測試架構會搜尋目前的顯示內容, 每次測試使用 UiObject2 例項點擊使用者介面時 元素或查詢屬性

下列程式碼片段說明如何在測試中建構 UiObject2 代表應用程式中的「取消」按鈕和「確定」按鈕。

Kotlin


val okButton: UiObject2 = device.findObject(
    By.text("OK").clazz("android.widget.Button")
)

// Simulate a user-click on the OK button, if found.
if (okButton != null) {
    okButton.click()
}

Java


UiObject2 okButton = device.findObject(
    By.text("OK").clazz("android.widget.Button")
);

// Simulate a user-click on the OK button, if found.
if (okButton != null) {
    okButton.click();
}

指定選取器

如要存取應用程式的特定 UI 元件,請使用 By 類別,用來建構 BySelector 例項。BySelector 代表在顯示的 UI 中對特定元素的查詢。

如果找到多個相符元素, 版面配置階層會做為目標 UiObject2 傳回。建構容器時 BySelector,可將多個屬性鏈結在一起以縮小 搜尋。如果找不到相符的 UI 元素,會傳回 null

您可以使用 hasChild()hasDescendant() 方法建立巢狀結構 多個 BySelector 執行個體。例如,以下程式碼範例顯示 測試可指定搜尋的方式,找出第一個 ListView 具有含文字屬性的子項 UI 元素。

Kotlin


val listView: UiObject2 = device.findObject(
    By.clazz("android.widget.ListView")
        .hasChild(
            By.text("Apps")
        )
)

Java


UiObject2 listView = device.findObject(
    By.clazz("android.widget.ListView")
        .hasChild(
            By.text("Apps")
        )
);

建議您在選擇器條件中指定物件狀態,適用對象 舉例來說,如果您想要選取所有已勾選元素的清單 請取消勾選 checked() 方法,並將引數設為 true。

執行動作

測試取得 UiObject2 物件後,您可以在 UiObject2 類別,用於在 UI 元件上執行使用者互動 由該物件表示的您可以指定下列動作:

  • click():點選 UI 元素可見邊界的中心點。
  • drag():將此物件拖曳至任意座標。
  • setText():在清除 欄位中的內容。相反地,clear() 方法會清除現有文字 對應於可編輯欄位中
  • swipe():往指定方向執行滑動動作。
  • scrollUntil():朝指定方向執行捲動動作 直到滿足 ConditionEventCondition 為止。

UI Automator 測試架構可讓您傳送 Intent 或啟動 在不使用殼層指令的情況下建立 Activity,方法是取得結構定義 物件更新路徑getContext()

下列程式碼片段說明如何在測試中使用意圖啟動 測試中的應用程式。如果您只想在測試時 計算機應用程式執行時,系統並不在意啟動器。

Kotlin


fun setUp() {
...

  // Launch a simple calculator app
  val context = getInstrumentation().context
  val intent = context.packageManager.getLaunchIntentForPackage(CALC_PACKAGE).apply {
    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)
}

Java


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);
}

確認結果

InstrumentationTestCase 擴充了 TestCase 的內容,因此您可以使用 使用標準 JUnit 斷言方法,測試應用程式中的 UI 元件 預期的結果。

下列程式碼片段說明如何讓測試在 計算機應用程式,依順序點選這些元素,然後確認結果是否正確 高度。

Kotlin


private const val CALC_PACKAGE = "com.myexample.calc"

fun testTwoPlusThreeEqualsFive() {
  // Enter an equation: 2 + 3 = ?
  device.findObject(By.res(CALC_PACKAGE, "two")).click()
  device.findObject(By.res(CALC_PACKAGE, "plus")).click()
  device.findObject(By.res(CALC_PACKAGE, "three")).click()
  device.findObject(By.res(CALC_PACKAGE, "equals")).click()

  // Verify the result = 5
  val result: UiObject2 = device.findObject(By.res(CALC_PACKAGE, "result"))
  assertEquals("5", result.text)
}

Java


private static final String CALC_PACKAGE = "com.myexample.calc";

public void testTwoPlusThreeEqualsFive() {
  // Enter an equation: 2 + 3 = ?
  device.findObject(By.res(CALC_PACKAGE, "two")).click();
  device.findObject(By.res(CALC_PACKAGE, "plus")).click();
  device.findObject(By.res(CALC_PACKAGE, "three")).click();
  device.findObject(By.res(CALC_PACKAGE, "equals")).click();

  // Verify the result = 5
  UiObject2 result = device.findObject(By.res(CALC_PACKAGE, "result"));
  assertEquals("5", result.getText());
}

在裝置或模擬器上執行 UI Automator 測試

您可以透過 Android Studio 或 建立服務帳戶請務必將 AndroidJUnitRunner 指定為預設值 。

其他示例

與系統 UI 互動

UI Automator 可與畫面上的所有內容互動,包括系統 元素,如以下程式碼片段所示:

Kotlin


// Opens the System Settings.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.executeShellCommand("am start -a android.settings.SETTINGS")

Java


// Opens the System Settings.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.executeShellCommand("am start -a android.settings.SETTINGS");

Kotlin


// Opens the notification shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.openNotification()

Java


// Opens the notification shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.openNotification();

Kotlin


// Opens the Quick Settings shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.openQuickSettings()

Java


// Opens the Quick Settings shade.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.openQuickSettings();

Kotlin


// Get the system clock.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock"))
print(clock.getText())

Java


// Get the system clock.
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
UiObject2 clock = device.findObject(By.res("com.android.systemui:id/clock"));
print(clock.getText());

等待轉換

關閉幹擾
圖 1. UI Automator 會在以下裝置上關閉零打擾模式: 測試裝置。

畫面切換過程可能很耗時,而且要預測持續時間並不可靠, 您應在執行作業後等待 UI Automator。UI Automator 提供多種做法:

下列程式碼片段說明如何使用 UI Automator 關閉「零打擾」模式 系統設定中,使用以下的 performActionAndWait() 方法的零打擾模式: 等待轉換:

Kotlin


@Test
@SdkSuppress(minSdkVersion = 21)
@Throws(Exception::class)
fun turnOffDoNotDisturb() {
    device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
    device.performActionAndWait({
        try {
            device.executeShellCommand("am start -a android.settings.SETTINGS")
        } catch (e: IOException) {
            throw RuntimeException(e)
        }
    }, Until.newWindow(), 1000)
    // Check system settings has been opened.
    Assert.assertTrue(device.hasObject(By.pkg("com.android.settings")))

    // Scroll the settings to the top and find Notifications button
    var scrollableObj: UiObject2 = device.findObject(By.scrollable(true))
    scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP))
    val notificationsButton = scrollableObj.findObject(By.text("Notifications"))

    // Click the Notifications button and wait until a new window is opened.
    device.performActionAndWait({ notificationsButton.click() }, Until.newWindow(), 1000)
    scrollableObj = device.findObject(By.scrollable(true))
    // Scroll down until it finds a Do Not Disturb button.
    val doNotDisturb = scrollableObj.scrollUntil(
        Direction.DOWN,
        Until.findObject(By.textContains("Do Not Disturb"))
    )
    device.performActionAndWait({ doNotDisturb.click() }, Until.newWindow(), 1000)
    // Turn off the Do Not Disturb.
    val turnOnDoNotDisturb = device.findObject(By.text("Turn on now"))
    turnOnDoNotDisturb?.click()
    Assert.assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000))
}

Java


@Test
@SdkSuppress(minSdkVersion = 21)
public void turnOffDoNotDisturb() throws Exception{
    device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    device.performActionAndWait(() -> {
        try {
            device.executeShellCommand("am start -a android.settings.SETTINGS");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }, Until.newWindow(), 1000);
    // Check system settings has been opened.
    assertTrue(device.hasObject(By.pkg("com.android.settings")));

    // Scroll the settings to the top and find Notifications button
    UiObject2 scrollableObj = device.findObject(By.scrollable(true));
    scrollableObj.scrollUntil(Direction.UP, Until.scrollFinished(Direction.UP));
    UiObject2 notificationsButton = scrollableObj.findObject(By.text("Notifications"));

    // Click the Notifications button and wait until a new window is opened.
    device.performActionAndWait(() -> notificationsButton.click(), Until.newWindow(), 1000);
    scrollableObj = device.findObject(By.scrollable(true));
    // Scroll down until it finds a Do Not Disturb button.
    UiObject2 doNotDisturb = scrollableObj.scrollUntil(Direction.DOWN,
            Until.findObject(By.textContains("Do Not Disturb")));
    device.performActionAndWait(()-> doNotDisturb.click(), Until.newWindow(), 1000);
    // Turn off the Do Not Disturb.
    UiObject2 turnOnDoNotDisturb = device.findObject(By.text("Turn on now"));
    if(turnOnDoNotDisturb != null) {
        turnOnDoNotDisturb.click();
    }
    assertTrue(device.wait(Until.hasObject(By.text("Turn off now")), 1000));
}

其他資源

如要進一步瞭解如何在 Android 測試中使用 UI Automator,請參閱 資源。

參考說明文件:

範例