4. 业务层夹具设计:Repository注入、UseCase数据预置
4.1 边界与分层定位
在 Room 数据库夹具分层设计中,业务层处于数据层(Database + DAO)与表现层(ViewModel / Screen)之间。业务层的夹具目标不是模拟数据库本身,而是模拟 Repository 的实现 以及 UseCase 的数据预置状态。
- 上层边界:ViewModel / UseCase 依赖的是 Repository 接口,而非 DAO 或 RoomDatabase。
- 下层边界:Repository 内部组合 DAO 或 Remote 数据源,夹具只需提供同接口的替换实现。
- 信任链:测试应信任 Repository 夹具的返回值,而不信任真实数据库的随机状态。
核心原则:业务层夹具不操作 SQLite、不初始化 Room 实例,只通过构造注入或工厂方法提供预置数据的数据源。
4.2 组件职责
| 角色 | 职责 | 夹具方式 |
|---|---|---|
| FakeRepository | 实现 Repository 接口,内部持有内存 Map 或 List | 构造时传入预置数据 |
| UseCase | 组合多个 Repository 或执行业务逻辑 | 通过依赖注入替换内部 Repository 为 Fake 版本 |
| 数据预置工厂 | 生成符合业务模型的默认值、有效值、边界值 | 静态方法或 Builder 模式 |
| TestDispatcherProvider | 提供协程调度器(主线程与 IO 分离) | 通常由测试框架统一注入 |
4.3 数据流 / 控制流 / 信任链路
下方 SVG 图展示了业务层夹具的典型结构:UseCase → FakeRepository → 内存数据源,控制流由测试用例通过构造或 DI 容器注入。
4.4 选型权衡
- Fake vs Mock:Fake 拥有真正的业务逻辑(如数据筛选、排序),可验证完整交互;Mock 仅验证方法调用次数,不适合复杂预置场景。
- 全局单例 vs 局部工厂:每个测试用例应获得独立 FakeRepository 实例,避免共享状态污染。推荐使用
testObjectFactory或@Before方法重建。 - 数据预置粒度:为每个 UseCase 定义最小化数据模型,而非从数据库表结构映射。例如“用户已登录且拥有管理员角色”只需三个字段,无需全字段构造。
常见陷阱:若 FakeRepository 直接复制真实 Repository 的 SQL 处理逻辑,会导致测试与生产行为不对等。应仅模拟接口契约,不模拟数据库约束(如唯一索引、事务回滚)。
4.5 风险点与缓解
| 风险点 | 缓解措施 |
|---|---|
| FakeRepository 与真实实现行为差异 | 编写契约测试(Contract Test)验证两者对同一输入返回相同结果 |
| 预置数据过多导致测试膨胀 | 使用数据构建器(Builder)设置默认值,测试只覆盖关键差异字段 |
| 协程调度器混乱导致异步断言失败 | 统一注入 TestDispatcherProvider,并配合 runTest 或 advanceUntilIdle |
| Repository 多依赖组合时注入链过长 | 提供 Repository 创建工厂方法,如 createFakeUserRepository(prefilledData: List |
4.6 落地步骤
- 定义 Repository 接口,提取出 DAO 之外的数据访问抽象。
- 创建 FakeRepositoryImpl,内部维护
ConcurrentHashMap或MutableStateFlow。 - 实现数据预置工厂类(如
UserTestDataFactory),提供createDefaultUser()、createAdminUser()等方法。 - 在测试的
@Before或TestFactory中实例化 FakeRepository,传入预置数据集合。 - 使用构造注入或自定义 DI 模块(如 Hilt 测试绑定
@Binds)将 FakeRepository 注入 UseCase。 - 编写第一个 UseCase 测试,验证预置数据能被正确查询和转换。
- 对每个 UseCase 补充边界值测试(空列表、单条记录、最大缓存数等)。
4.7 验收标准
- 每个 UseCase 测试类不依赖
Room.inMemoryDatabaseBuilder或任何 SQLite 文件。 - FakeRepository 实现了接口的每一个方法,且返回类型、异常行为与真实实现保持一致。
- 数据预置工厂可以覆盖:空数据、正常数据、异常数据(如 null 字段、越界时间戳)。
- 测试用例可以在不启动模拟器的情况下(纯 JVM)运行,测试耗时小于 100ms/用例。
- Repository 契约测试(若存在)在生产环境和夹具环境下均通过。