3. Roborazzi 库详解:介绍、集成与第一个快照测试

讲快照测试,就绕不开 Roborazzi。说实话,我第一次接触它是在一个跨团队合作的项目里——团队七八个人同时改 UI,回归测试每次都跑得肝疼。后来我试着把 Roborazzi 塞进流水线,嗯,效果出乎意料得好。

3.1 什么是 Roborazzi?

Roborazzi 是 Dropbox 开源的一个 Android 快照测试工具。它专门为 Compose 和 View 系统设计,底层用 JVM 上的 Robolectric 来渲染,不需要真机或者模拟器。说白了,你在本地跑一条命令,就能拿到 Compose 组件的截图,然后跟基准图做像素级对比。

常见截图测试库的对比:

工具运行环境Compose 支持图像对比上手难度
RoborazziJVM(Robolectric)原生支持像素级低
PaparazziJVM需额外配置像素级中
Screenshot Tests for Android真机/模拟器有限支持模糊对比高

从表格就能看出来,Roborazzi 在 Compose 领域的兼容性和上手成本上都有优势。我自己的项目一开始用的 Paparazzi,后来发现每次改个 padding 都要等模拟器启动,果断切到了 Roborazzi。

3.2 为什么选 Roborazzi?

  • 跑得快: 基于 Robolectric,无需启动模拟器,单条测试几百毫秒搞定。
  • 对比准: 支持像素级 diff,连 1px 的偏移都能报警。
  • 集成简单: 加个依赖、写个 test 类,十分钟就能跑出第一张快照。
  • 报告直观: 自动生成 HTML 报告,左边基准、右边新图、中间 diff,一眼看出哪变了。

这里我要提一句,Roborazzi 不仅支持 Compose,也支持 View 系统的截图。但既然咱们在做 Compose 课程,我后面所有示例都会用 Compose 组件来演示。

3.3 集成到项目中

集成步骤非常简单,咱们一步步来。

第一步:添加插件和依赖(项目级 build.gradle.kts)

// 在根 build.gradle.kts 的 plugins 中
id("io.github.takahirom.roborazzi") version "1.12.0" apply false

// 在 module 级 build.gradle.kts 中
plugins {
    id("io.github.takahirom.roborazzi")
}

dependencies {
    testImplementation("io.github.takahirom.roborazzi:roborazzi:1.12.0")
    testImplementation("io.github.takahirom.roborazzi:roborazzi-compose:1.12.0")
    testImplementation("io.github.takahirom.roborazzi:roborazzi-junit-rule:1.12.0")
}

第二步:配置输出目录(同样在 module 级 build.gradle.kts)

roborazzi {
    // 基准截图存放路径
    baselineDir = "src/test/snapshots/baseline"
    // 新截图和 diff 图输出路径
    outputDir = "build/reports/roborazzi"
}

第三步:编写测试基类(可选)

我个人习惯写一个抽象基类,把 Compose 测试环境统一管理起来:

import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule
import org.junit.rules.TestRule
import io.github.takahirom.roborazzi.RoborazziRule

abstract class ComposeSnapshotTest {

    @get:Rule
    val composeRule = createComposeRule()

    @get:Rule
    val roborazziRule: TestRule = RoborazziRule(
        // 截图规则可以自定义输出文件名
        options = RoborazziRule.Options(
            outputFile = "build/reports/roborazzi/{classSimpleName}/{methodName}.png"
        )
    )

    protected fun setContent(content: @Composable () -> Unit) {
        composeRule.setContent(content)
    }
}
💡 提示: 我建议第一次集成时先不急着改默认配置,跑通默认流程后再调整输出目录和命名规则,避免踩坑。

3.4 编写第一个快照测试

假设咱们有个简单的 Compose 按钮组件:

@Composable
fun MyButton(text: String, onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text(text)
    }
}

写快照测试只需要三步:

  1. 继承咱们的基类(或用 createComposeRule + RoborazziRule)。
  2. 在 setContent 里放入待测试组件。
  3. 调用 RoborazziRule.captureView() 或直接用注解自动截图。

完整代码:

import io.github.takahirom.roborazzi.RoborazziRule
import org.junit.Test

class MyButtonSnapshotTest : ComposeSnapshotTest() {

    @Test
    @RoborazziRule(// 可以加选项,比如 captureType = RoborazziRule.CaptureType.Screenshot
    )
    fun buttonTest() {
        setContent {
            MyButton(text = "Click Me") { }
        }
    }
}

跑测试:

./gradlew testDebugUnitTest

第一次运行会自动生成基准截图(baseline),存放在你配置的 baselineDir 下。第二次运行会比对,如果图片不一致,测试会失败并在 outputDir 生成新的截图和 diff 图。

⚡ 关键点: 第一次跑一定会生成基准图,但不会失败。后续每次跑都会拿新图和基准图做对比。如果 UI 是有意改动,需要手动删除旧的基准图,让测试重新生成。
⚠️ 踩坑记录: 我曾经遇到过测试一直失败,查了半天发现是 Compose 动画导致的——每次渲染时进度不同,截图有微小差异。解决方案是给 Compose 测试环境设置 AnimationClockRule 固定动画帧,或者直接禁用动画。Roborazzi 提供了 RoborazziRule.Options(recordPrerecorded = true) 来处理这类问题,有兴趣的可以查一下文档。

下面这张图能帮你理清 Roborazzi 的整个工作流程:

Roborazzi 工作流程 编写 Compose 测试 Robolectric 渲染组件 生成 PNG 截图 存基准图? 存入 baselineDir 与基准图像素对比 生成 diff 报告 注:虚线代表首次运行流程

图中的核心流程很清楚:测试启动 → Robolectric 渲染 → 截图 → 判断是否需要生成基线 → 与基线对比 → 输出报告。理解了这个流程,后面调试就有的放矢了。