測試 Cupcake 應用程式

1. 簡介

在「使用 Compose 切換螢幕」程式碼研究室中,您已瞭解如何使用 Jetpack Navigation Compose 元件在 Compose 應用程式中新增導覽功能。

Cupcake 應用程式有多個畫面可前往,且使用者可以執行多項操作。這個應用程式為您提供了提升自動測試技能的絕佳機會!在本程式碼研究室中,您將為 Cupcake 應用程式編寫一些 UI 測試,並瞭解如何盡量擴大測試涵蓋範圍。

必要條件

課程內容

  • 使用 Compose 測試 Jetpack Navigation 元件。
  • 為每個 UI 測試建立一致的 UI 狀態。
  • 為測試建立輔助函式。

建構項目

  • Cupcake 應用程式的 UI 測試

軟硬體需求

  • 最新版 Android Studio
  • 需連線至網路來下載範例程式碼

2. 下載範例程式碼

  1. 在 Android Studio 中開啟 basic-android-kotlin-compose-training-cupcake 資料夾。
  2. 在 Android Studio 中開啟 Cupcake 應用程式程式碼。

3. 設定 Cupcake 以進行 UI 測試

新增 androidTest 依附元件

您可以使用 Gradle 建構工具為特定模組新增依附元件。這項功能可避免對依附元件進行不必要的編譯。在專案中加入依附元件時,您已熟悉 implementation 設定。您已使用此關鍵字在應用程式模組的 build.gradle.kts 檔案中匯入依附元件。使用 implementation 關鍵字,即可將依附元件提供給該模組中的所有來源集使用。在本課程的這一階段,您已經具備處理 main、test 和 androidTest 來源集的經驗。

UI 測試包含在名為 androidTest 的獨立來源集中。如果依附元件只需要應用於該模組,就不必針對其他模組 (例如包含應用程式程式碼的 main 模組) 進行編譯。新增僅用於 UI 測試的依附元件時,請使用 androidTestImplementation 關鍵字,在應用程式模組的 build.gradle.kts 檔案中宣告依附元件。這麼做可確保只有在您執行 UI 測試時,系統才會編譯 UI 測試依附元件。

請完成下列步驟,新增編寫 UI 測試所需的依附元件:

  1. 開啟 build.gradle.kts(Module :app) 檔案。
  2. 在檔案的 dependencies 部分新增下列依附元件:
androidTestImplementation(platform("androidx.compose:compose-bom:2023.05.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("androidx.navigation:navigation-testing:2.6.0")
androidTestImplementation("androidx.test.espresso:espresso-intents:3.5.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")

建立 UI 測試目錄

  1. 在專案檢視畫面中的 src 目錄上按一下滑鼠右鍵,然後依序選取「New」>「Directory」。

290b1ab99ce8f4e5.png

  1. 選取「androidTest/java」選項。

718bde5eb21b4f6f.png

建立測試套件

  1. 在專案視窗中的 androidTest/java 目錄上按一下滑鼠右鍵,然後依序選取「New」>「Package」。

7ad315f41e2b0881.png

  1. 將套件命名為 com.example.cupcake.test。b8306f9d11988a2.png

建立導覽測試類別

在 test 目錄中,建立名為 CupcakeScreenNavigationTest 的新 Kotlin 類別。

5515db35968f7d86.png4b527e7404fad927.png

4. 設定導覽主機

在先前的程式碼研究室中,您已瞭解 Compose 中的 UI 測試需具備 Compose 測試規則。測試 Jetpack Navigation 也是如此。不過,測試導覽時必須透過 Compose 測試規則進行一些額外設定。

測試 Compose Navigation 時,您無法在應用程式程式碼中存取相同的 NavHostController。不過,您可以使用 TestNavHostController 並設定這個導覽控制器的測試規則。本節將說明如何針對導覽測試設定測試規則並重複使用。

  1. 在 CupcakeScreenNavigationTest.kt 中使用 createAndroidComposeRule 建立測試規則,並將 ComponentActivity 做為類型參數傳遞。
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import org.junit.Rule

@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()

為確保應用程式導覽至正確的位置,您需要在應用程式執行導覽操作時,參照 TestNavHostController 例項來檢查導覽主機的導覽路徑。

  1. 對 TestNavHostController 例項進行例項化,使其成為 lateinit 變數。在 Kotlin 中,lateinit 關鍵字可用來宣告在宣告物件之後可初始化的屬性。
import androidx.navigation.testing.TestNavHostController

private lateinit var navController: TestNavHostController

接著,指定要用於 UI 測試的可組合函式。

  1. 建立名為 setupCupcakeNavHost() 的方法。
  2. 在 setupCupcakeNavHost() 方法中,針對您建立的 Compose 測試規則呼叫 setContent() 方法。
  3. 在傳遞至 setContent() 方法的 lambda 中,呼叫 CupcakeApp() 可組合函式。
import com.example.cupcake.CupcakeApp

fun setupCupcakeNavHost() {
    composeTestRule.setContent {
        CupcakeApp()
    }
}

現在,您必須在測試類別中建立 TestNavHostContoller 物件。您稍後會使用這個物件判斷導覽狀態,因為應用程式會使用控制器在 Cupcake 應用程式中的各畫面間導覽。

  1. 使用您先前建立的 lambda 設定導覽主機。初始化您建立的 navController 變數、註冊導覽器,然後將該 TestNavHostController 傳遞至 CupcakeApp 可組合函式。
import androidx.compose.ui.platform.LocalContext

fun setupCupcakeNavHost() {
    composeTestRule.setContent {
        navController = TestNavHostController(LocalContext.current).apply {
            navigatorProvider.addNavigator(ComposeNavigator())
        }
        CupcakeApp(navController = navController)
    }
}

CupcakeScreenNavigationTest 類別中的每個測試都包含導覽方面的測試。因此,每項測試都取決於您建立的 TestNavHostController 物件。您可以使用 junit 程式庫提供的 @Before 註解來自動設定導覽控制器,不必為每個測試手動呼叫 setupCupcakeNavHost() 函式。如果某個方法含有 @Before 註解,它會在每個含有 @Test 註解的方法之前執行。

  1. 將 @Before 註解新增至 setupCupcakeNavHost() 方法。
import org.junit.Before

@Before
fun setupCupcakeNavHost() {
    composeTestRule.setContent {
        navController = TestNavHostController(LocalContext.current).apply {
            navigatorProvider.addNavigator(ComposeNavigator())
        }
        CupcakeApp(navController = navController)
    }
}

5. 編寫導覽測試

驗證起始目標頁面

提醒您,建構 Cupcake 應用程式時,您會建立名為 CupcakeScreen 的 enum 類別,其中包含用來控管應用程式導覽的常數。

CupcakeScreen.kt

/**
* enum values that represent the screens in the app
*/
enum class CupcakeScreen(@StringRes val title: Int) {
   Start(title = R.string.app_name),
   Flavor(title = R.string.choose_flavor),
   Pickup(title = R.string.choose_pickup_date),
   Summary(title = R.string.order_summary)
}

所有具備 UI 的應用程式都有某種類型的主畫面。Cupcake 的主畫面是「Start Order Screen」。CupcakeApp 可組合函式中的導覽控制器會使用 CupcakeScreen 列舉的 Start 項目決定前往該畫面的時機。應用程式啟動後,如果目的地路徑尚未存在,系統會將導覽主機的目的地路徑設為 CupcakeScreen.Start.name。

您必須先編寫測試,用來驗證應用程式啟動時,「Start Order Screen」是否為當下的目的地路徑。

  1. 建立名為 cupcakeNavHost_verifyStartDestination() 的函式,並以 @Test 加上註解。
import org.junit.Test

@Test
fun cupcakeNavHost_verifyStartDestination() {
}

現在,您必須確認導覽控制器的初始目的地路徑為「Start Order Screen」。

  1. 斷言的預期路徑名稱 (本例中為 CupcakeScreen.Start.name) 等於導覽控制器目前返回堆疊項目的目的地路徑。
import org.junit.Assert.assertEquals
...

@Test
fun cupcakeNavHost_verifyStartDestination() {
    assertEquals(CupcakeScreen.Start.name, navController.currentBackStackEntry?.destination?.route)
}

建立輔助方法

UI 測試通常需要重複步驟,才能將 UI 設為可測試特定 UI 部分的狀態。自訂 UI 可能也會需要進行複雜斷言,此類斷言需要多行程式碼。您在上一節編寫的斷言需要大量程式碼,而您在 Cupcake 應用程式中測試導覽時會多次使用相同的斷言。在這種情況下,只要在測試中編寫輔助方法,即可不必編寫重複的程式碼。

針對每個您編寫的導覽測試,您可以使用 CupcakeScreen 列舉項目的 name 屬性,檢查導覽控制器目前的目的地路徑是否正確。您可以編寫輔助函式,以便在要進行此類斷言時隨時呼叫。

如要建立輔助函式,請完成下列步驟:

  1. 在 test 目錄中建立名為 ScreenAssertions 的空白 Kotlin 檔案。

63af08bd78a827c4.png

  1. 將擴充功能函式新增至名為 assertCurrentRouteName() 的 NavController 類別,並將預期路徑名稱的字串傳入方法簽章中。
fun NavController.assertCurrentRouteName(expectedRouteName: String) {

}
  1. 在這個函式中,斷言 expectedRouteName 等於導覽控制器目前的返回堆疊項目的目的地路徑。
import org.junit.Assert.assertEquals
...

fun NavController.assertCurrentRouteName(expectedRouteName: String) {
    assertEquals(expectedRouteName, currentBackStackEntry?.destination?.route)
}
  1. 開啟 CupcakeScreenNavigationTest 檔案,並修改 cupcakeNavHost_verifyStartDestination() 函式,以便使用新的擴充功能函式,而非冗長的斷言。
@Test
fun cupcakeNavHost_verifyStartDestination() {
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

許多測試也必須與 UI 元件互動。在本程式碼研究室中,通常可以使用資源字串找到這些元件。您可以使用 Context.getString() 方法,依據資源字串存取相關可組合函式,詳情請參閱這裡的相關說明。在 Compose 中編寫 UI 測試時,此方法的實作方式如下所示:

composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.my_string)

這是詳細的指示,且只要加入擴充功能函式即可予以簡化。

  1. 在 com.example.cupcake.test 套件中建立名為 ComposeRuleExtensions.kt 的新檔案。請確認這是純文字的 Kotlin 檔案。

5710fc2d8219048b.png

  1. 將以下程式碼加到該檔案中。
import androidx.activity.ComponentActivity
import androidx.annotation.StringRes
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.rules.ActivityScenarioRule

fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.onNodeWithStringId(
    @StringRes id: Int
): SemanticsNodeInteraction = onNodeWithText(activity.getString(id))

這個擴充功能函式可讓您在依字串資源尋找 UI 元件時,減少編寫的程式碼數量,而不是以下列方式編寫:

composeTestRule.onNodeWithText(composeTestRule.activity.getString(R.string.my_string)

現在可以使用下列指示:

composeTestRule.onNodeWithStringId(R.string.my_string)

確認「Start」畫面沒有「向上」按鈕

Cupcake 應用程式的原始設計中,「Start」畫面的工具列並不包含「向上」按鈕。

ffb8d156220c7e57.png

「Start」畫面缺少向上按鈕,原因是這個畫面是初始畫面,無法向上導覽。請按照下列步驟建立用於確認「開始」畫面沒有「向上」按鈕的函式:

  1. 建立名為 cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() 的方法,並加上 @Test 註解。
@Test
fun cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() {
}

在 Cupcake 中,「向上」按鈕的內容說明會設為 R.string.back_button 資源的字串。

  1. 在測試函式中使用 R.string.back_button 資源的值建立變數。
@Test
fun cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() {
    val backText = composeTestRule.activity.getString(R.string.back_button)
}
  1. 斷言螢幕畫面上沒有包含此內容說明的節點。
@Test
fun cupcakeNavHost_verifyBackNavigationNotShownOnStartOrderScreen() {
    val backText = composeTestRule.activity.getString(R.string.back_button)
    composeTestRule.onNodeWithContentDescription(backText).assertDoesNotExist()
}

驗證前往「Flavor」畫面的導覽

點選「Start」畫面上的任一按鈕,都能觸發會指示導覽控制器前往「Flavor」畫面的方法。

3bda045bfe202c90.png

在本測試中,您將編寫指令,用於在使用者點選按鈕時,觸發導覽並驗證目的地路徑為「Flavor」畫面。

  1. 建立名為 cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen() 的函式,並加上 @Test 註解。
@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen(){
}
  1. 依據字串資源 ID 找出「One Cupcake」按鈕,然後對其執行點擊動作。
import com.example.cupcake.R
...

@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen() {
    composeTestRule.onNodeWithStringId(R.string.one_cupcake)
        .performClick()
}
  1. 斷言目前的路徑名稱為「Flavor」畫面名稱。
@Test
fun cupcakeNavHost_clickOneCupcake_navigatesToSelectFlavorScreen() {
    composeTestRule.onNodeWithStringId(R.string.one_cupcake)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Flavor.name)
}

編寫更多輔助方法

Cupcake 應用程式主要採用線性導覽流程。如果不點選「Cancel」按鈕,則只能向單一方向瀏覽應用程式。因此,當您要測試應用程式中更深層的畫面時,會發現自己要重複編寫程式碼才能前往要測試的區域。在這種情況下,有必要使用更多輔助方法,讓您只需要編寫一次程式碼。

現在,您已測試了前往「Flavor」畫面的導覽,接著請建立前往「Flavor」畫面的方法,這樣您就不需要在後續測試中重複編寫該程式碼。

  1. 建立名為 navigateToFlavorScreen() 的方法。
private fun navigateToFlavorScreen() {
}
  1. 依照上一節介紹的做法,編寫指令來找出「One Cupcake」按鈕,並對其執行點擊操作。
private fun navigateToFlavorScreen() {
    composeTestRule.onNodeWithStringId(R.string.one_cupcake)
        .performClick()
}

提醒您,在選取口味之前,「Flavor」畫面的「Next」按鈕無法點選。這個方法只能用來讓 UI 做好導覽準備。呼叫此方法後,UI 應處於「Next」按鈕可點選的狀態。

  1. 在 UI 中找到含有 R.string.chocolate 字串的節點,然後對其執行點擊動作來選取節點。
private fun navigateToFlavorScreen() {
    composeTestRule.onNodeWithStringId(R.string.one_cupcake)
        .performClick()
    composeTestRule.onNodeWithStringId(R.string.chocolate)
        .performClick()
}

請確認您是否能編寫輔助方法,用來前往「Pickup」畫面和「Summary」畫面。請自行練習後再查看解決方案。

請使用以下程式碼完成這項操作:

private fun getFormattedDate(): String {
    val calendar = Calendar.getInstance()
    calendar.add(java.util.Calendar.DATE, 1)
    val formatter = SimpleDateFormat("E MMM d", Locale.getDefault())
    return formatter.format(calendar.time)
}
private fun navigateToPickupScreen() {
    navigateToFlavorScreen()
    composeTestRule.onNodeWithStringId(R.string.next)
        .performClick()
}

private fun navigateToSummaryScreen() {
    navigateToPickupScreen()
    composeTestRule.onNodeWithText(getFormattedDate())
        .performClick()
    composeTestRule.onNodeWithStringId(R.string.next)
        .performClick()
}

測試「Start」畫面以外的畫面時,必須規劃「向上」按鈕功能測試,確保它能引導使用者前往上一個畫面。請考慮建立輔助函式來找出並點選「向上」按鈕。

private fun performNavigateUp() {
    val backText = composeTestRule.activity.getString(R.string.back_button)
    composeTestRule.onNodeWithContentDescription(backText).performClick()
}

盡量擴大測試涵蓋範圍

應用程式的測試套件應盡可能測試更多的應用程式功能。最完美的情況,是 UI 測試套件的範圍能涵蓋 100% 的 UI 功能。實際上,由於應用程式有許多會影響 UI 的外部因素,因此測試很難達到完美的涵蓋範圍。例如裝置有特殊的螢幕尺寸、Android 作業系統版本不同,以及會影響手機上其他應用程式的第三方應用程式等。

如要盡可能擴大測試涵蓋範圍,其中一種方法是在新增功能時一併編寫測試。如此一來,您就不必在新功能已有深入開發進展時,還必須重新回想所有可能的情境。目前,Cupcake 是相當小的應用程式,您已測試該應用程式導覽的一大部分!不過,還有其他導覽狀態需要測試。

請確認您是否能編寫測試來驗證下列導覽狀態。建議您先自行實作,再查看解決方案。

  • 按一下「Flavor」畫面中的「向上」按鈕,前往「Start」畫面
  • 按一下「Flavor」畫面中的「Cancel」按鈕,前往「Start」畫面
  • 前往「Pickup」畫面
  • 按一下「Pickup」畫面中的「向上」按鈕,前往「Flavor」畫面
  • 按一下「Pickup」畫面中的「Cancel」按鈕,前往「Start」畫面
  • 前往「Summary」畫面
  • 按一下「Summary」畫面中的「Cancel」按鈕,前往「Start」畫面。
@Test
fun cupcakeNavHost_clickNextOnFlavorScreen_navigatesToPickupScreen() {
    navigateToFlavorScreen()
    composeTestRule.onNodeWithStringId(R.string.next)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Pickup.name)
}

@Test
fun cupcakeNavHost_clickBackOnFlavorScreen_navigatesToStartOrderScreen() {
    navigateToFlavorScreen()
    performNavigateUp()
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

@Test
fun cupcakeNavHost_clickCancelOnFlavorScreen_navigatesToStartOrderScreen() {
    navigateToFlavorScreen()
    composeTestRule.onNodeWithStringId(R.string.cancel)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

@Test
fun cupcakeNavHost_clickNextOnPickupScreen_navigatesToSummaryScreen() {
    navigateToPickupScreen()
    composeTestRule.onNodeWithText(getFormattedDate())
        .performClick()
    composeTestRule.onNodeWithStringId(R.string.next)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Summary.name)
}

@Test
fun cupcakeNavHost_clickBackOnPickupScreen_navigatesToFlavorScreen() {
    navigateToPickupScreen()
    performNavigateUp()
    navController.assertCurrentRouteName(CupcakeScreen.Flavor.name)
}

@Test
fun cupcakeNavHost_clickCancelOnPickupScreen_navigatesToStartOrderScreen() {
    navigateToPickupScreen()
    composeTestRule.onNodeWithStringId(R.string.cancel)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

@Test
fun cupcakeNavHost_clickCancelOnSummaryScreen_navigatesToStartOrderScreen() {
    navigateToSummaryScreen()
    composeTestRule.onNodeWithStringId(R.string.cancel)
        .performClick()
    navController.assertCurrentRouteName(CupcakeScreen.Start.name)
}

6. 編寫「Order」畫面的測試

導覽只是 Cupcake 應用程式功能的一部分。使用者也會與每個應用程式畫面互動。您需要驗證這些畫面上顯示的內容,以及在這些畫面上執行的動作是否會產生正確結果。SelectOptionScreen 是應用程式的重要部分。

在本節中,您將編寫測試,驗證此畫面中的內容是否已正確設定。

測試「Choose Flavor」畫面內容

  1. 在 app/src/androidTest 目錄中建立名為 CupcakeOrderScreenTest 的新類別,這個目錄包含其他測試檔案。

1aaa3be367a02dcd.png

  1. 在這個類別中,建立 AndroidComposeTestRule。
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
  1. 建立名為 selectOptionScreen_verifyContent() 的函式,並以 @Test 加上註解。
@Test
fun selectOptionScreen_verifyContent() {

}

在此函式中,您最後會將 Compose 規則內容設為 SelectOptionScreen。這麼做可確保 SelectOptionScreen 可組合函式會直接啟動,因此不需要導覽。不過,這個畫面需要兩個參數:口味選項清單和小計。

  1. 建立要傳遞給畫面的口味選項清單和小計。
@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"
}
  1. 使用剛才建立的值,將內容設為 SelectOptionScreen 可組合函式。

請注意,這個方法與從 MainActivity 啟動可組合函式類似。唯一的差別在於,MainActivity 會呼叫 CupcakeApp 可組合函式,而此處會呼叫 SelectOptionScreen 可組合函式。透過變更從 setContent() 啟動的可組合函式,您可以啟動特定可組合函式,不必在測試中循序漸進地執行應用程式來前往要測試的區域。這種做法有助於避免在與目前測試無關的程式碼中失敗。

@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"

    // When SelectOptionScreen is loaded
    composeTestRule.setContent {
        SelectOptionScreen(subtotal = subtotal, options = flavors)
    }
}

在測試的當前階段,應用程式會啟動 SelectOptionScreen 可組合函式,然後您就可以透過測試操作說明與其互動。

  1. 透過 flavors 清單進行疊代,確保清單中的每個字串項目都顯示在畫面上。
  2. 使用 onNodeWithText() 方法尋找畫面上的文字,並使用 assertIsDisplayed() 方法驗證文字是否已顯示在應用程式中。
@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"

    // When SelectOptionScreen is loaded
    composeTestRule.setContent {
        SelectOptionScreen(subtotal = subtotal, options = flavors)
    }

    // Then all the options are displayed on the screen.
    flavors.forEach { flavor ->
        composeTestRule.onNodeWithText(flavor).assertIsDisplayed()
    }
}
  1. 使用相同技巧驗證應用程式是否會顯示文字,確認應用程式已在螢幕上顯示正確的小計字串。在畫面上搜尋 R.string.subtotal_price 資源 ID 和正確的小計值,然後斷言應用程式會顯示該值。
import com.example.cupcake.R
...

@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"

    // When SelectOptionScreen is loaded
    composeTestRule.setContent {
        SelectOptionScreen(subtotal = subtotal, options = flavors)
    }

    // Then all the options are displayed on the screen.
    flavors.forEach { flavor ->
        composeTestRule.onNodeWithText(flavor).assertIsDisplayed()
    }

    // And then the subtotal is displayed correctly.
    composeTestRule.onNodeWithText(
        composeTestRule.activity.getString(
            R.string.subtotal_price,
            subtotal
        )
    ).assertIsDisplayed()
}

提醒您,您必須先選取項目,才能啟用「Next」按鈕。這項測試只會驗證畫面內容,因此最後要測試的部分是「Next」按鈕是否已停用。

  1. 使用相同方法,透過字串資源 ID 尋找節點,來找出「Next」按鈕。不過,請使用 assertIsNotEnabled() 方法,而不要驗證應用程式是否顯示節點。
@Test
fun selectOptionScreen_verifyContent() {
    // Given list of options
    val flavors = listOf("Vanilla", "Chocolate", "Hazelnut", "Cookie", "Mango")
    // And subtotal
    val subtotal = "$100"

    // When SelectOptionScreen is loaded
    composeTestRule.setContent {
        SelectOptionScreen(subtotal = subtotal, options = flavors)
    }

    // Then all the options are displayed on the screen.
    flavors.forEach { flavor ->
        composeTestRule.onNodeWithText(flavor).assertIsDisplayed()
    }

    // And then the subtotal is displayed correctly.
    composeTestRule.onNodeWithText(
        composeTestRule.activity.getString(
            R.string.subtotal_price,
            subtotal
        )
    ).assertIsDisplayed()

    // And then the next button is disabled
    composeTestRule.onNodeWithStringId(R.string.next).assertIsNotEnabled()
}

盡量擴大測試涵蓋範圍

「Choose Flavor」畫面內容測試只能測試單一畫面的一個部分。您可以編寫一些額外測試來擴大程式碼涵蓋範圍。下載解決方案程式碼前,請先嘗試自行編寫下列測試。

  • 驗證「Start」畫面內容。
  • 驗證「Summary」畫面內容。
  • 驗證在「Choose Flavor」畫面中選取選項時,「Next」按鈕為啟用狀態。

編寫測試時,請留意任何有助於減少程式碼編寫量的輔助函式。

7. 取得解決方案程式碼

完成程式碼研究室後,如要下載當中用到的程式碼,您可以使用以下 Git 指令:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-cupcake.git

另外,您也可以下載存放區為 ZIP 檔案,然後解壓縮並在 Android Studio 中開啟。

如要查看解決方案程式碼,請前往 GitHub。

8. 摘要

恭喜!您已瞭解如何測試 Jetpack Navigation 元件,也學到了編寫 UI 測試的一些基本技能,例如編寫可重複使用的輔助方法、如何利用 setContent() 編寫簡潔測試、透過 @Before 註解設定測試,以及如何盡可能擴展測試涵蓋率。往後建構 Android 應用程式時,除了編寫應用程式功能外,也別忘了編寫測試!