测试分层: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 的设计核心》

相关推荐
wuminyu5 小时前
Kafka中sendfile与mmap实现机制解析
java·linux·c语言·jvm·c++
天空之城--9 小时前
Android逆向安全合规接单指南
android·安全
万年咸鱼11 小时前
JVM 内存区域详解
jvm
消失的旧时光-194311 小时前
Android 系统层扫盲 09:AMS、ATMS、WMS、PMS 到底分别管什么?
android·wms·pms·ams·aosp·atms
Godikov11 小时前
Android 系统级设备应用踩坑实录:sharedUserId 签名、SDK 授权失败 -4、开机自启与 U 盘 OTA 升级
android
Escalating_xu13 小时前
【System V 信号量】从 P/V 原语到 Builder 封装:写出可控、可清理的进程互斥组件
java·linux·开发语言·jvm
IT毕设实战小研15 小时前
基于大数据的国内主要农作物产量趋势分析与可视化
android·java·大数据·django·课程设计
HEJOO916 小时前
JVM 参数调优实战指南
jvm
企业数字化笔记16 小时前
固定资产历史数据怎么导入系统?Excel模板、字段映射和数据校验
android·java·数据库·后端
龙之叶16 小时前
Android出海系列-GMS认证介绍
android