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 寫測試,剛好之前做完 後想要補一下這裡的測試,就藉著這個機會研究一下 Compose UI Testing。
HushKeyboard
ricky9667 • Updated Sep 14, 2024
今天這篇文會提到:
- 撰寫 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:- Finder:讓你在 Compose 的元件樹裡面選擇你想要測試的元件,例如
onNode()
或onNodeWithText()
。
- Action:對你選擇的元件進行操作,例如
performClick()
或performGesture()
。
- 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
以下是一個 的 Compose UI Test 範例,更詳細的教學可以看 Android Docs 的 Testing your Compose Layout 或是 Testing Cheatsheet。
HushKeyboard
ricky9667 • Updated Sep 14, 2024
使用 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)
就可以了,其他的程式碼都可以不變。然而,這時候你可能會遇到這兩個問題:
- 為什麼在測試的檔案裡面找不到 RobolectricTestRunner?
- 為什麼我的測試成功跑完,但是我的模擬器還是打開了?
將 UI Test 移到 Local Test
以上兩個的問題的答案是一樣的:因為你的 UI Test 目前是放在 Instrumented Test。
大家如果在 Android Studio 左邊的 File Tree 選 Android 的顯示方式,資料夾應該會像以下這張圖:
詳細可以看一下 Android 官方文件的 Test types and locations,不過我們簡單區分一下:
- Local Test:測試放在
test
資料夾中,不會啟動模擬機,使用 dependency 需要使用testImplementation
。
- Instrumented Test:測試放在
test
資料夾中,不會啟動模擬機,使用 dependency 需要使用androidTestImplementation
。
androidTest
代表的是 Instrumented Test,執行時是會開啟模擬機的;反之 test
所代表的 Local Test 就不會啟動模擬機。了解這個問題後,我們再回答以上的問題:
- 為什麼在測試的檔案裡面找不到 RobolectricTestRunner?
- 因為 Compose UI Test 是寫在 Instrumented Test,但是 Robolectric 目前是在 Local Test 引入的。
- 為什麼我的測試成功跑完,但是我的模擬器還是打開了?
- 如果你把
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,可以像這樣檢測每一行程式碼的覆蓋程度。使用 Github Actions 自動產生報表
為了讓自己可以不用手動跑測試跟 Coverage 的指令,使用 Github Actions 自動跑測試以及其他想要執行的程序。
整合 Github Actions 也不麻煩,可以在專案最上層建立一個
.github
資料夾並新增一個 .yml
檔案,以下是我的 CI 的範例:上面
on:
的區塊代表 Github Actions 在 master
分支有新的 commit,或是發出新的 Pull Request 的時候,會以下 jobs:
的步驟。可以看到 steps:
裡面的 Run Tests 和 Generate Coverage Report 就是我們上面自己在本地電腦執行的兩個指令。上傳 Coverage Report 至 Codecov
剛好這段時間在研究這些工具的時候,有看到 Codecov 這個工具,可以把產生好的 Coverage Report 上傳到 Codecov 後。除了能用 Codecov 檢視程式碼的 Coverage,他還能幫你比較不同的 Commit 和 Pull Request 的 Coverage。
將 Codecov 連接到 Github 專案
這部分 Codecov 的文件寫得滿清楚的,可以參考這裡。
透過 Github Actions 上傳報表至 Codecov
在你的 CI 裡面也只需要改動兩個部分:
- Generate Coverage Report 要把
koverHtmlReportDebug
改成koverXmlReportDebug
- 產生 Report 後面再使用 上傳 kover 產生的報表codecov-actioncodecov • Updated Jul 7, 2024
以下是完整的 CI 檔案:
加上這一行之後,Codecov 會在你的 Pull Request 上面留言你的 Coverage 的變動,其他更詳細的內容可以去看 Codecov 的 Dashboard。
其實這樣整理下來,自己的 Side Project 雖然規模不答,但也可以撐得上五臟俱全了吧XD,其實還滿有成就感的。
另外也歡迎到 Medium 看這篇文章,感謝大家閱讀。