測試 Navigation 元件

1. 事前準備

在之前的程式碼研究室中,您已學過如何利用 Navigation 元件進行瀏覽。在本程式碼研究室中,您將學習如何測試 Navigation 元件。請注意,這與不使用 Navigation 元件測試導覽功能的情況不同。

必要條件

  • 您已在 Android Studio 中建立測試目錄。
  • 您已在 Android Studio 中編寫了單元和檢測設備測試。
  • 您已將 Gradle 依附元件新增至 Android 專案。

課程內容

  • 如何利用檢測設備測試來測試 Navigation 元件。
  • 如何不使用重複的程式碼設定測試。

軟硬體需求

  • 安裝 Android Studio 的電腦。
  • Words 應用程式的程式碼解答。

下載本程式碼研究室的範例程式碼

在本程式碼研究室中,您將新增檢測設備測試到 Words 應用程式的程式碼解答。

  1. 前往專案指定的 GitHub 存放區頁面。
  2. 驗證分支版本名稱與程式碼研究室中指定的分支版本名稱相符。例如,在下列螢幕截圖中,分支版本名稱為「main」

1e4c0d2c081a8fd2.png

  1. 在專案的 GitHub 頁面中,按一下「Code」按鈕,畫面上會出現彈出式視窗。

1debcf330fd04c7b.png

  1. 在彈出式視窗中,按一下「Download ZIP」按鈕,將專案儲存至電腦。等待下載作業完成。
  2. 在電腦中找到該檔案 (可能位於「下載」資料夾中)。
  3. 按兩下解壓縮 ZIP 檔案。這項操作會建立含有專案檔案的新資料夾。

在 Android Studio 中開啟專案

  1. 啟動 Android Studio。
  2. 在「Welcome to Android Studio」視窗中,按一下「Open」

d8e9dbdeafe9038a.png

注意:如果 Android Studio 已開啟,請改為依序選取「File」>「Open」選單選項。

8d1fda7396afe8e5.png

  1. 在檔案瀏覽器中,前往已解壓縮的專案資料夾所在的位置 (可能位於「Downloads」資料夾中)。
  2. 按兩下該專案資料夾。
  3. 等待 Android Studio 開啟專案。
  4. 按一下「Run」按鈕 8de56cba7583251f.png 即可建構並執行應用程式,請確認應用程式的建構符合預期。

2. 範例應用程式總覽

Words 應用程式的主畫面會顯示一份清單,每個清單項目都是字母表中的一個字母。按一下其中一個字母,螢幕上會顯示該字母開頭的字詞清單。

3. 建立測試目錄

如有需要,請按照之前的程式碼研究室步驟,建立 Words 應用程式的檢測設備測試目錄。如果您已完成這個步驟,請直接跳到「新增必要的依附元件」。

4. 建立檢測設備測試類別

在「androidTest」資料夾中,建立名為 NavigationTests.kt 的新類別。

b023023a2ccc3813.png

5. 新增必要的依附元件

測試導覽元件時,會需要某些特定的 Gradle 依附元件。另外,我們會提供依附元件,以特定方式測試片段。前往應用程式模組的「build.gradle」檔案,並新增下列依附元件:

androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.4.0'
androidTestImplementation 'androidx.navigation:navigation-testing:2.5.2'

debugImplementation 'androidx.fragment:fragment-testing:1.5.3'

接著同步專案。

6. 編寫 Navigation 元件測試

測試 Navigation 元件不同於測試一般導覽。測試一般導覽時,我們會在裝置或模擬器上觸發導覽操作來執行。但測試 Navigation 元件時,我們實際上並未讓裝置/模擬器執行明顯的導覽操作,而是會以不實際變更裝置或模擬器上顯示的內容為前提,強制導覽控制器來瀏覽,然後才確認其是否抵達正確的目的地。

  1. 建立名為 navigate_to_words_nav_component() 的測試函式。
  2. 在測試中使用 Navigation 元件需要進行一些設定。請在 navigate_to_words_nav_component() 方法中,建立導覽控制器的測試執行個體。
val navController = TestNavHostController(
   ApplicationProvider.getApplicationContext()
)
  1. Navigation 元件會使用 Fragment 驅動 UI。其中一個相當於 ActivityScenarioRule 的片段,可用來隔離要測試的片段,所以需要片段專屬的依附元件。測試需要大量導覽的片段時,這個做法很實用,因為啟動不必額外的程式碼處理導覽目的地。
val letterListScenario = launchFragmentInContainer<LetterListFragment>(themeResId =
R.style.Theme_Words)

這裡會指出我們想要啟動 LetterListFragment。我們必須傳遞應用程式的主題,讓 UI 元件知道要使用哪個主題,否則測試可能異常終止。

  1. 最後,我們需要明確宣告片段啟動後,導覽控制器要使用哪個導覽圖。
letterListScenario.onFragment { fragment ->

   navController.setGraph(R.navigation.nav_graph)

   Navigation.setViewNavController(fragment.requireView(), navController)
}
  1. 接著觸發提示導覽的事件。
onView(withId(R.id.recycler_view))
   .perform(RecyclerViewActions
       .actionOnItemAtPosition<RecyclerView.ViewHolder>(2, click()))

使用 launchFragmentInContainer() 方法時,實際導覽是不可能的,因為容器不知道我們可能前往的其他片段或活動。容器只知道我們指定啟動的片段。因此,在裝置或模擬器上執行測試時,您不會看到實際的導覽。或許這不符合直覺,但我們可以根據目前的目的地,做出更直接的判斷。與其尋找已知會顯示在特定畫面的 UI 元件,我們可以直接檢查目前導覽控制器的目的地,確認是否包含預期的片段 ID。此方法比上述做法可靠許多。

assertEquals(navController.currentDestination?.id, R.id.wordListFragment)

您的測試看起來應像這樣:78b4a72f75134ded.png

7. 程式碼解答

8. 避免包含備註的重複程式碼

在 Android 中,檢測設備測試和單元測試都有功能可以不必重複程式碼,即可設定類別中每個測試相同的設定。

假設我們使用包含 10 個按鈕的片段。按一下按鈕後,按鈕會導向特定的片段。

如果我們按照上述測試的模式,可能必須分別為這 10 次測試重複使用下列程式碼 (請注意,此程式碼只是範例,不會在本程式碼研究室使用的應用程式中編譯):

val navController = TestNavHostController(
    ApplicationProvider.getApplicationContext()
)

val exampleFragmentScenario = launchFragmentInContainer<ExampleFragment>(themeResId =
R.style.Theme_Example)

exampleFragmentScenario.onFragment { fragment ->

   navController.setGraph(R.navigation.example_nav_graph)

   Navigation.setViewNavController(fragment.requireView(), navController)
}

重複 10 次的程式碼非常冗長。在這個案例中,我們可以使用 JUnit 提供的 @Before 註解來節省寶貴的時間。即在一個方法上加上註解,並於其中提供測試設定所需的程式碼。我們可以隨意命名這個方法,但名稱必須有關連性。我們不必重複設定相同的片段 10 次,而是按照以下範例撰寫設定程式碼:

lateinit var navController: TestNavHostController

lateinit var exampleFragmentScenario: FragmentScenario<ExampleFragment>

@Before
fun setup(){
    navController = TestNavHostController(
        ApplicationProvider.getApplicationContext()
    )

    exampleFragmentScenario =  launchFragmentInContainer(themeResId=R.style.Theme_Example)

    exampleFragmentScenario.onFragment { fragment ->

       navController.setGraph(R.navigation.example_nav_graph)

       Navigation.setViewNavController(fragment.requireView(),  navController)
    }
}

現在,此方法會對我們在這個類別中編寫的每項測試執行,且我們可從任何一項測試存取必要的變數。

以此類推,若要每次測試後執行程式碼,也可使用 @After 註解。例如,@After 可用來清理用於測試的資源,對檢測設備測試來說,也可用來將裝置回復至特定狀態。

JUnit 也提供 @BeforeClass@AfterClass 註解。不同之處在於此註解的方法僅執行一次,但已執行的程式碼仍會套用至每個方法。如果您的設定或中止方法涉及消耗大量資源的作業,建議您改用這些註解。使用 @BeforeClass@AfterClass 註解的方法必須搭配 @JvmStatic 註解,放在隨附物件中。若要示範這些註解的執行順序,請參考下列程式碼:

5157ab00a9b7fb84.png

請記得,@BeforeClass 會針對類別執行,@Before 會在函式執行前執行,@After 會在函式執行後執行,@AfterClass 也會針對類別執行。您能預測以下內容的輸出結果嗎?

39c04aa2ba7b8348.png

函式的執行順序為 setupClass()setupFunction()test_a()tearDownFunction()setupFunction()test_b()tearDownFunction()setupFunction()test_c()tearDownFunction()tearDownClass()。這個執行順序很合理,因為 @Before@After 會分別在每個方法前後執行。@BeforeClass 會在類別中的任何項目執行前執行一次,@AfterClass 則在類別的所有其他項目執行後執行一次。

9. 恭喜

在本程式碼研究室中,您已完成以下事項:

  • 學習測試 Navigation 元件的方法。
  • 瞭解如何使用 @Before@BeforeClass@After@AfterClass 註解,避免重複的程式碼。