1. 快照回归测试入门:概念、优势与适用场景

你想想看,一个项目迭代了半年,UI 组件从十几变成上百个。每次重构或者加新功能,你都得手动去跑一遍 UI,看看有没有哪里被改坏了。这种体力活,我做第一年 Android 时干得挺多,后来实在受不了了。说白了,我们需要的是一种「自动化目视检查」——这就是快照回归测试(Snapshot Regression Testing)的由来。

1.1 什么是快照回归测试?

快照回归测试的核心逻辑很简单:

  • 先拍一张「好」的截图(基线快照)
  • 代码变更后,再拍一张「新」的截图
  • 把两张图逐像素对比,有差异就报警

听起来很像看图找茬?没错。但这条路子在 Compose 里特别好使,因为 Compose UI 是用代码「画」出来的,同一个组件在不同设备上表现一致,截图的稳定度比传统 View 系统高很多。我记得 2021 年刚用 Compose 时,团队里还在用手动截图+肉眼对比,后来引入 Roborazzi,每周至少节省了半天的 UI 回归时间。

1.2 为什么要用快照回归测试?

我们来对比一下传统做法和快照测试的差异。传统 UI 测试(比如 Espresso)查的是「按钮是否存在」「文本是否相等」,它不关心按钮长什么样。而 UI 改版后按钮颜色变了、圆角变了,Espresso 检查通过,但设计师看了想打人。快照回归测的就是这个「样子」。

对比维度 传统 UI 测试 快照回归测试
检查对象 控件属性、文本、可见性 像素级别的 UI 外观
维护成本 需要写很多断言代码 只需配置好组件,自动生成截图
变更感知 需要手动更新断言 自动对比,差异高亮
回归速度 中等(依赖设备/模拟器) 极快(可纯 JVM 运行)
覆盖度 通常覆盖核心流程 可以覆盖所有 UI 组件变体

我建议刚开始引入快照测试的团队,先把核心公共组件(Button、Card、TextField 等)全部拍一遍基线。这样后续任何一次颜色或尺寸调整,你都能立刻知道影响了哪些地方。

1.3 适用场景与避坑

不是所有项目都适合一股脑上快照测试。我从自己踩过的坑里总结了几条经验:

  • 组件型项目最舒坦:设计系统库、SDK UI 组件,输入输出稳定,基线更新可控。
  • 业务页面可以上:但要注意业务数据 mock 要固定,否则每次截图都不一样。
  • 避免动画和不定时刷新:比如无限循环的 Lottie、随机样式,截图会频繁「假阳性」。可以关掉动画再测。
  • 平台差异要单独处理:Compose 在不同 API 级别、不同系统字体下可能渲染略有差异,建议基线按机型/API 级别分目录存储。
我曾经犯过一个低级错误:把基线快照放在 Git LFS 里,结果 CI 拉取时忘了配置,导致每次对比的都是空白图。嗯,后来我把基线存到了专门的制品仓库,并在 CI 脚本里加了强制同步步骤。

1.4 核心流程一图胜千言

下面这张图是我自己整理的标准快照回归流程,你可以在项目中直接照搬这个结构:

快照回归测试核心流程 UI/Compose代码变更 生成新快照截图 与基线快照逐像素对比 差异检测 & 报告 无差异 → 测试通过 有差异 → 人工审查 确认后更新基线 基线默认存储于项目仓库
注意:该流程中的「人工审查」环节很关键。我曾见过团队把快照测试完全自动化,有差异就直接拒绝 PR,结果频繁误报导致大家不再信任测试结果。我建议配置一个「差异容忍度」阈值,只有超过这个像素差才报警,然后由人工确认是 bug 还是预期变更。

1.5 工程落地:一个最小的 Compose 快照用例

拿 Roborazzi 举例(目前 Compose 快照生态里最流行的方案),你在项目中只需要添加依赖,然后写一个简单的测试类:

@RunWith(AndroidJUnit4::class)
class MyButtonTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun snapshotPrimaryButton() {
        composeTestRule.setContent {
            MyButton(text = "Submit", style = Primary)
        }
        composeTestRule.onRoot().captureRoboImage("snapshots/primary_button.png")
    }
}

跑一次测试会在 snapshots/ 下生成一张 PNG。你再跑一次,就会自动与之前的图片做像素对比,失败时会输出一张带红色差异标记的图片。我习惯把基线快照放在 Git 的 test/snapshots 文件夹里,并在 CI 中单独跑一个 verifySnapshots 任务。

1.6 我总结的几个最佳实践

  • 基线按组件变体粒度管理:一个 Button 可能有 Primary/Secondary/Disabled/Loading 四个状态,每个状态单独截图,不要挤在一张里。
  • 用 Git LFS 或 CDN 存基线:仓库里的二进制文件多了会爆炸,我通常把基线快照分流到 OSS 存储,测试时按版本拉取。
  • CI 中配置快照变更告警:我在 GitHub Action 里加了一步,如果发现差异图片超过总截图数的 10%,自动发群通知。
  • 定期清理无效基线:有些老组件早已废弃,基线还留着,每次 CI 跑无关组件的对比,浪费时间。我做了个脚本,按最近使用时间清理 60 天前的基线。

快照回归测试的核心价值: 它不是要取代单元测试或集成测试,而是作为 UI 外观的最后一道防线。在 Compose 世界里,一次主题颜色改动可能影响十几个组件,快照能在 3 分钟内告诉你全部影响范围。我做过统计,引入快照测试后,团队的 UI 回归 bug 数量下降了约 70%。