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 (社区) Google
学习成本 低 中 低 极低

3.6 选择决策树

光看表你可能还是纠结,别急,我画了一张决策图。你顺着箭头走,答案基本就出来了。

快照工具选择决策树 评估项目需求 需要真实 设备渲染? 是 Shot 依赖模拟器/设备 否 追求极速 反馈? 是 Roborazzi 基于 Robolectric 否 需要官方 长期支持? 是 SnapshotContainer Google 官方方案 否 Paparazzi 纯本地渲染 💡 提示: 如果项目同时包含 View 和 Compose,建议先用 Roborazzi 做快速验证,再配合 Shot 做真机巡检。

3.7 我的选择建议与避坑指南

决策树给了一个大方向,但实际落地还有不少细节。我根据自己的踩坑经验,给你几条实在的建议。

✅ 快速推荐:
· 纯 Compose 项目 + 追求速度 → Roborazzi 或 Paparazzi
· 需要真机像素级对比 → Shot
· 做 Jetpack 库或 Compose 工具链 → SnapshotContainer
· 大型项目混合 View/Compose → Roborazzi + Shot 组合使用
💡 我的个人习惯:在模块的单元测试阶段用 Roborazzi 跑常规截图,PR 提交前在 CI 上用 Shot 做一次全量的真机巡检。两者互补,既保速度又保真实度。

我吃过一个亏:有次在 CI 上只配了 Shot,每次提交要等 20 多分钟跑截图,开发效率直线下降。后来改成 Roborazzi 做日常 + Shot 做 nightly,问题才解决。你想想看,如果一开始就规划好工具的分层使用,这些时间完全可以省下来。

另一个容易踩的坑是图片存储。快照测试跑多了,Git 仓库里的图片文件会迅速膨胀。我曾经遇到过仓库体积从 200MB 暴涨到 2GB 的情况。建议把截图目录添加到 .gitignore,只在 CI 上保留基线版本,或者用云存储管理。

⚠️ 注意:不要在同一模块中混用多个快照工具。不同工具的渲染管线不同,同一组件在不同工具下