第1章 开篇:为什么要学混合测试?

说实话,我刚接触 Android 测试那会儿,觉得 Espresso 就已经够用了。后来项目慢慢迁移到 Compose,问题就来了——老页面用 Espresso,新页面用 Compose,测试到底该怎么写?

你想想看,一个 App 里既有 View 系统又有 Compose,如果测试只能测一边,那另一半的 UI 就等于裸奔。所以我个人觉得,搞懂混合测试已经不是「进阶技巧」,而是每个 Android 工程师早晚要迈过去的坎。

从测试金字塔说起

测试金字塔这个概念,大家应该不陌生。我习惯把它理解成「投入产出比」的指南:底层单元测试快、便宜、好维护,适合写大量;顶层 E2E 测试慢、贵、不稳定,只能写关键路径。

但在 Android 里,UI 测试其实横跨了中间两层:集成测试和 E2E。你写个 Espresso 测一个 Activity 里的几个 View,这算集成测试;要是启动 App 然后测完整登录流程,那就算 E2E。说白了,工具一样,测试范围不同而已。

混合测试的核心问题:当你的 App 里既有 View 又有 Compose,测试范围该怎么切分?我曾经在一个页面上同时用 RecyclerView(View 系统) 和一段 Compose 的 BottomSheet,Espresso 拿不到 Compose 节点,Compose 的测试规则又没法直接操作 RecyclerView。嗯,这就是我们这门课要解决的核心痛点。

关键认识:混合测试不是「选一个淘汰另一个」,而是学会在同一个测试类里同时驾驭两套测试 API,让它们各司其职。

Espresso 与 Compose——两套体系

先列个表,方便你一眼看出差异:

对比维度 Espresso (View 系统) Compose UI 测试
测试规则 ActivityTestRule / ActivityScenario ComposeTestRule (createAndroidComposeRule)
找节点方式 onView(withId(R.id.foo)) onNodeWithTag("foo") / onNodeWithText(...)
执行交互 perform(click()) performClick()
跨组件限制 无法直接操作 Compose 节点 无法直接操作 View 系统的子节点
官方推荐场景 传统 View 页面 / 已有项目 Compose 新功能 / 纯 Compose 页面
个人经验:我在做电商项目时,商品列表页用 RecyclerView(Espresso 测),详情页的底部弹窗用 Compose(Compose 测试规则测)。两个测试类虽然分开,但每次跑全量测试都要单独配 runner,挺麻烦的。后来我干脆在同一个测试类里用 ActivityScenario + ComposeTestRule 双规则,虽然配置要小心,但维护成本低了一半。

你可能会问:「为什么不能只用一种?」原因很简单——Compose 和 View 系统的渲染树是两棵独立的树。Espresso 的 onView 只搜 View 树,Compose 的 onNode 只搜 Compose 树。两个世界中间隔着一堵墙。

混合测试的价值与挑战

说白了,价值就一句话:覆盖你 App 的真实 UI 场景。一个页面里既有 View 又有 Compose,你只测一边,另一边只是「看起来跑过了」,实际上可能按钮点了没反应、列表滚动冲突。我遇到过几次线上崩溃,都是因为 View 和 Compose 之间的消息传递没测到。

那挑战呢?我总结成三点:

  • 测试规则冲突:同一个测试方法里同时准备 ActivityScenario 和 ComposeTestRule,容易导致生命周期混乱。
  • 节点定位困难:Espresso 找不到 Compose 节点,Compose 找不到 View 节点。怎么办?要么切换规则,要么用 ComposeTestRule 的 onView 桥接——但桥接有性能损耗。
  • 等待机制差异:Espresso 自带 IdlingResource,Compose 有 waitForIdle(),混在一起时往往出现「一个等了,另一个没等」的偶发失败。
避坑指南:我曾经在同一个测试里混用 onView 和 onNode,没有显式设置等待,结果 CI 上 30% 的用例偶尔失败。排查了一天才发现:Espresso 的 perform 会等主线程空闲,但 Compose 动画完成后才真正空闲。解决办法是在 Compose 操作后加 composeTestRule.waitForIdle(),没有更好的捷径。

本章知识结构图

混合测试知识体系 测试金字塔 单元测试 集成测试(UI测试) E2E测试 Espresso (View系统) Compose UI测试 (Compose树) 混合测试 同一测试类中双规则 价值:全面覆盖 挑战:规则冲突
这张图想表达什么?混合测试不是凭空出现的,它站在测试金字塔的「集成测试」层上,同时需要 Espresso 和 Compose 两套工具配合。价值和挑战是硬币的两面,你必须都理解,才能写出稳定的混合测试用例。

小结

这一章我们聊了测试金字塔的分层、两套测试体系的差异,以及混合测试为什么既诱人又麻烦。我个人觉得,最重要的不是你今天记住多少 API,而是建立「混合视角」——以后看到一个新页面,先问自己:这里哪部分是 View,哪部分是 Compose?然后决定测试策略。

嗯,后面几章我们会动手写代码,从最简单的单页面混合测试开始,一步步搞定双规则、异步等待、跨组件定位这些硬骨头。你准备好了吗?