测试分层:JVM 单测、ViewModel 测试与 Compose UI Test

技术目标

  1. 会写 无 Android 框架依赖 的纯 Kotlin 单测(本仓库 reduceStateSample)。
  2. 知道 Compose UI Test 需要额外依赖与 device/emulator 环境,入口 API 是 createComposeRule()setContentonNodeWithText 等。
  3. 能区分 PR 上跑什么、本地/nightly 跑什么,避免把 flaky instrumented 全堆在 merge 门禁上。

1. 测试分层(心智图)

慢 / device
Compose UI Test
端到端 / 少量冒烟
中速
ViewModel 逻辑 + TestScope
快 / JVM
Reducer 纯函数
UseCase / Parser

  • 纯函数与 reducer :最快、最稳定,应覆盖 状态迁移 + 分支 + Effect 是否出现
  • ViewModel :可测 viewModelScopeStateFlow 的 wiring(需 MainDispatcherRule 等);本仓库当前以 reducer 为主力。
  • Compose UI Test / 真机 :验证 交互、语义、导航 ;注意 动画、时间、Idle 导致的 flaky。

2. JVM 单测:StateSampleReducerTest

断言要点(与测试代码一致):

用例 断言
Increment count 自增; Effect
Save lastSavedSummary 含当前 count;EffectShowSavedSnackbar 且 message 与 summary 一致

运行(模块级):

bash 复制代码
./gradlew :app:testDebugUnitTest --tests "com.kuen.composedemo.samples.state.StateSampleReducerTest"

运行 app 模块全部 JVM 单测:

bash 复制代码
./gradlew :app:testDebugUnitTest

局限 :不覆盖 StateSampleViewModelviewModelScope.launch { _effects.send(...) } 的并发与取消;关键路径可补 轻量 ViewModel 测试少量 instrumented 冒烟


3. Instrumented 现状(本仓库)

ExampleInstrumentedTest.kt 为模板级 useAppContext 断言包名;可作为 device 流水线是否接通 的探针。真正的 Compose UI Test 需在此基础上增加 createComposeRule()setContent { ... }onNodeWith... 等(见官方 Compose 测试)。

运行 instrumented 测试(需要已连接设备或 emulator):

bash 复制代码
./gradlew :app:connectedDebugAndroidTest

4. Compose UI Test(概念与最小模板)

当前仓库还没有真正接入 Compose UI Test 依赖;app/build.gradle.kts 里已有 Compose BOM、AndroidX JUnit、Espresso,但还需补:

kotlin 复制代码
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.test.manifest)

对应 version catalog 可加入:

toml 复制代码
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }

最小测试类骨架:

kotlin 复制代码
import androidx.compose.material3.Text
import androidx.compose.ui.test.assertExists
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import org.junit.Rule
import org.junit.Test

class GreetingComposeTest {
    @get:Rule
    val rule = createComposeRule()

    @Test
    fun showTitle() {
        rule.setContent { Text("Hi") }
        rule.onNodeWithText("Hi").assertExists()
    }
}

如果测试真实主题或组件,再在 setContent 中包项目自己的 Theme:

kotlin 复制代码
@get:Rule
val rule = createComposeRule()

@Test
fun showTitle() {
    rule.setContent { AppTheme { Text("Hi") } }
    rule.onNodeWithText("Hi").assertExists()
}

注意

  • 异步列表:waitUntil { ... }、或注册 IdlingResource
  • 导航 :常用 TestNavHostController + 单独 NavHost 测试模块;参见 Navigation 官方测试文档。
  • 动画:测试可关闭动画或等待 idle,减少 flaky。

5. 风险清单

  • Robolectric 替代 JVM 全家桶 :维护与行为差异成本高,适合 补充 而非替代 reducer 级单测。
  • FlakyDispatchers.Main 未替换、全局 timer、随机数据 → CI 随机红。
  • 只追覆盖率:无有意义断言的分支 = 白测。
  • 在 Android Test 里跑真实网络/真实后端:应 mock 或 fake;否则慢且不稳定。

6. 本篇在系列中的位置

前 07 篇主要讲 Compose 写法与运行机制;从本文开始,把重点转向 如何验证这些写法是否可靠。建议把 reducer / use case 等纯逻辑优先沉到 JVM 单测,把少量真正依赖设备语义、导航或交互的场景放到 Compose UI Test / instrumented 测试。


7. 工程化(收束)

团队规模化时,在 API 之上补:lint 规则、模块化边界、Review 清单,才能把「会写测试」变成「CI 可信」。


8. 自检清单

  1. 新业务状态迁移是否 落在可 JVM 测的纯函数上?
  2. PR 门禁是否以 JVM 单测 为主、instrumented 为辅?
  3. UI Test 是否避免 依赖真实时间、真实网络、未关闭动画
  4. 失败用例是否有 稳定复现步骤断言信息(而非仅截图)?

参考答案(复习用)

  1. 建议先 。如 reduceStateSample:纯函数 + StateSampleReducerTest,分支与 Effect 一眼断言;ViewModel 只做薄胶水时再补 VM 测。
  2. 建议如此 。merge 前 ./gradlew :app:testDebugUnitTest 快且稳;connectedCheck 慢、易 flaky,适合 nightly 或关键路径少量条数。
  3. 应避免 。用 TestDispatcher、Fake 仓库、waitUntil;动画可用测试规则关闭或等待 idle,减少随机红。
  4. 应有 。CI 日志里能看到 assertEquals 期望值与实际值;本地复现命令写进 PR 描述,避免「只红截图无法修」。

源码仓库ComposeDemo(分支 main

系列推荐

《Navigation Compose:NavHost、NavController 与参数》

《深入 MaterialTheme:掌握 ColorScheme 与 Typography 的设计核心》

相关推荐
黄林晴7 小时前
Google Play 全面进化:AI 驱动增长,从上架到收入全链路重构
android·google
Access开发易登软件7 小时前
Access 用 VBA 操作 SQLite,不用装任何驱动
jvm·数据库·sqlite·vba·access·access开发
Niyy_7 小时前
WASM 的使用笔记
jvm·笔记·wasm
Refrain_zc7 小时前
Android 音视频通话核心二 —— 音频编码详解记录
kotlin
小L写Java7 小时前
第六章:JVM 调优实战 —— GC日志分析、内存溢出排查与线上问题定位
java·jvm
qq3621967057 小时前
Android 12/13/14/15 Google Play 兼容性检查指南:设备不兼容怎么办?2026最新解决方案
android·gitee
韩曙亮8 小时前
【错误记录】flutter attach 附加设备 执行报错 ( 附加设备注意事项 )
android·javascript·flutter·flutter attach
ZC跨境爬虫9 小时前
跟着 MDN 学CSS day_44:响应式设计——让网页适配所有屏幕的完整指南
前端·css·ui·html·tensorflow
le1616169 小时前
Android Compose基础布局——从传统XML的视角切入了解
xml·compose