1. 理解测试夹具与隔离的必要性

测试夹具(Test Fixture)是 UI 测试中为被测组件准备的一组前置条件与依赖环境。在 Compose UI 测试中,夹具通常包括:Compose 树的根节点状态、ViewModel 实例、导航控制器、网络/数据库模拟对象以及界面主题配置。

常见误区:很多团队将夹具理解为一个“初始化函数”,实际上夹具是状态集合——它不仅决定测试的输入数据,还约束了测试运行时所有可变的内部变量。

1.1 共享状态导致的不确定性

当多个测试用例复用同一个全局或静态夹具时,测试之间会引入非预期的状态泄漏。典型场景包括:

  • ViewModel 持有的 LiveData/StateFlow 跨测试残留旧值
  • CompositionLocal 在运行序列中被改写后未复位
  • 协程作用域未在测试后卸载,导致协程继续执行引发竞争

这种不确定性会使测试结果不可靠:某次运行时全部通过,稍加修改或换序运行后大片失败,且失败原因难以定位——不是业务逻辑错误,而是夹具内部的“脏状态”。

debug 陷阱:共享状态的测试失败往往在 CI 中表现为偶发失败(Flaky Test),人工复现时可能因为调试环境线程调度不同而永不触发,浪费大量排查时间。

1.2 夹具隔离的四大收益

收益维度 核心说明 对 Compose UI 测试的直接影响
确定性 每个测试用例独立创建完全干净的夹具,不受之前用例残留影响 同一份 Composable 代码在任何运行顺序下都能得到一致的 pass/fail 结果
可重复 固定输入 + 固定夹具 = 固定输出,排除了环境变量/线程时序的干扰 开发者本地调试时失败,CI 上 100% 能以相同方式复现
并行 不存在共享可变状态,多个测试用例可以同时在多线程或多设备上执行 在 Gradle 并行测试模式下,运行时长可降低到原来的 1/4~1/8
维护性 夹具创建逻辑集中化、模块化,修改依赖仅影响单个夹具工厂 当 Compose 主题或 ViewModel 构造函数变更时,只需更新一处夹具工厂
核心原则:隔离不是“添加更多代码”,而是控制可变状态的生命周期——让每个测试用例的夹具在其启动时创建、结束时销毁,且不与其它测试共享内存中的任何对象引用。

1.3 隔离的典型实现路径(预览)

在本课程后续章节中,我们将按以下流程逐步构建完整的夹具隔离方案:

  1. 结构化夹具工厂——封装 ComposeTestRule 的创建与重组
  2. 状态重置协议——在每个测试前通过 clear/setValue 重置 ViewModel
  3. 依赖容器隔离——为每个测试注入独立的 Hilt/Koin 子组件
  4. 协程调度器切分——使用 TestDispatcher + 隔离作用域避免跨测试协程
  5. Snapshot 回退机制——利用 Compose Snapshot API 在测试间回滚状态

1.4 隔离前后的对比示意图

测试夹具隔离前后对比 未隔离 — 共享测试夹具 Test A Test B 全局/静态夹具 Scheduler · ViewModel · NavHost ⚠ 结果:偶发失败 · 不可复现 隔离后 — 独立夹具工厂 Test A Test B 夹具工厂 v1 独立实例 夹具工厂 v1 独立实例 ✓ 结果:通过/失败完全一致 可并行 · 可重复 · 易维护

上图左侧代表未隔离的测试用例共享同一个全局夹具对象,导致状态在 Test A 和 Test B 之间交叉污染;右侧展示每个测试用例从独立的夹具工厂获取全新实例,相互之间无任何状态耦合,从而获得确定、可重复、可并行、易维护的四项收益。