1. 混合测试夹具概述

1.1 什么是混合夹具

混合测试夹具(Hybrid Test Fixture)是指在一次 Android 测试中同时使用 Espresso(针对传统 View 系统)和 Compose Test(针对 Jetpack Compose)两套测试 API 来驱动界面交互与断言。这种夹具出现在 View 向 Compose 迁移过渡期或 同一页面内同时包含 View 和 Composable 的场景中。

判定标准:一个测试方法体内同时调用了类似 onView(withId(...)) 和 onNodeWithText(...) 即视为混合夹具。传统纯 View 或纯 Compose 测试不属于本章讨论范围。

1.2 适用场景

场景详细说明典型比例
渐进式迁移已有大型 View-based 工程逐步将 Fragment 内布局迁移为 Compose,测试需覆盖新旧混合页面60%
第三方 SDK / 复杂 View地图、WebView、视频播放器等强依赖 View 的组件仍保留,UI 外层已用 Compose 包裹20%
跨层次联动Compose 弹出 Dialog 中嵌套传统 SurfaceView,或 ViewPager2 内加载 Composable15%
历史遗留回归交接团队或遗留模块尚未重构,新功能测试必须同时操作两套 UI 体系5%

1.3 核心挑战

混合夹具并非两套 API 的简单拼接,而是面临以下四个关卡:

  • 查找方式不一致 – Espresso 使用 onView(withId(...)) 匹配 View,Compose 使用 onNodeWithTag(...) 匹配 Semantics 节点,两者定位语义完全不同。
  • 同步与等待机制冲突 – Espresso 用 IdlingResource 控制异步任务,Compose 用 waitForIdle() 驱动 recomposition;混合场景容易导致 IdlingResource 超时或 Compose 主线程锁死。
  • View 与 Compose 嵌套 – 当 Compose 内嵌入 View(AndroidView)时,Espresso 无法直接抵达 View 层级,需要先通过 Compose 定位到 AndroidView 容器再切换查找策略。
  • 测试初始化与重置成本 – 混合夹具需要同时初始化 Espresso 和 Compose 的 ActivityScenario / ComposeTestRule,重复启动 Activity 会显著增加单测执行时间。
常见误解:ComposeTestRule 自带 activityRule 不等于自动兼容所有 View 操作。必须在测试类中显式声明 @get:Rule val composeRule = createAndroidComposeRule() 并组合 ActivityScenarioRule,否则 Espresso 的 onView 将抛出 NoMatchingViewException。
混合测试夹具结构关系图 混合测试夹具 Espresso 测试层 View / WindowManager IdlingResource 连锁 Compose 测试层 Semantics 节点树 recomposition 调度 核心挑战:查找冲突 / 同步锁 / 嵌套穿透 / 初始化重置 ▸ 双规则叠加 ▸ 主线程死锁 ▸ AndroidView 逃逸 ▸ 重复启动

1.4 混合夹具的通用架构

正式编写测试前,理解混合夹具的典型构件组合是关键。大多数项目采用 双 Rule + 统一 Activity 启动 方案:

@RunWith(AndroidJUnit4::class)
class HybridDashboardTest {

    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @get:Rule
    val composeRule = createAndroidComposeRule<MainActivity>()

    @Test
    fun hybridValidationTest() {
        // Espresso 操作传统 View
        onView(withId(R.id.btn_legacy)).perform(click())

        // Compose 操作 Composable
        composeRule.onNodeWithText("迁移标题").assertIsDisplayed()
    }
}
关键原则:createAndroidComposeRule 内部已持有 ActivityScenario,无需额外创建;但 Espresso 的 onView 仍依赖 ActivityScenarioRule 的自动 lifecycle 管控。两个 Rule 的 启动顺序 和 TestRunner 层级 会直接影响夹具稳定性,第 3 章将详细展开。