測試 ViewModel 和 LiveData

1. 事前準備

在先前的程式碼研究室中,您已學會如何使用 ViewModel 處理商業邏輯,以及使用 LiveData 處理反應式 UI。在本程式碼研究室中,您將學習如何編寫單元測試,以檢查 ViewModel 程式碼是否正常運作。

必要條件

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

課程內容

  • 如何編寫 ViewModelLiveData 單元測試。

軟硬體需求

  • 已安裝 Android Studio 的電腦。
  • Cupcake 應用程式的解決方案程式碼。

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

在本程式碼研究室中,您需要將檢測設備測試新增至先前解決方案程式碼的 Cupcake 應用程式。

如要取得這個程式碼研究室的程式碼,並在 Android Studio 中開啟,請按照下列步驟操作:

取得程式碼

  1. 按一下上面顯示的網址。系統會在瀏覽器中開啟專案的 GitHub 頁面。
  2. 檢查並確認分支版本名稱與程式碼研究室中指定的版本相符。例如,在下列螢幕截圖中,分支版本名稱為「main」

fe29aa9112862a93.png

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

5b0a76c50478a73f.png

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

在 Android Studio 中開啟專案

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

a065e3d575fe607b.png

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

4f3b1e628c7695f1.png

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

2. 範例應用程式總覽

Cupcake 應用程式中的主畫面可顯示訂單畫面,此畫面提供三個杯子蛋糕數量選項。點選選項後,您將會前往選取口味的畫面,然後再前往選取訂單取貨日期的畫面。隨後,即可將訂單傳送至其他應用程式。您可於任一階段取消您的訂單。

3. 建立單元測試目錄

以您在先前程式碼研究室中相同的方式,為 Cupcake 應用程式建立單元測試目錄。

4. 建立單元測試類別

建立名為 ViewModelTests.kt 的新類別。

5. 新增必要的依附元件

將下列依附元件新增至專案:

testImplementation 'junit:junit:4.+'
testImplementation 'androidx.arch.core:core-testing:2.1.0'

接著同步專案。

6. 編寫 ViewModel 測試

讓我們先從簡單的測試開始。當我們在裝置或模擬器上與應用程式互動時,首先會選取杯子蛋糕的數量。因此,我們會在 OrderViewModel 中測試 setQuantity() 方法,並檢查 quantity LiveData 物件的值。

我們要測試的 quantity 變數是 LiveData 執行個體。測試 LiveData 物件需要執行額外步驟,因此,我們所新增的依附元件就能派上用場。只要值有變更,我們就會使用 LiveData 更新 UI。UI 於「主執行緒」上運作。如果您不熟悉執行緒和並行的概念,別擔心,我們會在其他程式碼研究室中深入介紹。就 Android 應用程式而言,請暫時將主執行緒視為 UI 執行緒。向使用者顯示 UI 的程式碼會在此執行緒上運作。除非另有指定,否則單元測試會假設所有項目皆在主執行緒上運作。不過,由於 LiveData 物件無法存取主執行緒,因此必須明確指出 LiveData 物件不得呼叫主執行緒。

  1. 如要指定 LiveData 物件不得呼叫主執行緒,我們需在每次測試 LiveData 物件時提供專用測試規則。
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
  1. 現在可建立名為 quantity_twelve_cupcakes() 的函式。在方法中,建立 OrderViewModel. 執行個體
  2. 在本測試中,您需確認 OrderViewModel 中的 quantity 物件在 setQuantity 呼叫時已更新。但在呼叫任何方法或處理 OrderViewModel 中的任何資料前,請注意測試 LiveData 物件的值時,必須先觀察物件,才能發出變更。方法很簡單,只要使用 observeForever 方法即可。呼叫 quantity 物件的 observeForever 方法。這個方法需要 lambda 運算式,但可以留空。
  3. 然後呼叫 setQuantity() 方法,將 12 傳入做為參數。
val viewModel = OrderViewModel()
viewModel.quantity.observeForever {}
viewModel.setQuantity(12)
  1. 我們可以放心推論 quantity 物件的值為 12。請注意,LiveData 物件本身並不是值。值包含在名為 value 的屬性中。指定以下斷言:
assertEquals(12, viewModel.quantity.value)

測試程式碼的編寫方式如下:

@Test
fun quantity_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.quantity.observeForever {}
   viewModel.setQuantity(12)
   assertEquals(12, viewModel.quantity.value)
}

執行測試!恭喜!您剛剛已編寫第一個 LiveData 單元測試,這是 Modern Android Development 的重要技能。這種方式並未測試到大部分的商業邏輯,因此我們要設計較為深入的測試。

計算訂單價格是 OrderViewModel 的主要功能之一。當我們選取杯子蛋糕數量,並選取取貨日期時,就會執行此功能。價格計算是以私人方法進行,因此我們的測試無法直接呼叫此方法。只有 OrderViewModel 中的其他方法可以進行呼叫。這些是公開方法,因此我們會呼叫此類方法觸發價格計算作業,以便確認價格值是否符合預期。

最佳做法

選取杯子蛋糕數量及日期時,價格將會隨之更新。儘管兩者都應進行測試,但我們通常建議針對單一功能進行測試。因此,我們會針對各測試建立不同的方法:一種在數量更新時用於測試價格的函式,另一種在更新日期時用於測試價格的不同函式。我們不希望測試結果因為另一項測試失敗而失敗。

  1. 建立名為 price_twelve_cupcakes() 的方法,並將其做為測試加上註解。
  2. 在方法中,建立 OrderViewModel 執行個體,並呼叫 setQuantity() 方法,將 12 傳入做為參數。
val viewModel = OrderViewModel()
viewModel.setQuantity(12)
  1. 查看 OrderViewModel 中的 PRICE_PER_CUPCAKE 時,可看見每個杯子蛋糕的售價為 $2.00 美元。還可以看到每次 ViewModel 初始化時都會呼叫 resetOrder(),在此方法中,預設日期為今天的日期,PRICE_FOR_SAME_DAY_PICKUP 為 $3.00 美元。因此,12 * 2 + 3 = 27。選擇 12 個杯子蛋糕後,我們預期 price 變數的值應為 $27.00 美元。接著做出斷言,假設 $27.00 美元的預期值等於 price LiveData 物件的值,
assertEquals("$27.00", viewModel.price.value)

現在請執行測試。

測試應會失敗!

17c8a24e4d7d635d.png

測試結果顯示,實際值是 null。以下為相關說明。如果您查看 OrderViewModel 中的 price 變數,可看見:

val price: LiveData<String> = Transformations.map(_price) {
   // Format the price into the local currency and return this as LiveData<String>
   NumberFormat.getCurrencyInstance().format(it)
}

此範例是應在測試中觀察到 LiveData 的原因。系統會使用 Transformation 設定 price 的值。基本上,此程式碼會使用我們指派給 price 的值,並將其轉換成貨幣格式,使我們不必手動執行。但這個程式碼還有其他含意。轉換 LiveData 物件時,除非有必要進行呼叫,否則系統不會呼叫程式碼,而會將資源儲存到行動裝置。只有在觀察到物件變更時,系統才會呼叫程式碼。當然,此操作是在應用程式中執行,但我們也需要為測試進行相同操作。

  1. 在測試方法中,請先新增下列程式碼,再設定數量:
viewModel.price.observeForever {}

測試程式碼的編寫方式如下:

@Test
fun price_twelve_cupcakes() {
   val viewModel = OrderViewModel()
   viewModel.price.observeForever {}
   viewModel.setQuantity(12)
   assertEquals("$27.00", viewModel.price.value)
}

如果現在進行測試,應可通過測試。

7. 解決方案程式碼

8. 恭喜

在本程式碼研究室中,我們:

  • 學會如何設定 LiveData 測試。
  • 學會如何測試 LiveData 本身。
  • 學會如何測試已完成轉換的 LiveData
  • 學會如何在單元測試中觀察 LiveData