SuperAPI AI 工具集

setting-up-host-vs-device-tests

测试与质量

查看项目

更新于 10-16 03:00

技能介绍

Setting Up Host vs Device Tests — 选择正确的源码集

Compose 测试在 Robolectric(JVM,快速,无需模拟器)或真实/虚拟设备(完整 Android 栈、RenderThread、无障碍)上均可原样运行。同一段 runComposeUiTest { setContent { … } } 代码在两者中都能编译,差异仅在于底层的 Looper 与 Choreographer。

何时使用本技能

  • 用户开始写 Compose UI 测试并询问「test/ 还是 androidTest/?」。
  • 测试在本地模拟器通过但在 CI 的 Robolectric runner 上失败(或反之)。
  • 用户报告 enableAccessibilityChecks(...) 产生 Build.FINGERPRINT == "robolectric" 警告。
  • 截图测试在宿主 runner 上产出黑色/空白 PNG。
  • ripple/pressInteraction 测试在 Robolectric 上不渲染涟漪,用户正在排查原因。
  • 用户写了 Thread.sleep(1000) 来「等待动画」,并询问为何测试不稳定。
  • 用户提到 androidDeviceTest / androidHostTest 源码集(androidx 内部使用)。

何时不使用本技能

  • 依赖尚未正确接入——从 ./configuring-test-dependencies/SKILL.md 开始。
  • 选择发生在 createComposeRule() 与 runComposeUiTest { } 之间——见 ./choosing-test-rule-vs-runtest/SKILL.md。
  • 测试运行在正确 flavor 但仍在 idle/动画上不稳定——见同步相关技能。

前置条件

  • androidx.compose.ui:ui-test、ui-test-junit4、ui-test-manifest 已配置到所选 flavor 的正确 configuration。
  • 宿主测试:testImplementation 下 org.robolectric:robolectric,Android block 中 testOptions { unitTests.isIncludeAndroidResources = true }。
  • 设备测试:配置好的模拟器或物理设备,testInstrumentationRunner 设为 androidx.test.runner.AndroidJUnitRunner(或 Hilt 子类)。
  • 了解 MainTestClock 语义。

工作流

  1. 按能力而非速度决定 flavor。能力矩阵:重组与状态变更(两者皆可);布局与测量(两者皆可);performTouchInput 触摸输入(宿主为合成事件,设备为真实输入管线);Modifier.indication 涟漪(宿主不可用,RenderThread 未被驱动);截图捕获 captureToImage(宿主不可用,无真实 Surface);enableAccessibilityChecks(宿主结果不确定,Robolectric 不能忠实驱动无障碍服务,b/332778271;设备可用,@RequiresApi(34));需要实时时钟变化的手势检测(宿主仅在手动 mainClock.advanceTimeBy(...) 时可用,设备自动可用);Activity 生命周期(宿主为近似,设备为真实);宿主最小 SDK 为 23,设备为模块的 minSdk;速度上宿主为秒级,设备为数十秒加模拟器启动;CI 占用上宿主仅需 JVM,设备需要模拟器服务或 Firebase Test Lab。逻辑/重组/finder 覆盖选宿主;涉及 RenderThread、涟漪动画、截图金标、无障碍验证的选设备。

  2. 将文件放入匹配的源码集。androidx 约定:src/androidHostTest/(= test/,Robolectric on JVM)、src/androidDeviceTest/(= androidTest/,instrumentation)、src/androidCommonTest/(共享 helper)。在普通应用模块中即 src/test/(宿主)与 src/androidTest/(设备)。

  3. 配置宿主测试类骨架(使用 @RunWith(AndroidJUnit4::class) @Config(minSdk = 23),并在 @Before 捕获 master timeout、@After 恢复)。

  4. 配置设备测试类骨架(使用 @get:Rule val rule = createComposeRule())。

  5. 对任何动画/手势检测测试,使用 mainClock.advanceTimeBy(...)。Robolectric 上手势检测依赖测试时钟推进,不接收真实墙钟 tick。

  6. 识别唯一合法的 Thread.sleep 场景:截图测试等待 RenderThread。Compose 的 IdlingResource 聚合 recomposer、snapshot 与 frame-clock awaiter,但 RenderThread 不在聚合内。涟漪动画与平台渲染管线拥有的绘制期动画无法通过 mainClock.advanceTimeBy 或 waitForIdle 等待。

  7. 强制要求:宿主测试中不得出现 Thread.sleep。

  8. 将截图、涟漪、无障碍测试放入设备源码集:截图需真实 Surface;涟漪在 RenderThread 绘制;无障碍检查为 @RequiresApi(34),Robolectric 结果不确定。

  9. 将快速逻辑/重组/状态测试放入宿主源码集,例如 StateRestorationTester 流程、纯状态变更验证、不依赖速度物理的 LazyList finder/滚动测试。

模式

  • 截图测试放宿主(错误,返回黑/空像素、涟漪不绘制)vs 放设备(正确)。
  • 宿主动画测试中使用 Thread.sleep(错误,与 MainTestClock 脱节)vs 使用 mainClock.advanceTimeByFrame() 与 advanceTimeBy(durationMillis)(正确)。
  • 宿主上跑无障碍检查(错误,结果不确定)vs 设备上跑(正确)。
  • 共享 body、两个源码集:将 body 放入 androidCommonTest/ 并创建两个薄包装。

强制规则

  • 不得将截图/captureToImage/涟漪/Modifier.indication 相关测试放入宿主源码集。
  • 不得依赖宿主测试的 enableAccessibilityChecks 结果,应在真实设备 API 34+ 上运行。
  • 宿主测试任何情况下都不得使用 Thread.sleep。
  • 设备测试仅在等待 RenderThread 进行涟漪/截图金标捕获时可使用 Thread.sleep。
  • Robolectric 宿主测试必须标注 @Config(minSdk = 23) 或更高。
  • 手势检测或动画宿主测试必须手动推进时钟。
  • 模块 android { } 中必须设置 testOptions { unitTests.isIncludeAndroidResources = true }。
  • 优先从宿主测试开始以快速反馈,仅在能力需要时迁移到设备测试。
  • 需要在两种 flavor 工作的测试,将 body 提取到共享 helper。

验证

  • ./gradlew :<module>:testDebugUnitTest 与 connectedDebugAndroidTest 均通过。
  • 宿主测试中无 Thread.sleep。
  • 宿主源码集中无 captureToImage、无 enableAccessibilityChecks、无依赖涟漪的测试。
  • 设备源码集中每个 Thread.sleep 都有解释 RenderThread/截图理由的注释。
  • Robolectric 宿主测试类带有 @RunWith 与 @Config(minSdk = 23)。
  • 动画/手势宿主测试设置 mainClock.autoAdvance = false 并显式调用 advanceTimeBy。

参考资料

Compose testing overview (Android Developers): https://developer.android.com/develop/ui/compo