測試 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 關鍵字,即可將依附元件提供給該模組中的所有來源集使用。在本課程的這一階段,您已經具備處理 maintestandroidTest 來源集的經驗。

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」

31f950996e51807b.png

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

e1e4d108c863ae27.png

建立測試套件

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

c5cdd7125e61bf22.png

  1. 將套件命名為 com.example.cupcake.testb6cec61624467422.png

建立導覽測試類別

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

8da2547b2ef1cc8e.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 應用程式時,您會建立名為 CupcakeScreenenum 類別,其中包含用來控管應用程式導覽的常數。

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 檔案。

8c3c7146050db2a8.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 檔案。

82762f66f890cf1b.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」畫面的工具列中沒有「向上」按鈕。

886fb7e824608c92.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」畫面的方法。

82c62c13891d627e.png

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

  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 的新類別,其中包含其他測試檔案。

4409b8333eff0ba2.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 應用程式時,除了編寫應用程式功能外,也別忘了編寫測試!