詳細了解測試以及如何新增測試
依賴項(dependencies)
。進一步練習編寫單元測試(unit test)
和設備測試(instrumentation test)
。
學習目標
- 撰寫測試的基本概念。
- 如何新增測試專用的
Gradle
依附元件。 - 如何透過
檢測設備測試(instrumentation tests)
,來測試List。
最佳做法
測試程式碼在設計上會與應用程式的商業邏輯有所不同。原因是測試不應包含任何邏輯內容;只單純進行測試而已。因此,測試不應有條件陳述式 (例如 if 或 when),或者控制流程陳述式 (例如 for 或 while)。也不得操控值或執行任何實際的運算。
有時候,您的測試可能需要其中一些項目,不過一般而言,請避免使用這些項目。由於我們想要在應用程式中測試這個邏輯類型,因此如果測試中有這類程式碼,可能就會失敗,就像應用程式的程式碼可能會失敗一樣。
我們的單元測試只應從應用程式呼叫部分測試所需的程式碼,並測試呼叫這些程式碼時所產生的程式碼值或狀態。UI 測試則只應測試使用者介面的預期狀態。
建立測試目錄
在先前的筆記中,我們已說明如何建立 androidTest
目錄來進行檢測設備測試。針對 androidTest
目錄和 test
目錄,重複這個專案的流程。這兩者的流程相同,唯一的差別是對於 test
目錄,您必須從「New Directory」下拉式選單中選取「test/java」,而不是「androidTest/java」。接著請為每個名稱為 com.example.affirmations
的新目錄建立新套件。

建立檢測設備測試類別
在「androidTest」->「com.example.affirmations」路徑中,建立名為 AffirmationsListTests.kt
的新類別。
與 Dice Roller 應用程式一樣,Affirmations 只有一個活動。為了測試活動的使用者介面,我們必須指明要啟動活動。試試看能不能自己回想出做法!
- 在新建的類別中加入測試執行器。
1 |
- 為主要活動建立活動情境規則。
1 |
|
- Affirmations 應用程式會顯示圖片及各自的正向肯定語錄清單。使用者介面不允許與項目進行任何互動,例如點擊或滑動。因此,這個應用程式的檢測設備測試只會測試靜態資料。建立名為
scroll_to_item()
的測試方法。請記得加上@Test
的註解。
這項測試應捲動至清單中的特定項目。我們尚未介紹這個方式,因為這需要用到專案尚未參照的方法。在繼續測試之前,需要新增一些測試依附元件。
新增檢測設備測試dependencies
您應該已大致瞭解如何在應用程式的程式碼中,新增要使用的 Gradle 依附元件。透過 Gradle,我們也能新增單元測試和檢測設備測試專用的dependencies。方法是依序前往「app」->「build.gradle」,開啟應用程式層級的 build.gradle
檔案。「依附元件(dependencies)」部分會列出三種實作依附元件的方式:implementation
、testImplementation
和 androidTestImplementation
。
implementation
適用於應用程式本身會使用的依附元件,testImplementation
適用於單元測試中使用的依附元件,androidTestImplementation
則適用於檢測設備測試中使用的依附元件。
- 新增依附元件,允許在檢測設備測試中與
RecyclerView
互動。將下列程式庫新增為androidTestImplementation
:
1 | androidx.test.espresso:espresso-contrib:3.5.1 |
dependencies看起來會像這樣:
1 | dependencies { |
- 接著點選 Sync Now(同步)。
測試 RecyclerView
- 專案同步處理後,請返回
AffirmationsListTests.kt
檔案。提供ViewInteraction
,以使用onView()
執行動作。onView()
方法需要傳入ViewMatcher
。使用withId()
,確認傳入的是用於確認的RecyclerView
ID。立即在ViewInteraction
上呼叫perform()
。也就是新加入的依附元件!現在可以傳入RecyclerViewActions.scrollToPosition<RecyclerView.Viewholder>(9) ViewAction
。
1 | onView(withId(R.id.recycler_view)).perform( |
瞭解這一行的語法不太重要,但還是值得看一看。RecyclerViewActions
這個名稱與跟名稱所暗示的一樣:讓您在 RecyclerView
執行測試的類別。scrollToPosition()
是 RecyclerViewActions
類別中的靜態方法,可捲動至指定位置。這個方法會傳回「一般」內容。「一般」內容不在這個程式碼研究室的涵蓋範圍內,但在此案例中,您可以把它想成是 scrollToPosition()
方法,可傳回 RecyclerView
中的所有項目 (可能是任何內容)。
在我們的應用程式中,RecyclerView
中的項目是 ViewHolder
,因此我們會在方法呼叫完成後置入一對角括號,並在其中指定 RecyclerView.ViewHolder
。最後,請傳遞清單中的最後一個位置 (9)
。
- 現在已經可以捲動至
RecyclerView
的所需位置,因此請做出斷言,確保 UI 顯示的是預期資訊。確保當您捲動至最後一個項目後,系統會顯示與最終肯定相關聯的文字。請從ViewInteraction
開始,但這次在新的ViewMatcher
中傳遞 (在本案例中為withText()
)。對於此方法,請傳送包含最後一個肯定語錄文字的字串資源。withText()
方法會根據顯示的文字來識別使用者介面元件。
對於這項元件,只需檢查元件中是否顯示所需的文字即可。方法是透過在 ViewInteraction
上呼叫 check()
。check()
需要 ViewAssertion
,因此您可以使用 matches()
方法。最後,傳遞 isDisplayed()
方法,宣告要顯示使用者介面元件。
1 | onView(withText(R.string.affirmation10)) |
回到用硬式編碼的方式設定捲動目標位置的附註,有一種方式可透過 RecyclerViewActions
解決此問題。當您不確定清單長度時,可以使用 scrollTo
動作。如要使用 scrollTo
函式,您需要使用 Matcher<View!>!
來尋找特定項目。這可以包含許多項目,但若要達到這項測試的目的,請使用 withText
。將其套用到您剛才編寫的測試後,程式碼看起來會像這樣:
1 | /* 捲動到字串10的位置 */ |
解決 bug
原 google 課程的 code 有 bug,會執行失敗:
1 | onView(withId(R.id.recycler_view)).perform( |
參考這篇文章,在 withText()
外用 hasDescendant()
包起來,即可執行成功。
1 | onView(withId(R.id.recycler_view)).perform( |
現在一切已準備就緒,隨時可以執行測試。裝置或模擬器應捲動到清單底部,然後才通過測試。如要確保測試結果正確無誤,請將字串 ID 替換成 R.string.affirmation1
。捲動完成後,這個字串資源就不會顯示,且測試應會失敗。
RecyclerViewActions
類別提供的方法有很多種,建議您查看可用的方法。
建立本機測試類別
在「test」->「com.example.affirmations」路徑中,建立名為 AffirmationsAdapterTests.kt
的新類別。

新增本機測試dependencies
在本程式碼研究室的前半部,我們討論了三種依附元件(dependencies)實作方式,且您新增了檢測設備測試的依附元件。現在請新增本機測試的依附元件。方法是依序前往「app」->「build.gradle」,並將以下內容新增為單位測試依附元件:
1 | org.mockito:mockito-core:3.12.4 |
dependencies應如下所示:
1 | dependencies { |
- 接著點選 Sync Now(同步)。
測試 Adapter
這個應用程式本身不需要進行單元測試,因為沒有足夠的邏輯可以測試。然而,我們可以取得測試各項元件的更多經驗,為日後的測試做準備。
- 在單元測試類別中加入以下這行:
1 | private val context = mock(Context::class.java) |
mock()
方法來自我們剛才在專案中實作的程式庫。模擬是單元測試的必要部分,但不在這個程式碼研究室的範圍內。我們會在另一個程式碼研究室中詳細說明模擬功能。在 Android 中,Context
是應用程式目前狀態的結構定義,但別忘了,單元測試是在 JVM 執行,而不是在實際裝置上執行,因此沒有 Context
。這個模擬方法能讓我們建立 Context
的「模擬」執行個體。這個執行個體沒有任何實際的功能,但可用來測試需要結構定義的方法。
- 建立名為
adapter_size()
的函式並加上註解作為測試。這項測試旨在確認 adapter 與傳遞至 adapter 的 list 兩者大小相同。執行方法是建立ItemAdapter
的執行個體,並傳入Datasource
類別中loadAffirmations()
方法所傳回的 list。您也可以建立新的 list 並進行測試。如果是單元測試,最佳做法是建立測試專屬的資料,以便我們為這項測試建立自訂名單。
1 | val data = listOf( |
- 立即建立
ItemAdapter
的執行個體,並傳入在上述步驟中建立的context
和data
變數。
1 | val adapter = ItemAdapter(context, data) |
Recycler view adapters 有一方法,會傳回名為 getItemCount()
的 adapter 大小。對於這個應用程式而言,方法如下:
1 | /** |
- 這是應該測試的方法。這個方法的傳回值必須與您在步驟 2 中建立的 list 大小相符。使用
assertEquals()
方法,並比較 list 大小和 adapter 大小的值。
1 | assertEquals("ItemAdapter is not the correct size", data.size, adapter.itemCount) |
您已經熟悉 assertEquals()
方法,但建議您仔細檢查這一行。第一個參數是測試失敗時,會在測試結果中顯示的字串。第二個參數是預期的值。第三個參數是實際值。您的測試類別應如下所示:
1 | package com.example.affirmations |
最後,執行測試應該可以看到 Tests passed。