Compose UI Testing with Coverage Analysis

date
Jun 17, 2024
slug
compose-ui-testing-with-coverage-analysis
status
Published
tags
Programming
summary
如何幫 Jetpack Compose 寫 UI 測試,並且使用 kotlinx-kover 統計 Code Coverage 並且上傳到 Codecov。
type
Post

Introduction

這個學習修了學校的軟體測試,剛好期末專案需要轉一個 Android Project 寫測試,剛好之前做完
HushKeyboard
ricky9667Updated Sep 14, 2024
後想要補一下這裡的測試,就藉著這個機會研究一下 Compose UI Testing。
今天這篇文會提到:
  • 撰寫 Compose UI Tests
  • 使用 Robolectric 提升 UI Test 效率
  • 檢測測試的 Code Coverage

撰寫 Compose UI Tests

其實官方文件寫的滿詳細的,而且 Compose UI Testing 真的滿好寫的,用過之後完全不想碰 Espresso(x

前置設定 Setup

導入套件

首先要先在 build.gradle.kts 加上這兩個 implementation:

建立 Compose Rule

接下來我們可以透過 createComposeRule() 裡面建立一個 ComposeTestRule 的 instance,到時候我們會用這個 Test Rule 來測試 Compose 的 UI:
如果你需要 Activity 相關的資訊,可以改成用 createActivityComposeRule()

設定測試 UI

最後你可以透過 setContent 設定你想要測試的 UI:

撰寫 UI 測試

ComposeTestRule 有三種類型的 Method:
  1. Finder:讓你在 Compose 的元件樹裡面選擇你想要測試的元件,例如 onNode()onNodeWithText()
  1. Action:對你選擇的元件進行操作,例如 performClick()performGesture()
  1. Assertion:檢測你選擇的元件狀態是不是你預期的,例如 assertIsEnabled()assertHasClickAction()

1. Finder

我們測試 UI 的第一步需要選擇我們要測的某個元件或區塊,我們會在 onNode() 裡面提供你元件的篩選條件,這個篩選條件稱為一個 Matcher。
如果你需要一次選擇多個元件,可以改成用 onAllNodes()
另外,有一些常用的 Finder 會直接跟 Matcher 合併,例如:
  • onNodeWithText("Button")onNode(hasText(”Button”))
  • onNodeWithContentDescription("Button")onNode(hasContentDescription(”Button”))

2. Assertion

選擇 UI 之後,我們可以針對 Finder 選取到的 UI,檢測他是否符合我們的預期。這時候用的會是 assert(),並在裡面提供一個 Matcher。另外,Assertion 同樣也會有跟 Matcher 合併的方法,像是 assertHasClickAction()assertHasContentDescription()

3. Action

除此之外,我們可以針對自己用 Finder 選取的 UI 做一些行為,通常都是 perform…() 類型的方法。

Example

以下是一個
HushKeyboard
ricky9667Updated Sep 14, 2024
的 Compose UI Test 範例,更詳細的教學可以看 Android Docs 的 Testing your Compose Layout 或是 Testing Cheatsheet
 

使用 Robolectric 提升測試速度

雖然是寫好 UI Test 了,但是想到每次跑 Android 的 Instrumented Tests,Android Studio 還要把模擬器開起來跑,實在是不太舒服(x
很幸運的,之前實習的時候有聽過 Robolectric 這個 Android 測試框架,他可以讓你執行測試時用 JVM 執行一個模擬的 Android 環境,這樣就不用透過開啟模擬器消耗這麼多資源測試 Android 的 Component。

導入套件

首先需要加入 JUnit 和 Robolectric 這兩個 dependency,不過通常建立 Android 專案的時候就已經有 JUnit 了,這個要注意一下。
還有很重要的一點,在 build.gradle.kts 裡面需要將 isIncludeAndroidResource 設定為 true

在測試中使用 Robolectric

引用的方式很簡單,在你的 UI Test 的 Class 上面加一個 @RunWith(RobolectricTestRunner::class) 就可以了,其他的程式碼都可以不變。
然而,這時候你可能會遇到這兩個問題:
  1. 為什麼在測試的檔案裡面找不到 RobolectricTestRunner?
  1. 為什麼我的測試成功跑完,但是我的模擬器還是打開了?

將 UI Test 移到 Local Test

以上兩個的問題的答案是一樣的:因為你的 UI Test 目前是放在 Instrumented Test。
大家如果在 Android Studio 左邊的 File Tree 選 Android 的顯示方式,資料夾應該會像以下這張圖:
notion image
詳細可以看一下 Android 官方文件的 Test types and locations,不過我們簡單區分一下:
  • Local Test:測試放在 test 資料夾中,不會啟動模擬機,使用 dependency 需要使用 testImplementation
  • Instrumented Test:測試放在 test 資料夾中,不會啟動模擬機,使用 dependency 需要使用 androidTestImplementation
androidTest 代表的是 Instrumented Test,執行時是會開啟模擬機的;反之 test 所代表的 Local Test 就不會啟動模擬機。
了解這個問題後,我們再回答以上的問題:
  1. 為什麼在測試的檔案裡面找不到 RobolectricTestRunner?
      • 因為 Compose UI Test 是寫在 Instrumented Test,但是 Robolectric 目前是在 Local Test 引入的。
  1. 為什麼我的測試成功跑完,但是我的模擬器還是打開了?
      • 如果你把 testImplementation 都換成 androidTestImplementation 就會遇到這個情況。因為你的測試放在 Instrumented Test,就算使用了 Robolectric,Instrumented Test 還是會把你的手機打開。
搞清楚之後問題就很簡單了,我們只要把測試的 dependency 都換成 testImplementation ,並且把測試都移動到 test 資料夾,就可以執行了。
更改後的檔案如下:
 

檢測測試的 Code Coverage

既然都寫測試了,那也順便來測試一下 Coverage 好了!幸運的在調查 Coverage 工具的時候找到 ,趁這個機會順便玩了一下。

使用 kotlinx-kover 產生 Coverage Report

首先引入 kotlinx-kover 的 Plugin:
在產生 Coverage Report 之前,需要先執行 Local Test,我們撰寫的 UI Test 也包含在這裡。
跑完之後,我們就可以產生我們的 Coverage Report:
執行完之後,kover 會在這個路徑 app/build/reports/kover/htmlDebug/index.html 產生一個 Coverage Report,可以像這樣檢測每一行程式碼的覆蓋程度。
notion image

使用 Github Actions 自動產生報表

為了讓自己可以不用手動跑測試跟 Coverage 的指令,使用 Github Actions 自動跑測試以及其他想要執行的程序。
整合 Github Actions 也不麻煩,可以在專案最上層建立一個 .github 資料夾並新增一個 .yml 檔案,以下是我的 CI 的範例:
上面 on: 的區塊代表 Github Actions 在 master 分支有新的 commit,或是發出新的 Pull Request 的時候,會以下 jobs: 的步驟。可以看到 steps: 裡面的 Run TestsGenerate Coverage Report 就是我們上面自己在本地電腦執行的兩個指令。

上傳 Coverage Report 至 Codecov

剛好這段時間在研究這些工具的時候,有看到 Codecov 這個工具,可以把產生好的 Coverage Report 上傳到 Codecov 後。除了能用 Codecov 檢視程式碼的 Coverage,他還能幫你比較不同的 Commit 和 Pull Request 的 Coverage。
notion image
將 Codecov 連接到 Github 專案
這部分 Codecov 的文件寫得滿清楚的,可以參考這裡
透過 Github Actions 上傳報表至 Codecov
在你的 CI 裡面也只需要改動兩個部分:
  • Generate Coverage Report 要把 koverHtmlReportDebug 改成 koverXmlReportDebug
以下是完整的 CI 檔案:
加上這一行之後,Codecov 會在你的 Pull Request 上面留言你的 Coverage 的變動,其他更詳細的內容可以去看 Codecov 的 Dashboard。
notion image
其實這樣整理下來,自己的 Side Project 雖然規模不答,但也可以撐得上五臟俱全了吧XD,其實還滿有成就感的。
另外也歡迎到 Medium 看這篇文章,感謝大家閱讀。

Reference


© Ricky 2023 - 2024