4. 业务层夹具设计

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 容器注入。

业务层夹具信任链路 测试用例 UseCase (业务逻辑) FakeRepository 内存数据源 预置数据工厂 测试用例通过 DI 将 FakeRepository 注入 UseCase;FakeRepository 使用预置数据工厂提供的测试数据, 避免依赖 Room 实例或 SQLite 文件。信任链路完全由内存数据结构保证。

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 落地步骤

  1. 定义 Repository 接口,提取出 DAO 之外的数据访问抽象。
  2. 创建 FakeRepositoryImpl,内部维护 ConcurrentHashMap 或 MutableStateFlow。
  3. 实现数据预置工厂类(如 UserTestDataFactory),提供 createDefaultUser()、createAdminUser() 等方法。
  4. 在测试的 @Before 或 TestFactory 中实例化 FakeRepository,传入预置数据集合。
  5. 使用构造注入或自定义 DI 模块(如 Hilt 测试绑定 @Binds)将 FakeRepository 注入 UseCase。
  6. 编写第一个 UseCase 测试,验证预置数据能被正确查询和转换。
  7. 对每个 UseCase 补充边界值测试(空列表、单条记录、最大缓存数等)。

4.7 验收标准

  • 每个 UseCase 测试类不依赖 Room.inMemoryDatabaseBuilder 或任何 SQLite 文件。
  • FakeRepository 实现了接口的每一个方法,且返回类型、异常行为与真实实现保持一致。
  • 数据预置工厂可以覆盖:空数据、正常数据、异常数据(如 null 字段、越界时间戳)。
  • 测试用例可以在不启动模拟器的情况下(纯 JVM)运行,测试耗时小于 100ms/用例。
  • Repository 契约测试(若存在)在生产环境和夹具环境下均通过。