3. 主流快照工具对比:选对工具,少走弯路
快照测试的工具选型,我见过不少团队卡在这一步。有人冲着名气选,结果CI跑一次要十分钟;有人贪图配置简单,后来发现不支持Compose动画截图。市面上主流的有四款:Paparazzi、Shot、Roborazzi、还有Google官方的SnapshotContainer。它们各有各的脾气,没有银弹。
你想想看,选错工具的代价是什么?不只是重写测试用例,连整个CI流程都得改。我个人习惯在项目初期先花半小时做一次工具评估,这半小时能省下后面几个月的返工时间。
3.1 Paparazzi — 老牌稳定派
Paparazzi 是快照测试圈的"老前辈"。它基于 layoutlib 直接在 JVM 上渲染,不依赖真机或模拟器。我最早接触的就是它,当时被它的速度惊艳到——本地跑几百个用例也就几秒钟。
- 优点:
- 纯 JVM 运行,CI 集成零门槛,不需要启动模拟器
- 截图一致性强,同一份代码在不同机器上产出完全相同的图片
- 社区成熟,文档齐全,遇到坑基本都能搜到答案
- 缺点:
- 不支持真实的设备渲染效果,字体渲染、圆角、阴影和真机有肉眼可见的差异
- 对 Compose 动画的支持比较弱,需要手动控制帧
- 更新频率一般,Compose 新特性出来后往往要等几个版本才跟上
// build.gradle.kts — Paparazzi 配置示例
plugins {
id("app.cash.paparazzi") version "1.3.1"
}
dependencies {
testImplementation("app.cash.paparazzi:paparazzi:1.3.1")
}
// 测试用例
@RunWith(ParameterizedTest::class)
class MyComponentTest {
@Test
fun captureButton() {
paparazzi.snapshot {
MyButton(text = "Submit")
}
}
}
3.2 Shot — 真实设备派
Shot 走的是另一条路——它依赖真机或模拟器,截取屏幕上真实渲染的内容。说白了,它更像是一个"截图对比框架",而不是渲染引擎。我记得有一次在团队中推广 Shot,大家最大的顾虑就是 CI 上要跑模拟器,慢得让人崩溃。
- 优点:
- 截图和用户真实看到的效果完全一致,包括字体、阴影、硬件加速渲染
- 支持系统级 UI 元素(状态栏、导航栏)的截图
- 容差对比功能比较成熟,可以忽略微小的抗锯齿差异
- 缺点:
- 必须在设备或模拟器上运行,CI 配置复杂,执行速度慢
- 不同设备、不同 API 级别的截图会有差异,维护成本高
- 本地开发时频繁切换模拟器,体验不太流畅
// build.gradle.kts — Shot 配置示例
plugins {
id("com.karumi.shot") version "5.14.0"
}
shot {
tolerance = 0.1 // 像素容差比例
}
// 测试用例(Android Instrumentation Test)
@Test
fun captureButton() {
composeTestRule.setContent {
MyButton(text = "Submit")
}
compareScreenshot(composeTestRule)
}
3.3 Roborazzi — 速度优先派
Roborazzi 是后起之秀,基于 Robolectric 在 JVM 上运行。它既保留了 Paparazzi 的速度优势,又通过 Robolectric 模拟了 Android 框架的更多行为。我个人习惯在需要快速迭代的模块里先用 Roborazzi 做第一道防线。
- 优点:
- 纯 JVM 运行,速度极快,和 Paparazzi 在同一量级
- 对 Compose 的支持很积极,官方在持续迭代
- 支持 GIF 动画输出,方便调试过渡动画
- 缺点:
- 社区相对年轻,遇到冷门问题可能搜不到解决方案
- 模拟的 Android 环境不是 100% 真实的,某些 API 行为有差异
- 和 Paparazzi 一样,截图效果和真机存在差距
// build.gradle.kts — Roborazzi 配置示例
plugins {
id("io.github.takahirom.roborazzi") version "1.8.0"
}
dependencies {
testImplementation("io.github.takahirom.roborazzi:roborazzi:1.8.0")
}
// 测试用例
@RunWith(RobolectricTestRunner::class)
class MyComponentTest {
@Test
fun captureButton() {
composeTestRule.setContent {
MyButton(text = "Submit")
}
roboazzi.snapshot(composeTestRule, "MyButton")
}
}
3.4 SnapshotContainer — 官方正统派
SnapshotContainer 是 Google 在 Compose 1.5 之后推出的官方方案。它的思路和 Paparazzi 类似,也是纯本地渲染,但深度绑定 Compose 的渲染管线。我在做 Jetpack 库的测试时首选它,因为它和 Compose 源码走的是同一套代码路径。
- 优点:
- Google 官方维护,和 Compose 版本同步,不会有兼容性滞后
- 渲染结果和 Compose Preview 完全一致,开发体验统一
- 配置极其简单,几乎零样板代码
- 缺点:
- 只支持 Compose UI,不适用于 View 系统
- 功能相对基础,没有高级的容差对比、批量管理等功能
- 社区资源少,遇到问题主要靠看源码
// build.gradle.kts — SnapshotContainer 配置示例
// 无需额外插件,使用 Compose 测试依赖即可
dependencies {
testImplementation("androidx.compose.ui:ui-test-junit4:1.6.0")
}
// 测试用例
@Test
fun captureButton() {
composeTestRule.setContent {
MyButton(text = "Submit")
}
composeTestRule.snapshotContainer("MyButton")
}
3.5 四工具横向对比
直接看表最清楚。我把核心维度列出来,你对着自己的需求勾一下就行。
| 维度 | Paparazzi | Shot | Roborazzi | SnapshotContainer |
|---|---|---|---|---|
| 渲染方式 | layoutlib (JVM) | 真机/模拟器 | Robolectric (JVM) | Compose 渲染管线 |
| 速度 | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 真实度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| CI 友好度 | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Compose 支持 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 维护方 | Cash App | Karumi | takahirom (社区) | |
| 学习成本 | 低 | 中 | 低 | 极低 |
3.6 选择决策树
光看表你可能还是纠结,别急,我画了一张决策图。你顺着箭头走,答案基本就出来了。
3.7 我的选择建议与避坑指南
决策树给了一个大方向,但实际落地还有不少细节。我根据自己的踩坑经验,给你几条实在的建议。
· 纯 Compose 项目 + 追求速度 → Roborazzi 或 Paparazzi
· 需要真机像素级对比 → Shot
· 做 Jetpack 库或 Compose 工具链 → SnapshotContainer
· 大型项目混合 View/Compose → Roborazzi + Shot 组合使用
我吃过一个亏:有次在 CI 上只配了 Shot,每次提交要等 20 多分钟跑截图,开发效率直线下降。后来改成 Roborazzi 做日常 + Shot 做 nightly,问题才解决。你想想看,如果一开始就规划好工具的分层使用,这些时间完全可以省下来。
另一个容易踩的坑是图片存储。快照测试跑多了,Git 仓库里的图片文件会迅速膨胀。我曾经遇到过仓库体积从 200MB 暴涨到 2GB 的情况。建议把截图目录添加到 .gitignore,只在 CI 上保留基线版本,或者用云存储管理。