全网首发:探秘Flutter UI测试-Golden Test

前言

UI测试分为两种类型:行为测试和视觉测试。行为测试主要关注用户在操作UI组件时,UI的变化是否正确。而视觉测试则着重于验证UI的外观是否符合预期。通过比较应用的实际截图与预期结果,视觉测试能够确保UI在不同平台、不同设备上的一致性和美观性。

在实际项目开发中,经常会对现有的UI进行功能扩展或修改。例如,在消息通知按钮上添加红点提示功能。这种改动可能会导致原有的UI出现问题,比如间距不一致或字体大小不对等。而验证是否出现该问题需要不少的成本,如果只是细小的差别,如2,3个像素的差别,那肉眼很难鉴别。这时就需要Golden test了。Golden Test正是一种视觉测试工具,它能够帮助我们确认新功能的引入是否影响了UI的整体外观,从而确保应用的质量和用户体验。

什么是Golden Test

Golden Test 是一种通过比较图片来测试 UI 的技术。它首先创建一个标准图片,表示 UI 正确状态。然后,当开发者对 UI 进行修改时,Golden Test 会自动捕获新的 UI 状态,并与标准图片进行比较。通过比较这两张图片,Golden Test 可以发现 UI 的变化,并生成测试报告。因此,Golden Test 也被称为快照测试,它以像素级别的准确性进行比较,即使是微小的像素差异也能被捕捉到。这使得 Golden Test 在视觉测试方面非常可靠,开发者可以放心地使用它来确保应用在不同情况下的一致性和美观性。

Golden Test 在许多平台中都有对应的工具,如Android中有Paparazzi,IOS中有SnapshotTesting, Web中有snapshot-diff 等,不过它们大部分都不是平台官方的工具而是以第三方库的形式存在。而Flutter是少有的提供了Golden Test官方支持的平台。接下来我将讲述如何在flutter中使用Golden Test

如何使用Golden test

在Flutter 项目中的test文件夹里,创建一个golden_widget_test.dart的代码文件。代码如下:

dart 复制代码
void main() {
  testWidgets('Golden test', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());
    await expectLater(find.byType(MyApp),
                      matchesGoldenFile('circle_button.png'));
  });
}

正如你所看到的,我通过tester.pumpWidget方法渲染一个My App widget ,该widget包含CircleButton

dart 复制代码
await tester.pumpWidget(MyApp());

然后expectLater 验证该widget是否与基准图片(main.png)一致。

dart 复制代码
    await expectLater(find.byType(MyApp),
                      matchesGoldenFile('circle_button.png'));

一开始我们并没有基准图片,所以假定当前Widget的UI是正确的,即通过UI设计师验证过的,则通过运行该命令,生成基准图片。

shell 复制代码
flutter test --update-goldens

运行该命令后,test文件夹出现了circle_button.png ,这就是基准图片。不过这基准图片似乎不太对,对比运行在真机上按钮的字体变成了矩形。

circle_button.png

真机运行图片

出现这种情况的原因是不同的设备上运行的字体都是不同的,为了保证Golden Test在不同的设备上运行时生成的图片一致,所以将字体都渲染成了矩形,但是这样我们就无法通过查看图片来验证UI显示是否符合预期了,而且在运行在Ci/Cd机子的设备基本都是统一的。所以并不需要将此渲染成矩形。

这里我通过引入三方库golden_toolkit来解决此问题。golden_toolkit也能帮助开发者更加易于编写Golden Test。它的优势这里不作过多赘述。

引入golden_toolkit后,test文件夹下新建flutter_test_config.dart 。文件代码如下:

dart 复制代码
import 'dart:async';
import 'dart:io';

import 'package:golden_toolkit/golden_toolkit.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  return GoldenToolkit.runWithConfiguration(
    () async {
      await loadAppFonts();
      await testMain();
    },
    config: GoldenToolkitConfiguration(
      // Currently, goldens are not generated/validated in CI for this repo. We have settled on the goldens for this package
      // being captured/validated by developers running on MacOSX. We may revisit this in the future if there is a reason to invest
      // in more sophistication
      skipGoldenAssertion: () => !Platform.isMacOS,
    ),
  );
}

然后下载第三方字体文件进行导入,这是因为golden_toolkit自带的字体只支持渲染英文。然后在pubspec.yaml 中的flutter 标签下添加如下字符串。

在实际项目中直接导入字体文件会使包体积增加一些无用文件。而字体暂时不能像dev_dependencies 一样添加只在开发中引用的库。github.com/flutter/flu...

这里我建议在项目中文件夹里再建一个flutter example,在该example里写Golden Test

这些操作后开始编写基于golden_toolkitGolden Test。新建circle_button_test。在test文件夹中

dart 复制代码
import 'package:flutter_golden_test_example/circle_button.dart';
import 'package:golden_toolkit/golden_toolkit.dart';

void main() {
  testGoldens('Circle button', (tester) async {
    final builder = GoldenBuilder.grid(
      columns: 3,
      widthToHeightRatio: 1,
    )
      ..addScenario(
        'alarm',
        const CircleButton(icon: 'assets/images/ic_alarm.svg', text: '时钟'),
      )
      ..addScenario(
        'notification',
        const CircleButton(
            icon: 'assets/images/ic_notification.svg', text: '通知'),
      );
    await tester.pumpWidgetBuilder(builder.build());
    await screenMatchesGolden(tester, 'circle_button');
  });
}

然后运行flutter test --update-goldens 就会生成如下文件。这就是基准图片。

然后我们模拟下对该UI在新增功能时出现的破坏性变更导致文本字体颜色错误。

此时运行flutter test 。则会报错。图中圈出来的信息则告知了当前UI代码输出的图和基准图的差异比例。并新增了failures文件夹,里面的文件分别表示的作用是:

文件后缀名 作用
xx_isolatedDiff 标记两者差异的图片
xx_maskedDiff 标记两者差异的图片
xx_masterImage 基准图片
xx_testImage 被测试图片(当前UI代码生成的图片)

其中isolatedDiff和maskedDiff的差异,直接看图。

circle_button_isolatedDiff.png

circle_button_maskedDiff.png

这些Diff的图片会圈出UI差异的地方。

最后记得还需要将 failures 文件夹添加在 .gitignore

相关链接

源代码仓库: github.com/drown0315/f...

相关推荐
思盛iOS签名上架9 小时前
App苹果签名有什么作用?
macos·ios·cocoa
alexhilton10 小时前
让架构边界变成可执行的测试
android·kotlin·android jetpack
hai_android11 小时前
SendChannel 与 ReceiveChannel 通信机制
android
CocoaKier12 小时前
Facebook广告后台收不到Adjust回传数据,我们排查了一个月终于找到了根因
ios·facebook
数据治理自习室13 小时前
AI 应用评测体系(GraphRAG)
android·大数据·人工智能·kotlin
爱笑鱼14 小时前
Android 系统启动机制(五):system_server 是 init 启动的,还是 Zygote fork 出来的?
android
智购科技无人售货机工厂14 小时前
2026自动售货机防拆机物理安全设计:从安全螺丝到结构互锁的工程实践~YH
android·网络·驱动开发·python·单片机·安全·云原生
开开心心就好14 小时前
电子教鞭工具支持画框写字插图片功能齐全
android·开发语言·前端·javascript·人工智能·pdf·html
Dovis(誓平步青云)15 小时前
拍视频前先把镜头想清楚:做一个分镜取景辅助器
android·java·服务器·javascript·人工智能
峥嵘life16 小时前
Android16 系统 APEX 模块说明
android·大数据·开发语言