第一章 快照测试概述
什么是快照测试?
快照测试,说白了就是把 UI 的渲染结果拍张"照片"存起来。下次跑测试时,再拍一张新的,然后逐像素对比——不一样就报错。
在 Jetpack Compose 里怎么做?我们用 Roborazzi 或 Paparazzi 这类库,让 Composable 在纯 Java 环境下渲染成一张 PNG。这张 PNG 就是基线(baseline)。以后每次改代码,测试工具都会自动截一张新图,跟基线比对。不一样?说明界面变了——可能是你想要的,也可能是个 bug。
核心逻辑: 截图 → 保存基线 → 对比差异 → 判定通过/失败。
我刚开始接触快照测试时觉得这玩意儿太粗暴了。像素级对比?能靠谱吗?后来有个活生生的事让我改变了看法——那次一个 1dp 的阴影偏移,传统测试全绿通过,线上用户却说按钮"长歪了"。嗯,从那以后我就老老实实把快照测试加进了每个组件库。
为什么需要快照测试?
你想想看,传统的 UI 测试怎么做的?
- 语义匹配:
assert(isDisplayed)检查控件存在,但看不到颜色、圆角、阴影。 - 属性断言:检查
width、height、padding,但你不会把每个像素的 RGB 值都写出来吧? - 截屏对比(手动):每次发版前人工打开 App 扫一遍?那基本是走马观花。
快照测试直接把"视觉结果"变成可断言的值。你改了一个主题色?基线图片自动告诉你有多少地方被影响了。我个人习惯在 CI 流水线里加一步快照对比,哪怕只是改了某个 Composable 的 Modifier.padding,也能立刻看到 Layout 是否跑偏。
| 类型 | 能发现什么 | 发现不了的 |
|---|---|---|
| 单元测试 | 逻辑错误 | 像素级视觉偏差 |
| 语义测试 | 元素存在/缺失 | 颜色、阴影、间距 |
| 快照测试 | 任意视觉差异 | 交互行为、动画时序 |
我遇到过最经典的案例:某次升级 Compose BOM 版本后,所有 Button 的圆角从 12dp 变成了 16dp。单元测试全部通过,人工 review 也没注意。快照测试直接标红了 47 张图——每个带按钮的页面都变了。你说值不值?
典型应用场景
不是什么项目都适合上快照测试。根据我的经验,下面几类场景效果最好:
- 组件库 / Design System
你有一个统一的PrimaryButton、SecondaryButton……改一个基础样式会影响所有用法。快照测试能保证"牵一发而全身皆知"。 - 主题切换验证
Light / Dark 模式、高对比度主题、品牌色更换。跑一遍快照,所有主题下的所有组件截出来,对比基线——完美覆盖。 - 多屏幕 / 多语言适配
不同屏幕密度、不同 Locale 下文字长度不一样。快照测试能帮你发现文本溢出、布局断裂。 - 自定义绘制 (Canvas)
Compose 里用Canvas或drawBehind的图形,常规测试根本没法断言。快照是唯一靠谱的方式。 - 动画关键帧
虽然快照不能测试完整的 60fps 动画,但你可以截取动画的起止状态或中间关键帧,确保过渡正确。
局限性
话不能说太满。快照测试不是银弹,我自己踩过不少坑:
我曾经遇到过一个坑:
- 在 Mac 上生成的基线图片是 Retina 分辨率(@2x),但 CI 机器是 Linux,字体渲染差异导致 90% 的测试失败。费了半天才搞清楚,后来强制统一用
robolectric的虚拟屏幕密度。
这里列举几个主要局限:
- 平台渲染差异: macOS、Windows、Linux 的字体渲染引擎不一样。同一段文本在不同 OS 上截出来的像素可能差 1~2px。解决方案:统一在 Docker 容器或同一套 JDK + 字体环境下运行快照测试。
- 像素级敏感: 1px 的样式变化就会导致测试失败。有时候是设计师故意微调,有时候只是反走样算法波动。你需要一个合理的 阈值容忍度(比如
tolerance = 0.1%)。 - 维护成本: 故意改 UI 后必须手动更新基线图片,推一个新的 PR。如果一个团队频繁改视觉样式,基线图片会变成"变更清单",测试反而变成负担。
- 没法测交互: 快照是静态的,不能验证点击、滑动、长按后的状态变化(除非你手动触发并重新截图)。
- 基线膨胀: 成百上千张 PNG 塞进 Git 仓库,历史记录会变得很重。我建议用 Git LFS 或专门的对象存储来管理。
我的建议: 快照测试只用在"相对稳定"的组件上——那些设计稿定稿后很少改的部分。对于快速迭代的页面,可以只对关键区域做快照,或者推迟到功能冻结后再加入。
总结一下:快照测试是 UI 回归的最后一道防线,但不是全部防线。它跟单元测试、集成测试、人工走查互补。理解它的能力和边界,才能用好它。