1. 事前準備
在先前的程式碼研究室中,您已學會如何使用 ViewModel
處理商業邏輯,以及使用 LiveData
處理反應式 UI。在本程式碼研究室中,您將學習如何編寫單元測試,以檢查 ViewModel
程式碼是否正常運作。
必要條件
- 您已在 Android Studio 中建立測試目錄。
- 您已在 Android Studio 中編寫了單元和檢測設備測試。
- 您已將 Gradle 依附元件新增至 Android 專案。
課程內容
- 如何編寫
ViewModel
和LiveData
單元測試。
軟硬體需求
- 已安裝 Android Studio 的電腦。
- Cupcake 應用程式的解決方案程式碼。
下載本程式碼研究室的範例程式碼
在本程式碼研究室中,您需要將檢測設備測試新增至先前解決方案程式碼的 Cupcake 應用程式。
如要取得這個程式碼研究室的程式碼,並在 Android Studio 中開啟,請按照下列步驟操作:
取得程式碼
- 按一下上面顯示的網址。系統會在瀏覽器中開啟專案的 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. 範例應用程式總覽
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
物件不得呼叫主執行緒。
- 如要指定
LiveData
物件不得呼叫主執行緒,我們需在每次測試LiveData
物件時提供專用測試規則。
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
- 現在可建立名為
quantity_twelve_cupcakes()
的函式。在方法中,建立OrderViewModel.
執行個體 - 在本測試中,您需確認
OrderViewModel
中的quantity
物件在setQuantity
呼叫時已更新。但在呼叫任何方法或處理OrderViewModel
中的任何資料前,請注意測試LiveData
物件的值時,必須先觀察物件,才能發出變更。方法很簡單,只要使用observeForever
方法即可。呼叫quantity
物件的observeForever
方法。這個方法需要 lambda 運算式,但可以留空。 - 然後呼叫
setQuantity()
方法,將12
傳入做為參數。
val viewModel = OrderViewModel()
viewModel.quantity.observeForever {}
viewModel.setQuantity(12)
- 我們可以放心推論
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
中的其他方法可以進行呼叫。這些是公開方法,因此我們會呼叫此類方法觸發價格計算作業,以便確認價格值是否符合預期。
最佳做法
選取杯子蛋糕數量及日期時,價格將會隨之更新。儘管兩者都應進行測試,但我們通常建議針對單一功能進行測試。因此,我們會針對各測試建立不同的方法:一種在數量更新時用於測試價格的函式,另一種在更新日期時用於測試價格的不同函式。我們不希望測試結果因為另一項測試失敗而失敗。
- 建立名為
price_twelve_cupcakes()
的方法,並將其做為測試加上註解。 - 在方法中,建立
OrderViewModel
執行個體,並呼叫setQuantity()
方法,將 12 傳入做為參數。
val viewModel = OrderViewModel()
viewModel.setQuantity(12)
- 查看
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)
現在請執行測試。
測試應會失敗!
測試結果顯示,實際值是 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
物件時,除非有必要進行呼叫,否則系統不會呼叫程式碼,而會將資源儲存到行動裝置。只有在觀察到物件變更時,系統才會呼叫程式碼。當然,此操作是在應用程式中執行,但我們也需要為測試進行相同操作。
- 在測試方法中,請先新增下列程式碼,再設定數量:
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
。