质量不是测试数量,是反馈速度与生产证据
测试证明行为可重复,基准证明性能变化可测,生产指标证明真实设备没有被你的实验遗漏。
要验证「飞行模式下发送,恢复网络后只出现一条已发送消息」,最有价值的组合是:
- 只测一个 Repository mock 被调用一次
- 只做 Compose 截图测试
- Repository 集成测试验证数据库与重试,再用 UI 测试验证用户可见状态
- 人工点一遍即可
按失败成本分层,不按时髦程度分层
纯 reducer、格式化、排序和领域规则放本地单元测试,速度快、定位准。ViewModel 用 Fake Repository 与 runTest 验证状态序列。Room migration、DAO 查询、WorkManager 与真实 Android API 需要仪器测试。Compose 测试从语义树查找文本、角色与动作,验证用户能否完成任务。
端到端测试覆盖关键旅程,但慢且脆,数量应少。截图测试能抓视觉回归,不能证明点击后业务正确。测试替身也要匹配目的:Fake 模拟可工作的简化实现,Mock 验证交互,Stub 只给固定答案。不要把「调用过某方法」当成用户结果。
协程测试要控制时间,不要真的等待
runTest 提供虚拟时间与测试调度器。把生产 Dispatcher 注入,让测试里的所有协程共享同一个 scheduler;用 advanceUntilIdle() 推进任务,不写 Thread.sleep。测试 StateFlow 时要记得 stateIn(WhileSubscribed) 可能需要真实 collector 才启动上游。
Flow 的断言要覆盖值、顺序、取消与错误,不只取 first()。Turbine 能把逐项等待写得清楚,但核心仍是业务合同:搜索新词会取消旧结果,消息发送则不能被下一条取消。
性能必须测发布形态
Debug 构建、连接着 profiler、刚编译完的模拟器都不代表用户。启动分冷、温、热三种,优化以冷启动为基线。Macrobenchmark 从应用外驱动 release-like 构建,适合测启动、滚动与关键旅程;Microbenchmark 测小段热点函数。
Baseline Profile 告诉 ART 哪些关键路径预编译,让首次安装后的启动与滚动不必先靠解释和 JIT 热身。官方资料称许多应用可获得约 30% 的性能改善,但这是经验量级,不是你项目的保证;仍要用自己的基准。
ANR 是主线程失去响应,不是单纯「很慢」
主线程阻塞 I/O、锁竞争、Binder 慢调用、过重启动初始化都可能导致 ANR。Android Vitals 当前把用户感知 ANR 的整体不良阈值列为 0.47%,单一设备型号阈值为 8%。这两个数字会影响 Play 可见度,验证器会同官方事实表交叉核对。
诊断顺序:看生产聚类与受影响设备;抓主线程堆栈;判断是阻塞、锁、Binder 还是 CPU;用 Perfetto、系统 trace、StrictMode 与基准复现;最后加回归测试和指标。不要见到 ANR 就把代码扔进 IO,锁和 Binder 仍可能换个地方堵住。
写一个不靠 sleep 的 ViewModel 测试。发送后先看到 Pending,推进调度器后看到 Sent,且列表始终只有一条:
before advance: [Pending(c-1)] after advance: [Sent(c-1, s-9)] message count: 1
@Test
fun `retry reconciles the same optimistic message`() = runTest {
val repo = FakeChatRepository(scheduler = testScheduler)
val vm = ChatViewModel(repo, SavedStateHandle(mapOf("id" to "room-1")))
vm.onDraftChange("hello")
vm.send()
runCurrent()
assertEquals(Delivery.Pending, vm.uiState.value.messages.single().delivery)
advanceUntilIdle()
val message = vm.uiState.value.messages.single()
assertEquals(Delivery.Sent, message.delivery)
assertEquals("s-9", message.serverId)
}在 Android Studio 的 JVM test 中运行。再让 Fake 第一次返回「服务端成功但响应超时」,第二次按幂等键返回原结果,确保测试真的覆盖第 14 章的故障窗口。
医疗聊天不能只测快乐路径。优先做一张故障矩阵:进程在本地事务前后被杀、上传到一半断网、令牌刷新并发、权限在相机返回前撤销、数据库迁移失败、服务端返回重复确认。每个格子都要有可见状态与恢复路径。
「覆盖率 90% 说明质量很好。」覆盖率只说明哪些代码被经过,不说明断言是否有意义、关键故障是否建模、用户旅程是否能完成。
从风险列测试:状态转换用快速单测,边界一致性用集成测试,关键旅程用少量 UI/端到端测试,性能用发布基准,线上用 Vitals。
答案是第 3 项。幂等、数据库事务与重试是数据边界行为,需要 Repository 集成测试;气泡从 Pending 变 Sent 是用户可见合同,再用 UI 测试覆盖。只验证 mock 调用会把实现细节当结果。
这一章的一句话
质量链从可重复的状态测试开始,经发布基准,最后由真实设备指标闭环。
下一章不再加知识:把全书压成六个三十秒答案、一场消息系统设计和一份三天冲刺表。你会发现面试真正检查的不是记忆容量,而是能否先立合同,再谈取舍。