1. 事前準備
在之前的程式碼研究室中,您已學過如何利用 Navigation 元件進行瀏覽。在本程式碼研究室中,您將學習如何測試 Navigation 元件。請注意,這與不使用 Navigation 元件測試導覽功能的情況不同。
必要條件
- 您已在 Android Studio 中建立測試目錄。
- 您已在 Android Studio 中編寫了單元和檢測設備測試。
- 您已將 Gradle 依附元件新增至 Android 專案。
課程內容
- 如何利用檢測設備測試來測試 Navigation 元件。
- 如何不使用重複的程式碼設定測試。
軟硬體需求
- 安裝 Android Studio 的電腦。
- Words 應用程式的程式碼解答。
下載本程式碼研究室的範例程式碼
在本程式碼研究室中,您將新增檢測設備測試到 Words 應用程式的程式碼解答。
- 前往專案指定的 GitHub 存放區頁面。
- 驗證分支版本名稱與程式碼研究室中指定的分支版本名稱相符。例如,在下列螢幕截圖中,分支版本名稱為「main」。
- 在專案的 GitHub 頁面中,按一下「Code」按鈕,畫面上會出現彈出式視窗。
- 在彈出式視窗中,按一下「Download ZIP」按鈕,將專案儲存至電腦。等待下載作業完成。
- 在電腦中找到該檔案 (可能位於「下載」資料夾中)。
- 按兩下解壓縮 ZIP 檔案。這項操作會建立含有專案檔案的新資料夾。
在 Android Studio 中開啟專案
- 啟動 Android Studio。
- 在「Welcome to Android Studio」視窗中,按一下「Open」。
注意:如果 Android Studio 已開啟,請改為依序選取「File」>「Open」選單選項。
- 在檔案瀏覽器中,前往已解壓縮的專案資料夾所在的位置 (可能位於「Downloads」資料夾中)。
- 按兩下該專案資料夾。
- 等待 Android Studio 開啟專案。
- 按一下「Run」按鈕 即可建構並執行應用程式,請確認應用程式的建構符合預期。
2. 範例應用程式總覽
Words 應用程式的主畫面會顯示一份清單,每個清單項目都是字母表中的一個字母。按一下其中一個字母,螢幕上會顯示該字母開頭的字詞清單。
3. 建立測試目錄
如有需要,請按照之前的程式碼研究室步驟,建立 Words 應用程式的檢測設備測試目錄。如果您已完成這個步驟,請直接跳到「新增必要的依附元件」。
4. 建立檢測設備測試類別
在「androidTest」資料夾中,建立名為 NavigationTests.kt 的新類別。
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 元件時,我們實際上並未讓裝置/模擬器執行明顯的導覽操作,而是會以不實際變更裝置或模擬器上顯示的內容為前提,強制導覽控制器來瀏覽,然後才確認其是否抵達正確的目的地。
- 建立名為
navigate_to_words_nav_component()
的測試函式。 - 在測試中使用 Navigation 元件需要進行一些設定。請在
navigate_to_words_nav_component()
方法中,建立導覽控制器的測試執行個體。
val navController = TestNavHostController(
ApplicationProvider.getApplicationContext()
)
- Navigation 元件會使用 Fragment 驅動 UI。其中一個相當於
ActivityScenarioRule
的片段,可用來隔離要測試的片段,所以需要片段專屬的依附元件。測試需要大量導覽的片段時,這個做法很實用,因為啟動不必額外的程式碼處理導覽目的地。
val letterListScenario = launchFragmentInContainer<LetterListFragment>(themeResId =
R.style.Theme_Words)
這裡會指出我們想要啟動 LetterListFragment
。我們必須傳遞應用程式的主題,讓 UI 元件知道要使用哪個主題,否則測試可能異常終止。
- 最後,我們需要明確宣告片段啟動後,導覽控制器要使用哪個導覽圖。
letterListScenario.onFragment { fragment ->
navController.setGraph(R.navigation.nav_graph)
Navigation.setViewNavController(fragment.requireView(), navController)
}
- 接著觸發提示導覽的事件。
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions
.actionOnItemAtPosition<RecyclerView.ViewHolder>(2, click()))
使用 launchFragmentInContainer()
方法時,實際導覽是不可能的,因為容器不知道我們可能前往的其他片段或活動。容器只知道我們指定啟動的片段。因此,在裝置或模擬器上執行測試時,您不會看到實際的導覽。或許這不符合直覺,但我們可以根據目前的目的地,做出更直接的判斷。與其尋找已知會顯示在特定畫面的 UI 元件,我們可以直接檢查目前導覽控制器的目的地,確認是否包含預期的片段 ID。此方法比上述做法可靠許多。
assertEquals(navController.currentDestination?.id, R.id.wordListFragment)
您的測試看起來應像這樣:
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
註解,放在隨附物件中。若要示範這些註解的執行順序,請參考下列程式碼:
請記得,@BeforeClass
會針對類別執行,@Before
會在函式執行前執行,@After
會在函式執行後執行,@AfterClass
也會針對類別執行。您能預測以下內容的輸出結果嗎?
函式的執行順序為 setupClass()
、setupFunction()
、test_a()
、tearDownFunction()
、setupFunction()
、test_b()
、tearDownFunction()
、setupFunction()
、test_c()
、tearDownFunction()
、tearDownClass()
。這個執行順序很合理,因為 @Before
和 @After
會分別在每個方法前後執行。@BeforeClass
會在類別中的任何項目執行前執行一次,@AfterClass
則在類別的所有其他項目執行後執行一次。
9. 恭喜
在本程式碼研究室中,您已完成以下事項:
- 學習測試 Navigation 元件的方法。
- 瞭解如何使用
@Before
、@BeforeClass
、@After
和@AfterClass
註解,避免重複的程式碼。