LoginSignup
1

More than 1 year has passed since last update.

Flutterで始めるGoldenTest

Last updated at Posted at 2022-12-21

この記事は MIXI DEVELOPERS Advent Calendar 2022 21日目の記事です。
この記事ではFlutterのテストについて、テスト手法の1つであるGoldenTestをどのように実装するかご紹介したいと思います。

GoldenTestとは

そもそもGoldenTestとは何なのか簡単に説明します。
GoldenTestとは「過去に実行したテストの結果を保存し、再度テストを実行する際にそのファイルと同じ結果になるかどうかをチェックするテスト」です。
RegressionTestの手法の1つで、Flutterとりわけクライアントサイドの開発においては、UIが予期せず変更されていないかを確かめるテストとなります。
※RegressionTestは回帰テストとも言い、プログラムの一部を変更したことで他の箇所に不具合が出ていないかを確認するためのテストです。

今回はflutter createで作成できるアプリを例にしてどのようにテストするかご紹介したいと思います。

下準備

テスト対象にするアプリを作成

今回使用するFlutterのバージョンは以下にします。

$ flutter --version
Flutter 3.3.6 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 6928314d50 (5 weeks ago) • 2022-10-25 16:34:41 -0400
Engine • revision 3ad69d7be3
Tools • Dart 2.18.2 • DevTools 2.15.0

最初に雛形となるアプリを作成します。

$ flutter create flutter_golden_test

flutter runを実行するとこのようなアプリが起動できます。
sample app

GoldenTestを組み込む

GoldenTestのパッケージ「golden-toolkit」を追加します。
※余談ですがgolden_toolkitはebayで開発しているOSSのようです。

$ flutter pub add golden_toolkit

pubspec.ymlに以下のように依存関係が追加されます。

dependencies:
  flutter:
    sdk: flutter

  〜中略〜  

  golden_toolkit: ^0.13.0

次にプロジェクト直下にdart_test.yamlを追加して以下のように記述してGoldenTestの設定を追加します。

tags:
  golden:

テストを書いてみる

先ほど作成したアプリのテストを実際に書いていきます。

testディレクトリにmy_app_golden_test.dartを追加して以下のように記述します。※ファイル名は何でも良いです

import 'package:flutter/material.dart';
import 'package:flutter_golden_test/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';

void main() {
  // テスト対象のウィジェット
  const targetWidget = MyApp();
  // テスト対象の画面サイズ
  const targetSize = Size(392, 813);

  testGoldens(
    'my_app',
    (WidgetTester tester) async {
      // 指定したウィジェットを指定したサイズでビルド
      await tester.pumpWidgetBuilder(targetWidget, surfaceSize: targetSize);
      // 保存しているスクリーンショットとUIが一致するか検証
      await screenMatchesGolden(tester, 'my_app');
    },
  );
}

以下のコマンドを実行します。

$ flutter test --update-goldens

するとtestディレクトリ以下に./goldens/my_app.pngが生成されます。
こちらが冒頭に記載した「過去に実行したテストの結果」になります。
my_app

このままだとフォントやアイコンが読み込めていないので、読み込み処理を追加します。
testディレクトリ直下にflutter_test_config.dartを追加し以下のように記述します。

import 'dart:async';

import 'package:golden_toolkit/golden_toolkit.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  await loadAppFonts();
  return testMain();
}

再びflutter test --update-goldensを実行すると画像ファイルが更新されます。
先ほどマスクされていた文字やアイコンなどが表示されるようになります。
my_app

また、このままだとサイズを指定してテストを行う形なので、ページ全体のテストとしては不適感があります。
そこでテストを以下のように変更します。

import 'package:flutter_golden_test/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';

void main() {
  const targetWidget = MyApp();

  testGoldens(
    'my_app',
    (WidgetTester tester) async {
      final builder = DeviceBuilder()
        ..overrideDevicesForAllScenarios(
          devices: [
            Device.phone,
            Device.iphone11,
          ],
        )
        ..addScenario(
          widget: targetWidget,
          name: 'my_app',
        );
      await tester.pumpDeviceBuilder(builder);
      await screenMatchesGolden(tester, 'my_app');
    },
  );
}

golden_toolkitのDeviceというクラスを指定してテストを行う形に変更しました。
今回はDeviceクラスに定義されているphoneiphone11を使用していますが、システムの要件に合わせてよしなに変更すると良いと思います。
flutter test --update-goldensを実行すると以下のように./test/goldens/my_app.pngが更新されます。
my_app

UIを変更してみる

テスト結果がわかりやすいようにデバイスではなくサイズを指定したテストで見ていきます。

その1(プライマリーカラーを変更)

プライマリーカラーを青→赤に変更してみます
red

この状態で先程の「過去に実行したテストの結果」を変えずにflutter testを実行してみると以下のようにテストが失敗するようになります。

❯ flutter test
00:01 +0: loading /Users/.../flutter_golden_test/test/my_app_golden_test.dart
00:02 +1: /Users/.../flutter_golden_test/test/my_app_golden_test.dart: my_app
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following assertion was thrown while running async test code:
Golden "goldens/my_app.png": Pixel test failed, 5.53% diff detected.
Failure feedback can be found at
/Users/.../flutter_golden_test/test/failures

When the exception was thrown, this was the stack:
#0      LocalFileComparator.compare (package:flutter_test/src/_goldens_io.dart:101:7)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)
════════════════════════════════════════════════════════════════════════════════════════════════════
00:02 +1 -1: /Users/.../flutter_golden_test/test/my_app_golden_test.dart: my_app [E]
  Test failed. See exception logs above.
  The test description was: my_app


To run this test again: /Users/.../flutter/bin/cache/dart-sdk/bin/dart test /Users/.../flutter_golden_test/test/my_app_golden_test.dart -p vm --plain-name 'my_app'
00:02 +1 -1: Some tests failed.

また、testディレクトリ以下にfailuresディレクトリが生成され、配下に失敗したテストの結果が保存されます。
こちらを参照すると画像ファイルベースでテストの実行結果が確認できます。

my_app_isolatedDiff.png my_app_maskedDiff.png my_app_masterImage.png my_app_testImage.png
my_app_isolatedDiff.png my_app_maskedDiff.png my_app_masterImage.png my_app_testImage.png

flutter test --update-goldensを実行して「過去に実行したテストの結果」を更新した後に、flutter testを実行すると再度テストが通るようになります。

その2(テキストを変更)

数字の上のYou have pushed the button this many times:という文言をchangedという文言に変えてみます。
changed

先程と同様に「過去に実行したテストの結果」を変えずにflutter testを実行すると失敗します。テストを実行して失敗した結果がfailuresディレクトリ以下に保存されます。

my_app_isolatedDiff.png my_app_maskedDiff.png my_app_masterImage.png my_app_testImage.png
my_app_isolatedDiff.png my_app_maskedDiff.png my_app_masterImage.png my_app_testImage.png

その3(アイコンを変更)

次に右下のフロートボタンのアイコンを+からに変えます。
my_app

例によって「過去に実行したテストの結果」を変えないflutter testが失敗するようになり、テストの実行結果ファイルがfailuresディレクトリ以下に保存されます。

my_app_isolatedDiff.png my_app_maskedDiff.png my_app_masterImage.png my_app_testImage.png
my_app_isolatedDiff.png my_app_maskedDiff.png my_app_masterImage.png my_app_testImage.png

まとめ

色や文字、アイコンを変更してテストが通らなくなることを確認しました。
今回は意図的にUIを変更して確認してみましたが、本来は内部のロジックを変更したりリファクタリングを行った際などに効力を発揮するテスト手法になります。意図しない不具合が起きていないかチェックすることが出来るようになり、品質をより強固に保つことができるようになります。
品質担保するのにGoldenTestのみでは不十分だとは思いますが、CIに組み込んで上手く仕組み化出来れば、人では気づけないような微妙な違いなどを自動で検出できるとても有用なツールになるのではないかと思います。
Flutterはテストも手軽に実装することができますが、今回ご紹介したGoldenTestも今回ご紹介したように割と手軽に導入できるのではないかと思います。ぜひ取り入れてみてはいかがでしょうか。

今回使用したコードはgithub上にも公開しておりますので見てみてください。
https://github.com/yukihiro-numata/flutter_golden_test

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1