編寫單元測試

1. 事前準備

在先前的程式碼實驗室中,我們說明了如何使用 Android Studio 建立專案、修改 XML 來製作應用程式的自訂使用者介面,以及修改商業邏輯來新增功能。本程式碼研究室著重於測試的重要性,並展開單元測試。您有機會瞭解什麼是單元測試及其編寫方式。

必要條件

  • 您已在 Android Studio 中建立專案。
  • 您具備一些在 Android Studio 中編寫程式碼的經驗。

課程內容

  • 測試的重要性。
  • 什麼是單元測試?
  • 如何編寫及執行單元測試。

軟硬體需求

2. 簡介

您已經編寫了一些 Android 程式碼,現在正好可以用一些測試程式碼來追蹤後續狀況。我們首先會看看一些測試的理念,然後深入研究 Android 專案中自動產生的測試,最後再為 Dice Roller 應用程式編寫您自己的測試!本課涵蓋大量教材,不過也別嚇到!測試時間較長,而且需要大量的練習才能學會,所以您可以慢慢熟讀這些教材。就算您無法立即掌握,也別氣餒。

測試的重要性

您一開始也許覺得似乎不需要在應用程式中進行測試。如果應用程式較小而且功能有限時,您可以輕鬆手動測試,並確認一切功能是否正常運作。然而,當應用程式逐漸成長,進行手動測試會比編寫自動測試花費更多精力。此外,在您開始建構專業級的應用程式時,如果使用者數量龐大,測試也會變得十分重要。您必須考到許多不同類型的裝置,而這些裝置又會執行不同的 Android 版本。最終,您還是會選擇自動化測試,因為這樣可以比手動測試更快地測試大部分的使用情境。如果您在發布新的程式碼之前執行測試,就可以修改現有的程式碼,以免發布的應用程式出現非預期的行為。請注意,自動化測試是透過軟體執行的測試,而人工測試則是由會直接與裝置互動的人員執行。就確保使用者獲得愉快的產品體驗來說,兩者都有舉足輕重的作用。不過,自動化測試的準確度較高,也可以提升團隊的工作效率,因為員工不必手動執行測試,因此自動化測試的執行速度會比手動測試還要快。

深入探索單元測試

在本程式碼研究室中,您只需要專注於單元測試上。我們稍後會說明檢測設備測試。首先,請查看您透過 Android Studio 建立 Android 應用程式時所產生的測試。此外,您還會獲得實際測試的經驗,並會熟悉如何編寫測試程式碼。

在先前的課程中,您已瞭解如何尋找用於測試的來源檔案。單元測試一律位於 test 目錄中:

f02b380da4e8f661.png

  1. 開啟 app/build.gradle 檔案,然後查看依附元件。有部分依附元件會標記為 testImplementationandroidTestImplementation,分別是指單元測試和檢測設備測試。值得一提的是:

app/build.gradle

testImplementation 'junit:junit:4.12'

用來驅動單元測試的 JUnit 程式庫,可讓您將程式碼標示為測試,以便採用可以測試應用程式碼的方式編譯和執行應用程式。

  1. test 目錄中,開啟 ExampleUnitTest.kt 檔案。

以下是單元測試的範例:

ExampleUnitTest.kt

class ExampleUnitTest {
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
   }
}

雖然你已在 Dice Roller 應用程式中新增了一些程式碼,但可能並未編寫任何測試。因此,這裡只有一些由 Android Studio 自動建立的一般程式碼。這個任意測試可做為預留位置,以便開發人員用來編寫更多相關的測試。這個程式碼區塊現在只會測試 2 + 2 = 4。答案當然是正確的。讓我們進一步瞭解實際情況:

  • 您必須先從 org.junit.test 程式庫匯入 @ Test 註解,然後為測試函式加上該註解。您可以將註解視為一段程式碼的中繼資料標記,可改變程式碼的編譯方式。在這個範例中,@Test 註解可讓編譯器知道以下方法為測試,從而讓該方法據此執行。

註解之後是函式宣告,在這個範例中為 addition_isCorrect() 函式。在函式中,assertEquals() 函式會宣告預期值應等於透過商業邏輯取得的實際值。宣告方法是單元測試的最終目標。最後,您要宣告從程式碼取得的結果處於特定狀態。如果結果狀態與預期狀態相符,即表示測試通過;如果結果狀態與預期狀態不符,即表示測試失敗。在這個範例中,程式碼會比較兩個值,因此 assertEquals() 方法會使用兩個參數,分別是預期值和實際值。顧名思義,預期值是您預期出現的特定結果,在本例中是 4。實際值代表實際程式碼片段的結果。一般而言,這樣會測試應用程式本身的程式碼片段。這個範例只有任意的程式碼片段,例如 2 + 2。請先執行這個測試,看看會發生什麼事。

您可透過多種方法在 Android Studio 中執行測試,我們稍後會深入探討。不過,現在先以簡單明瞭為主。

  1. 按一下 addition_isCorrect 方法宣告旁的箭頭,然後選取「Run ‘ExampleUnitTest.addition_isCorrect'」

78c943e851a33644.png

這就是所謂的正向測試,也就是說,斷言獲得確認。2 + 2 等於 4。或者,我們可以編寫負向測試,使斷言結果為負。例如:2 + 2 不等於 5。

「Run」窗格中的內容應如以下螢幕截圖所示。

190df0c8ff787233.png

許多指標都能表示測試成功,包括綠色勾號,以及所通過測試的數量。

aa7d361d8e4826ef.png

  1. 修改測試來看看測試失敗的情形。請將 2 + 2 變更為 2 + 3,然後再次執行測試。請注意,您只能以產生的程式碼進行實驗,進一步瞭解測試的運作方式。這些變更與 Dice Roller 功能並無任何關聯性。

ExampleUnitTest.kt

class ExampleUnitTest {
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 3)
   }
}

完成其餘操作後,螢幕截圖會如下所示:

751ac8089cf4c47c.png

紅色文字代表測試失敗。在測試結果選單中,按一下項目即會提供錯誤訊息,指出測試失敗的原因。

163708373e651ecc.png

在這個例子中,錯誤訊息表示斷言失敗,因為預期結果為 4,但實際值是 5。由於您將實際值變更為 2 + 3,但預期值還是 4,所以這是合理的狀況。您也會看到測試失敗的行。在這個例子中,第 15 行測試失敗,註明為 ExampleUnitTest.kt:15

  1. 為求測試徹底,請將預期值從 4 變更為 5,然後再次執行測試。在出現問題的程式碼中,預期值與實際值相符,所以現在測試應已通過。

3. 編寫第一個單元測試

現在您已慢慢熟悉單元測試,您就可以自行編寫與 Dice Roller 應用程式更相關的單元測試。

您應該發現到,Dice Roller 應用程式的主要功能是以隨機號碼產生器為基礎。不過,人所共知,隨機號碼產生器難於測試,因為您無法確定隨機產生號碼的結果。這項測試旨在確保你擲出骰子,或是呼叫 dice 類別上的 roll 方法時,能夠取得正確的數字。您編寫的測試只會測試隨機號碼產生器的結果不會超出您為產生器指定的數字範圍。

  1. ExampleUnitTest.kt 檔案中,刪除產生的測試方法並匯入陳述式。您的檔案應如下所示:

c06e8b402f293b5e.png

  1. 建立 generates_number() 函式:

ExampleUnitTest.kt

fun generates_number() {
}
  1. 使用 @Test 註解為 generates_number() 方法加上註解。請注意,嘗試呼叫 @Test 時,文字會顯示為紅色。這是因為找不到這個註解的斷言,所以您需要匯入斷言。按下 Control+Enter (在 Mac 上則為 Options+Return) 即可自動執行這項操作。

如果按一下這行程式碼,畫面上應會顯示下列匯入提示:

bbe5791b9565588c.png

或者,您也可以將 import org.junit.Test 檔案複製並貼到套件名稱之後,但在類別宣告之前。程式碼現在應如下所示:

9a94c2bdf84adb61.png

  1. 建立 Dice 物件的執行個體。

ExampleUnitTest.kt

@Test
fun generates_number() {
   val dice = Dice(6)
}
  1. 接下來,對這個執行個體呼叫 roll() 方法,並儲存傳回的值。

ExampleUnitTest.kt

@Test
fun generates_number() {
   val dice = Dice(6)
   val rollResult = dice.roll()
}
  1. 最後提出實際斷言即可。換句話說,您需要聲明該方法傳回的值在您傳遞的側側邊數目以內。因此在這個例子中,該值必須大於 0,且小於 7。為符合此要求,請使用 assertTrue() 方法。請注意,嘗試呼叫 assertTrue() 方法時,文字會先顯示為紅色。這是因為找不到這個方法的宣告,所以您必須匯入宣告,做法與處理註解時類似。

10eea07fc21bf998.png

您可以自動匯入先前討論過的內容。但請注意,這次您有多種選擇。在這個案例中,應選擇來自 org.junit.Assert 套件的選項:

5dbfba2ba0e37ac9.png

或者,您可以將此程式碼貼到測試註解的匯入陳述式之後:

ExampleUnitTest.kt

import org.junit.Assert.assertTrue

程式碼會如下所示:

347f792f455ae6b5.png

如果將游標放在兩個括號之間並按下 Control+P (在 Mac 上則為 Command+P),系統會顯示工具提示,當中會顯示方法使用的參數:

865cf0ac47738e08.png

assertTrue() 方法有兩個參數:StringBoolean。如果斷言失敗,字串即是控制台中顯示的訊息。布林值是條件陳述式。請將訊息設為:

ExampleUnitTest.kt

"The value of rollResult was not between 1 and 6"

如前文所述,測試隨機號碼是一大挑戰,因為是隨機產生的號碼,所以無法預測號碼的值。所有唯一能做的,就是確保值不會超出特定範圍。請將條件參數設為:

ExampleUnitTest.kt

rollResult in 1..6

程式碼應該如下所示:

ExampleUnitTest.kt

@Test
fun generates_number() {
   val dice = Dice(6)
   val rollResult = dice.roll()
   assertTrue("The value of rollResult was not between 1 and 6", rollResult in 1..6)
}
  1. 按一下函式旁邊的箭頭,然後選取「Run 'ExampleUnitTest.generates_number()'」

如果您的程式碼看起來像先前的程式碼片段,則表示測試通過!

  1. 選用:如要進一步練習,請在不變更斷言的情況下將骰子修改為 4 面或 5 面,查看測試失敗的狀況。

4. 恭喜

你學到:

  • 測試的重要性。
  • 什麼是單元測試。
  • 如何執行單元測試。
  • 一些常見的測試語法。
  • 如何編寫單元測試。

瞭解詳情