第3章 Compose UI测试基础:ComposeTestRule
你写过普通的Android UI测试吗?如果写过,大概会对 ActivityTestRule 或 ActivityScenario 不陌生。但在 Compose 的世界里,主角换成了 ComposeTestRule。说白了,它是 Compose 测试的“引擎”——帮你启动 Composable、查找节点、执行操作、验证状态。
我第一次接触 Compose 测试时,最困惑的是:明明写了一个 Button,怎么用常规的 onView(withId(...)) 就找不到了呢?后来才明白,Compose 的 UI 树和传统 View 系统完全不一样。嗯,这里就要靠 ComposeTestRule 来搭桥了。
3.1 什么是ComposeTestRule?
ComposeTestRule 是 Jetpack Compose 提供的测试规则。它让你的测试代码能像用户一样“看”到 Composable 界面,“点”按钮,“填”输入框,“读”文字。它背后做了很多事:自动帮你 Compose 组件,等待布局稳定,支持协程和动画。
我个人习惯把它理解成一个“微型宿主 Activity”。你在测试里调用 rule.setContent { YourComposable() },Compose 框架就会把它挂载到屏幕上(虽然是虚拟的)。然后你就可以用 onNodeWithText("提交") 这种玩法来定位元素了。
3.2 创建ComposeTestRule的两种方式
你可能会问:我该怎么拿到这个规则呢?两种方式。一张表说清楚:
| 创建方式 | 适用场景 | 示例 |
|---|---|---|
createComposeRule() |
只需要测试 Composable 本身,不关心 Activity | 测试自定义组件、弹窗、普通页面 |
createAndroidComposeRule<MainActivity>() |
需要启动真实 Activity,测试导航、系统交互等 | 测试 Fragment + Compose 混搭、深层链接 |
我在项目中遇到过一种情况:用 createAndroidComposeRule 测一个简单的 Composable,结果每次运行都要等 Activity 完全创建——慢!后来换成 createComposeRule,测试执行速度快了一倍。所以我的建议是:除非你非要 Activity 的生命周期不可,否则优先用轻量版。
3.3 核心API速览
记住这几个操作,就能搞定 80% 的回归测试:
- setContent —— 把要测的 Composable 摆上舞台。你可以传任意 Composable。
- onNodeWithText / onNodeWithTag —— 定位节点。文本定位最常用;Tag 需要你在 Composable 里用
Modifier.testTag()预先声明。 - assertExists() / assertIsDisplayed() —— 断言节点存在或可见。这是回归测试的“定海神针”。
- performClick() / performTextInput() —— 模拟用户点击、输入。注意:点击后可能需要等待异步完成。
- waitForIdle() —— 强制等待 Compose 闲下来。虽然不常用(规则会自带等待),但遇到动画或协程抖动时可以手动调用。
3.4 实战:第一个测试用例
光说不练假把式。我们直接写一个测试:假设你有一个登录按钮和一个文本。
@RunWith(AndroidJUnit4::class)
class LoginButtonTest {
@get:Rule
val rule = createComposeRule()
@Test
fun loginButton_displaysCorrectText() {
// 1. 设置界面
rule.setContent {
LoginScreen() // 你的 Composable
}
// 2. 找到按钮
val button = rule.onNodeWithText("登录")
// 3. 断言它存在
button.assertExists()
// 4. 点击
button.performClick()
// 5. 验证结果(比如登录后提示文字变了)
rule.onNodeWithText("欢迎回来").assertIsDisplayed()
}
}
你看,整个过程像在写“用户操作说明书”。这也是回归测试的美妙之处——你描述一遍流程,机器替你反复执行。
3.5 常见陷阱与最佳实践
这里有三个我踩过的坑,分享给你:
⚠️ 忘记 testTag 导致定位困难
我曾经在测试一个复杂的 LazyColumn 列表时,里边的条目没有加 Modifier.testTag(),结果用 onNodeWithText 找了半天,总是匹配到多个节点。后来给每个条目加上唯一的 testTag,一秒定位。
💡 异步操作要加 waitUntil
如果你的 Composable 里有协程或 Retrofit 请求,直接 assertExists 可能因为动画还没结束而失败。建议用 rule.waitUntil { condition } 或 waitForIdle()。我个人习惯在点击后立刻调一次 rule.waitForIdle(),保险。
⚠️ IDLE_TIMEOUT 不够长
默认超时是 1 秒。如果你的测试里包含 3D 动画或大数据加载,会因超时而报错。你可以通过 @get:Rule val rule = createComposeRule().apply { idleTimeout = 2000L } 调整。
最后,记得测试代码要 优雅且稳定。回归测试最怕“间歇性失败”——今天绿、明天红。我建议你多用 assertIsDisplayed 而非 assertExists,因为前者会检查节点是否在屏幕上可见(避免 off-screen 误判)。
本章核心回顾
- ComposeTestRule 是 Compose 测试的基石,帮你启动、查找、交互、断言。
- 轻量场景用
createComposeRule(),需要 Activity 时用createAndroidComposeRule。 - 核心操作:setContent → onNodeWithText/Tag → performClick → assertExists/IsDisplayed。
- 注意异步等待、testTag 命名规范、超时配置。