3. 常用快照测试库对比:Shot、Paparazzi、Roborazzi
做 Compose 快照测试这些年,我踩过的坑不算少。最开始用的 Shot,后来换成 Paparazzi,现在主力是 Roborazzi。你可能会问:这三个到底有什么区别?我该选哪个?
别急,咱们一个个聊。我先把话说明白——没有银弹。每个库都有自己的脾气,选对了少加班,选错了……嗯,你懂的。
3.1 Shot——老大哥,成熟稳重
Shot 是 Jake Wharton 的杰作,主打基于 JUnit 规则截图对比。我在公司第一个快照测试项目就用它,当时觉得太方便了——加个 @Rule 就行。
@get:Rule
val shotRule = ShotRule()
@Test
fun testLoginButton() {
composeTestRule.setContent {
LoginButton()
}
shotRule.compareScreenshot(composeTestRule)
}
优点?
- Compose 原生支持——直接和 ComposeTestRule 配合,不用额外配置。
- 社区成熟——遇到问题 Stack Overflow 上基本能找到答案。
- 代码侵入低——加个规则就能测,老项目改造压力小。
缺点也很明显:
- 依赖模拟器或真机——CI 上跑的话,得先启动模拟器,慢啊。我记得有一次 Jenkins 上跑 Shot 测试,光等模拟器启动就用掉 3 分钟。
- 多模块支持一般——每个模块都得单独配置规则,有点烦。
- 更新较慢——最近几个 Compose 版本更新后,Shot 偶尔会有兼容性问题。
我曾经踩过的坑:用 Shot 时如果 Compose 版本升级到 1.3+,记得检查 setContent 是否正常工作。别问我怎么知道的——那次 CI 全红,我排查了一下午才发现是版本不兼容。
3.2 Paparazzi——快、准、轻
Paparazzi 不走寻常路。它不启动模拟器,而是直接利用 Android 的布局渲染管线来生成图片。说白了,它就是个纯 Java 库,能在本地 JVM 上跑。
看看它的用法:
@RunWith(ParameterizedTest::class)
class MyTest {
@get:Rule
val paparazzi = Paparazzi(
deviceConfig = DeviceConfig.PIXEL_6,
theme = "android:Theme.Material.Light"
)
@Test
fun testButton() {
paparazzi.snapshot {
MyComposable()
}
}
}
优点:
- 快!——没有模拟器启动开销,几秒就能跑完快照集。我负责的一个中型项目,300+ 测试用例,用 Paparazzi 跑全程只要 40 秒,之前用 Shot 得 5 分钟。
- Gradle 配置简单——加个插件就完事,不需要复杂的依赖管理。
- 支持多设备配置——可以一键切换不同分辨率和 DPI,测试覆盖率上去了。
缺点同样存在:
- 渲染不完全等同于真机——字体、动画、硬件加速这些在 Paparazzi 里是模拟的,有时候会和真机有差异。
- 对系统依赖强——版本升级时可能出现渲染引擎不兼容。
- 只能测 View 级别的快照——不能像 Shot 那样直接测 Compose 的
setContent外部逻辑,得包成 Composable 函数。
我个人习惯:Paparazzi 适合用在组件级(例如 Button、Card、TextField)的快照测试,因为这些组件渲染稳定,不容易和真机有偏差。如果想测整个页面,我会用 Roborazzi。
3.3 Roborazzi——后来者,集大成
Roborazzi 是我目前的主力工具。它结合了 Paparazzi 的纯 JVM 渲染能力,又吸收了 Shot 的 Compose 原生集成思路。更棒的是,它内置了 CI 友好的输出格式。
配置方式:
// build.gradle.kts
plugins {
id("io.github.takahirom.roborazzi") version "1.8.0"
}
dependencies {
testImplementation("io.github.takahirom.roborazzi:roborazzi-compose:1.8.0")
}
// 测试代码
@Test
fun testCardSnapshot() {
composeTestRule.setContent {
MyCard()
}
composeTestRule.onRoot().captureRoboImage()
}
优点一览:
- 支持多平台——Compose Multiplatform 也能用,跨 iOS 和 Android。
- CI 好友——生成的报告可以直接和 GitHub Actions / GitLab CI 的
diff展示集成。 - 性能不错——介于 Paparazzi 和 Shot 之间,比 Shot 快不少。
- 可定制性强——支持自定义
ImageComparator,比如你想忽略某些像素区域的差异。
缺点:
- 起步晚——社区资源相对少,遇到边缘问题得靠自己看源码。
- 配置稍多——尤其是多模块项目,每个模块的插件版本需要保持一致,否则会莫名其妙的编译错误。
注意:Roborazzi 依赖 Robolectric 2 以上版本。如果你项目里还在用老版 Robolectric,升级时会遇到一些冲突。我曾经花了半天时间排查 classloader 冲突——最后发现是 androidx.test 的版本没对齐。
3.4 一图胜千言——核心对比
| 维度 | Shot | Paparazzi | Roborazzi |
|---|---|---|---|
| 运行环境 | 模拟器/真机 | JVM | JVM(基于 Robolectric) |
| 速度 | 慢(需模拟器) | 极快 | 较快 |
| CI 友好度 | 低(需模拟器加速) | 高 | 高(内置 diff 报告) |
| Compose 支持 | 原生 | 需封装 | 原生 |
| 多平台 | 仅 Android | 仅 Android | Android + 桌面 + iOS(实验) |
| 维护活跃度 | 中(更新慢) | 中 | 高 |
| 学习成本 | 低 | 低 | 中等 |
3.5 我怎么选?——实际场景建议
讲了这么多,直接给你一个决策流程吧(这也是我每次新项目选型时用的路子):
- 先看速度要求——如果 CI 上你只有 30 秒的快照测试时间预算?别想了,直接上 Paparazzi 或 Roborazzi。Shot 肯定来不及。
- 再看项目复杂度——纯 Android 单 App,团队小,测几个核心页面?Shot 够用,简单粗暴。你是多模块项目,还要跑桌面端?Roborazzi 才是最稳的。
- 最后看团队习惯——大家都熟悉 JUnit Rules?Shot 最像老式测试写法,迁移成本低。想一步到位拥抱多平台?Roborazzi 值得投入。
我的实践经验:对于中型 Android 项目(20+ 模块),我最后选了 Roborazzi + Paparazzi 混合:核心 UI 组件用 Paparazzi(快,跑 CI 爽),复杂页面级快照用 Roborazzi(覆盖全)。两个工具都整合进同一个 Gradle 插件配置里,互不干扰。效果?CI 快照测试从 6 分钟降到了 1 分钟以内。
3.6 避坑合集
嗯,既然说到了实务,我再分享几个自己趟过的坑:
- Shot 的图片对比默认是像素级的——一点点反走样差异就会导致失败。我曾用
compareScreenshot(composeTestRule, "golden", tolerance = 0.02)加个容差才解决。 - Paparazzi 在 CI 上渲染字体缺失——记得在测试模块添加
assets/fonts,或者用paparazzi.fontScale控制。 - Roborazzi 的图片过大——默认是 PNG,动辄几 MB。开启
roborazzi.compressFormat = "webp"可以大幅缩小。 - 所有库都有的通病——快照测试生成的 golden 文件一旦需要更新,要小心版本冲突。我团队规定:golden 文件必须和代码一起提交到 Git,且每次更新必须附带测试截图对比。
最后说一句:快照测试不是银弹。它最适合回归验证 UI 是否意外改变。真正严格的功能逻辑,还是要靠单元测试和集成测试。嗯,把合适的工具用在合适的地方——这就是我的原则。