Android Compose UI Test 測試與除錯

在宣告式 UI 中,我們不透過 viewId 來尋找元件,而是透過「語意 (Semantics)」系統。Compose 的測試 API 模擬了使用者的互動,能幫助你確保 UI 流程與輔助功能 (Accessibility) 的正確性。

語意樹 (Semantics Tree)

Compose 渲染 UI 時,同步會產生一棵「語意樹」。這棵樹描述了每個節點的用途(如:這是個按鈕、這段文字的內容是...)。

  • Merged Tree (預設):為了無障礙工具(如 TalkBack),Compose 會將多個子節點合併。例如一個 Button 包含一個 Text,測試時會被視為一個可點擊的整體。
  • Unmerged Tree:如果你需要測試按鈕內特定文字的細節,可以使用 useUnmergedTree = true 展開它。

測試結構:AAA 模式

src/androidTest/java 下建立測試,並使用 createComposeRule()

class LoginTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun loginFlow_success() {
        // 1. Arrange (準備環境)
        composeTestRule.setContent { MyTheme { LoginScreen() } }

        // 2. Act (執行動作)
        composeTestRule.onNodeWithTag("username").performTextInput("admin")
        composeTestRule.onNodeWithText("登入").performClick()

        // 3. Assert (斷言狀態)
        composeTestRule.onNodeWithText("歡迎回來").assertIsDisplayed()
    }
}

API 工具手冊

Matchers (尋找節點)

  • onNodeWithText("文字"):最直觀的尋找方式。
  • onNodeWithTag("MyTag"):當文字不固定時,使用 Modifier.testTag()
  • onNodeWithContentDescription("描述"):基於無障礙說明的尋找方式(推薦)。
  • hasParent(matcher):階層式過濾。

Actions (執行動作)

  • performClick():模擬點擊。
  • performTextInput("..."):鍵入文字。
  • performScrollTo():滾動到該節點出現在畫面中。
  • performImeAction():觸發鍵盤動作(如:送出)。

Assertions (斷言結果)

  • assertIsDisplayed():確認節點在畫面上。
  • assertIsEnabled() / assertIsNotEnabled():確認按鈕狀態。
  • assertIsSelected():確認單選/複選框狀態。
  • assertTextEquals("..."):字串精確比對。

測試非同步與等待

如果 UI 需要等待網路載入或動畫,可以使用 waitUntil 系列 API。

@Test
fun testAsyncLoading() {
    composeTestRule.setContent { DataScreen() }

    // 等待直到 "載入完成" 這四個字出現在語意樹中 (超時 5 秒)
    composeTestRule.waitUntil(timeoutMillis = 5000) {
        composeTestRule
            .onAllNodesWithText("載入完成")
            .fetchSemanticsNodes().isNotEmpty()
    }

    composeTestRule.onNodeWithText("載入完成").assertIsDisplayed()
}

除錯與分析工具

Layout Inspector

Android Studio 內建工具。它能讓你即時看到:

  1. 節點屬性:具體的寬高、間隔與參數。
  2. 重組統計 (Recomposition):哪些組件非預期地反覆繪製,幫助解決效能瓶頸。

印出語意樹 (Debugging)

如果在測試中找不到節點,可以將整棵語意樹印出來查看結構:

composeTestRule.onRoot().printToLog("MY_DEBUG_TAG")

遵循 使用者導向 的測試原則(多用文字與描述,少用 Tag),不僅能提高測試覆蓋率,還能確保你的 App 對於身障使用者同樣友善。