全网首发:探秘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...

相关推荐
一切皆是定数28 分钟前
Android车载——VehicleHal初始化(Android 11)
android·gitee
一切皆是定数30 分钟前
Android车载——VehicleHal运行流程(Android 11)
android
problc30 分钟前
Android 组件化利器:WMRouter 与 DRouter 的选择与实践
android·java
图王大胜1 小时前
Android SystemUI组件(11)SystemUIVisibility解读
android·framework·systemui·visibility
服装学院的IT男5 小时前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android
Arms2065 小时前
android 全面屏最底部栏沉浸式
android
服装学院的IT男6 小时前
【Android 源码分析】Activity生命周期之onStop-1
android
ChinaDragonDreamer8 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
网络研究院10 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下10 小时前
android navigation 用法详细使用
android