🐥

Flutter でgolden_toolkitを使ってVRT(Visual Regression Test)やってみた

2023/08/15に公開

初めに

Golden Testというものがある
これは一言でいえばデグレードテストのことで、過去にテストした時の出力ファイルと今テストした時の出力ファイルが同じ結果になっているか確認するテスト手法である
またVisual Regression Test(VRT)は、フロントエンドで使われるテスト手法で、アプリケーションの画面を画像として保存してその比較を行うGolden Testの手法の1つである
私も昔はテスト結果のスクショを撮りまくって比較を行い、デグレがないことを顧客に説明していたことを思い出す

今回はこれをFlutterで簡単に行うことができる"golden_toolkit"というものがあるらしいので使ってみる

前提条件

  • Androidエミュレータ、iOSシミュレータ、または実機でFlutterを実行できること

実装環境

  • Android Studio 2022.3.1
  • Flutter 3.10 (Dart 3.0.0)

インストールしたパッケージ

  • golden_toolkit: ^0.15.0

テスト対象の画面

Golden Testを最小限やってみる

  1. golden_toolkitをアプリに追加するflutter pub add golden_toolkit
  2. プロジェクトのtestディレクトリに以下のファイルを作成する(ファイル名は任意)
// test/golden_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:image_upload/main.dart';

void main() {
  testGoldens('golden_test', (WidgetTester tester) async {
    await tester.pumpWidgetBuilder(
        MaterialApp(
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: true,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'), // テストしたいWidget
        ),
        surfaceSize: const Size(375, 667)); // 画面サイズ
    await screenMatchesGolden(tester, 'golden_test_sample'); // 出力画像名
  });
}
  1. 以下のテストコマンドを実施して画像を出力する
    flutter test --update-goldens
  2. testディレクトリの中にgoldensというディレクトリが作成され、その中にgolden_test_sample.pngという画像が出力されればおk

文字化けが起きた!

画像を見た通り、文字化けを起こしている。これはgolden_toolkitではAhemというテスト用フォントを使っており、このフォントの場合文字列を■で表示する仕様らしい(このままでも画面を変更すれば差分として出力される)

自動で画面の文言をマスクしてくれるので、画面の文言を残したくないプロジェクトでは重宝するかもしれないが、差分比較したときに何で差分が出ているかちょっと分かりづらいため、文字化けが起きないようにする
色んなサイトを参考にした際に

await loadAppFonts();

を使う方法が多かったが、私のアプリは言語設定などの環境をあまり整理していないためか、上記ソースを入れても英語は表示されるが、日本語は文字化けしてしまった
なので、以下の対応を行った

日本語対応

  1. 好きなフォントをインストールする
    私はNoto_Sans_JPをインストールした。インストール方法はこちらを参考にした
  2. test/golden_test.dartに以下の処理を追加
    先ほどインストールしたフォントを指定する
//日本語フォントの読み込み用メソッド
Future<void> loadJapaneseFont() async {
  TestWidgetsFlutterBinding.ensureInitialized();
  final binary = rootBundle.load('assets/fonts/Noto_Sans_JP/static/NotoSansJP-Thin.ttf');
  final loader = FontLoader('NotoSansJP')..addFont(binary);
  await loader.load();
}
  1. main関数内のtestGoldensの一番最初にawait loadJapaneseFont();を追加する
  2. testGoldens内のMaterialAppのthemeに先ほどインストールしたフォントを指定する fontFamily: 'NotoSansJP',
void main() {
  testGoldens('golden_test', (WidgetTester tester) async {
    // 日本語のフォントを取得
    await loadJapaneseFont();
    await tester.pumpWidgetBuilder(
        MaterialApp(
          theme: ThemeData(
            fontFamily: 'NotoSansJP', // フォントを指定する
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: true,
          ),
          home: const MyHomePage(title: 'Flutter Demo Home Page'), // テストしたいWidget
        ),
        surfaceSize: const Size(375, 667)); // 画面サイズ
    await screenMatchesGolden(tester, 'golden_test_sample'); // 出力画像名
  });
}
  1. 以下のテストコマンドを実施して画像を出力する
    flutter test --update-goldens
  2. golden_test_sample.pngが出力。文字化けが治った!
  3. この画像もGitに一緒にcommitするなりプロジェクトに沿った運用方法で画像比較して意図せぬ変更が入っていないか確認する

おまけ:更に実用的にしたい

以上で画像を出力できたので、Gitや画像の比較ツールを使えば差分が出るが、今の場合1画面にしか対応していないし、画面ごとにテストファイルを書くのは大変である。形骸化しがち
またアプリの場合、スマホの画面サイズによって変な折り返しや見切れが発生しないか確認したい場合もある
なので、以下の処理を追加した

  1. ファイルの先頭に画面の一覧を定義するようにした
//** 画面を定義 **//
const testScreen = {
  'home': MyHomePage(title: 'Flutter Demo Home Page'),
  'data_add': DataAdd(),
  'data_update': DataUpdate(),
  'storage_upload': StorageUpload(),
};
  1. 画面サイズを指定して画面サイズごとにテストする
//** 端末サイズを定義 **//
const screenSize = {
  '390x844': Size(390, 844),
  '375x667': Size(375, 667),
  '375x812': Size(375, 812),
  '414x896': Size(414, 896),
};
  1. main関数内を上記定義を使っていい感じに変更する
testGoldens('golden_test', (WidgetTester tester) async {
  // 日本語のフォントを取得
  await loadJapaneseFont();

  for (var sizeKey in screenSize.keys) {
    for (var screenKey in testScreen.keys) {
      await tester.pumpWidgetBuilder(
          MaterialApp(
            theme: ThemeData(
              fontFamily: 'NotoSansJP', // フォントを指定する
              colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
              useMaterial3: true,
            ),
            home: testScreen[screenKey], // テストしたいWidget
          ),
          surfaceSize: screenSize[sizeKey]!); // 画面サイズ
      await screenMatchesGolden(tester, '$sizeKey/$screenKey'); // 出力画像名
    }
  }
});

これでテストを実行すると、各画面サイズごとの画面の画像を出力できるので一括でテストすることができる
ただ、画像が増えるのでこれをGitにそのままコミットするとプロジェクトによっては怒られるかもなので、どの画面、どの画面サイズを対象にするかは要検討である

まとめ

Webフロントエンドでは、StoryBookを使ってのVRTがメジャーっぽいがそれもやったことないのでどこかでやってみたい
Flutterの場合もStoryBookのようなパッケージがあるのでそれを使ったVRTもあるみたいだが、私が調べたところだとgolden_toolkitを使う方がメジャーなような気がしたのでやってみた

今回は私が実験用に書いているソースの中で導入したので大した画面もないが、実際のプロジェクトで運用しているアプリで導入したときに運用が耐えられるかは、実業務の中で確認していきたい

Discussion