1. 测试夹具基础概念
1.1 什么是测试夹具
测试夹具(Test Fixture) 是指为执行测试而准备的固定环境与资源集合。在 Android 测试语境中,夹具涵盖了被测对象实例、依赖注入容器、数据库状态、文件系统快照、网络模拟端点以及配置参数等。夹具不是测试逻辑本身,而是让测试逻辑能够稳定、重复、独立运行的前提条件。
| 维度 | 解释 | Android 示例 |
|---|---|---|
| 物理资源 | 文件、数据库、SharedPreferences | mock 一个 SQLite 数据库,注入测试数据 |
| 环境状态 | 系统语言、网络状况、传感器 | 使用 ConfigurationContext 设置地区 |
| 依赖服务 | 网络 API、Binder、ContentProvider | 用 MockWebServer 模拟 HTTP 响应 |
| 对象图 | DI 容器中的 Bean 组装 | Dagger/Hilt 测试模块替换生产实现 |
核心原则:测试夹具要做到“可重复构造、独立隔离、快速拆卸”。每次测试执行后夹具应自动清理,避免状态泄漏影响其他用例。
1.2 测试夹具的作用与价值
- 消除不确定性:固定时间、固定数据、固定依赖,使测试结果仅由被测代码决定。
- 提升执行速度:预置内存对象而非每次重新创建进程或启动 Activity。
- 隔离测试用例:每个用例拥有独立的夹具副本,不共享可变状态。
- 简化断言逻辑:夹具已知输入,输出只需比对预期值,无需处理环境噪声。
- 支持参数化:同一套夹具结构可注入不同数据组合,驱动大量边界用例。
经验要点:在 Android 测试中,约 70% 的测试失败源自夹具构造不当(如未清理数据库、未重置 Mock 对象),而非被测代码缺陷。优先把夹具的稳定性和隔离性做到位,能显著降低维护成本。
1.3 测试夹具与测试代码的关系
测试夹具和测试代码是“准备 — 执行 — 验证 — 拆卸”四阶段中的前、后两段。夹具负责第 1 和第 4 阶段,测试代码负责第 2、3 阶段。两者通过依赖注入或回调接口连接,不应互相耦合。
| 阶段 | 责任主体 | 典型操作 |
|---|---|---|
| SetUp(准备) | 测试夹具 | 创建对象、插入数据、启动 Mock Server |
| Exercise(执行) | 测试代码 | 调用被测方法、触发事件 |
| Verify(验证) | 测试代码 | 断言返回值、验证交互次数 |
| TearDown(拆卸) | 测试夹具 | 关闭数据库、释放锁、清空缓存 |
// 测试夹具与测试代码分离的典型结构
class UserRepositoryTest {
// —— 夹具开始 ——
private lateinit var database: AppDatabase
private lateinit var api: MockWebServer
@Before
fun setUp() {
database = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().targetContext,
AppDatabase::class.java
).build()
api = MockWebServer()
api.start()
}
@After
fun tearDown() {
database.close()
api.shutdown()
}
// —— 夹具结束 ——
// —— 测试代码 ——
@Test
fun testSaveUser() {
val user = User(id = 1, name = "Alice")
database.userDao().insert(user)
val loaded = database.userDao().getById(1)
assertEquals("Alice", loaded.name)
}
}
常见错误:在测试代码中直接 new 依赖对象、硬编码文件路径或依赖静态全局状态,会导致夹具与测试逻辑紧耦合,一旦环境变化就需要大量修改测试代码。应始终通过构造器注入或测试模块提供夹具。
1.4 Android 测试层次中的夹具定位
Android 测试从上到下分为 UI 测试、集成测试、单元测试 三个层次。不同层次的夹具复杂度和作用域差异很大:
- 单元测试:夹具最小,通常只包含一个类实例 + Mock 依赖。运行在 JVM 上,不依赖真机或模拟器。
- 集成测试:夹具涉及真实数据库、文件系统或网络端点。运行在 Android 设备或模拟器上,使用
AndroidJUnitRunner。 - UI 测试:夹具需要启动 Activity、注入 Intent、配置系统权限。常借助
ActivityScenario和ComposeTestRule。
分层策略:尽量把业务逻辑下沉到单元测试,用轻量夹具覆盖;集成测试和 UI 测试只验证跨越边界的行为,避免为每一行代码都准备重量级夹具。
1.5 Android 测试夹具的典型实现模式
在实际项目中,夹具设计有三种主流实现模式,开发者应根据测试层次和团队规范选择:
| 模式 | 适用层次 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|---|
| Implicit Fixture | 单元测试 | 在 @Before 中集中构造,所有用例共享 | 代码量少,适合通用依赖 | 用例间可能耦合,修改影响范围大 |
| Explicit Fixture | 所有层次 | 每个测试方法自包含构造代码 | 完全隔离,容易阅读 | 重复代码多,维护成本高 |
| Factory/Build Pattern | 集成测试、UI 测试 | 使用 ObjectMother 或 TestDataBuilder |
复用性强,组合灵活 | 需要额外工厂类,初期搭建较慢 |
// Factory/Build Pattern 示例:ObjectMother 风格
object TestUsers {
fun alice() = User(
id = 1,
name = "Alice",
email = "alice@example.com",
role = Role.ADMIN
)
fun bob() = User(
id = 2,
name = "Bob",
email = "bob@example.com",
role = Role.GUEST
)
fun listOfThree() = listOf(alice(), bob(), User(
id = 3,
name = "Charlie",
email = "charlie@example.com",
role = Role.EDITOR
))
}
推荐做法:团队内约定使用 Explicit Fixture + ObjectMother 组合。核心业务数据通过 ObjectMother 构造,而每个测试方法的特殊状态(如异常条件、边界值)显式写在方法内,既保证可读性又避免臃肿的 @Before 块。
1.6 常见错误与稳定性优化
- 错误:全局静态状态泄漏 — 多个测试共享一个静态
DatabaseHelper实例,导致数据污染。应使用inMemoryDatabaseBuilder每次重新创建。 - 错误:Fixture 与测试逻辑混合 — 在断言语句中创建文件或网络连接。必须通过
@Before/@After分离。 - 错误:忽略 TearDown 异常 — 夹具拆卸中抛异常会掩盖测试失败。使用
@Rule或Closeable接口确保资源释放。
// 使用 @Rule 安全管理夹具资源的推荐写法
@RunWith(AndroidJUnit4::class)
class SafeFixtureTest {
@get:Rule
val mockServerRule = MockWebServerRule() // 自动 shutdown
@get:Rule
val databaseRule = InMemoryDatabaseRule() // 自动 close
@Test
fun testWithSafeFixture() {
// 直接使用 databaseRule.database 和 mockServerRule.server
// 无需手动 setUp / tearDown
}
}
性能警示:在集成测试和 UI 测试中,每次重新创建数据库或启动 MockWebServer 都会产生毫秒级开销。如果夹具构造时间超过测试执行时间的 50%,应考虑使用
@ClassRule 或测试套件级别共享只读夹具,但必须确保共享数据不会被修改。
1.7 本章要点总结
| 知识点 | 核心结论 |
|---|---|
| 测试夹具定义 | 为测试执行提供的固定环境与资源集合,包括对象、数据、配置、依赖 |
| 测试夹具作用 | 消除不确定性、提升速度、隔离用例、简化断言、支持参数化 |
| 与测试代码的关系 | 夹具负责 SetUp 和 TearDown,测试代码负责 Exercise 和 Verify,通过构造器注入解耦 |
| Android 测试层次 | UI 测试 → 集成测试 → 单元测试,每层夹具复杂度递减,执行速度递增 |
| 实现模式选择 | 单元测试用 Implicit/Explicit,集成和 UI 测试推荐 Factory/Build Pattern |
| 稳定性关键 | 资源必须释放,状态必须隔离,Fixture 与测试逻辑必须分离 |
下一阶段预告:第 2 章将深入讲解如何基于 JUnit4/5 的 Rule 体系设计可复用的测试夹具,包括
@Rule、@ClassRule 和自定义 TestWatcher 的实现细节与实战案例。