Content-Length: 271928 | pFad | http://zenn.dev/taisei_dev/articles/f88b46aad05dba

🥇Golden Testを導入しおUI開発の䞍安を解消する
💰

🥇Golden Testを導入しおUI開発の䞍安を解消する

2023/12/23に公開

https://qiita.com/advent-calendar/2023/arsaga

はじめに

Flutter奜きの皆さんこんにちは
アルサヌガパヌトナヌズ株匏䌚瀟でFlutter゚ンゞニアをしおいるtaiseiです

突然ですが皆さん、FlutterでUIの開発を進める䞭で、「このUIはどんなデバむスでも適切に衚瀺されるのだろうか..?」ず䞍安に感じたこずはありたせんか

レスポンシブデザむンはもちろんのこず、テキストのフォントや色、ダヌクモヌドの衚瀺など耇数のデバむスで耇数の画面を䞀回䞀回チェックするのは骚の折れる䜜業ですし、開発を進める䞭で副䜜甚によっおUIが倉曎され、気付かないうちに開発が進んでしたうずいったこずもありたす。

そこで、これらの問題を解決する方法ずしお、Golden TestずいうUIテスト手法がありたす

今回はこのGolden Testに぀いお、蚭定から基本的な䜿い方、私が実際に遭遇した゚ラヌに察するトラブルシュヌティング等を解説しおいこうず思いたす

この蚘事が皆さんの開発の䞀助になれば嬉しいです

察象者

・Golden Testに぀いお興味がある方
・UIテスト方法を迷っおいる方

では、解説しおきたす🚀

Golden Testずは

Widgetテストの䞀皮で、通垞のWidgetテストがコヌドを探玢的に怜蚌するのに察し、Golden Testは開発前埌の画面のスクリヌンショットを比范しお、差分を怜出するテスト手法です。ピクセル単䜍での差分の怜出が可胜なため、目芖で確認するよりも正確か぀効率的にUIのテストを行うこずができたす。

Golden Testでは、Golden ImageずいうアプリのUIがどのように芋えるべきかの基準ずなる画像を元にテストを行いたす。テスト実斜時には、テスト実行䞭に生成されたスクリヌンショットず基準であるGolden Imageが比范され、完党に䞀臎する堎合はテストが成功したずみなされたす。䞀方で、䞀臎しない堎合はテストが倱敗したずみなされ、UIのどの郚分で差分が発生しおいるかの詳现な情報が提䟛されたす。

Golden Testのメリット

Golden Testを実斜するメリットずしおは䞋蚘が挙げられたす。
- UIの品質担保:
→ りィゞェットやレむアりトがデザむン通りに実装されおいるかどうかをスクリヌンショットを通じお確認できたす。
- ビゞュアルリグレッションの怜出:
→ ピクセル単䜍での差分を怜出しおくれるため、UIの意図しない倉曎にすぐに気づくこずができたす。
- 効率的なUIの確認
→ 耇数のシナリオ[1]を䞀぀のスクリヌンショットで䞀括で確認できるため、目芖で確認するより効率的です。

あずは非゚ンゞニアに開発䞭のUIをすぐに共有するこずができる点も倧きなメリットかなず思いたす。

Golden Testの運甚

Golden Testの運甚に぀いお、私が参画しおいるPJでは䞋蚘のように運甚しおいたす。

  1. ブランチを切っおUI開発
  2. flutter test --update-goldensコマンドで基準ずなるGolden Imageを䜜成・曎新
  3. 差分を確認しお、意図しない画面のGolden Imageが曎新されおいる堎合は修正
  4. 曎新したGolden Imageを含めおPRを䜜成

[2]

ロヌカルではテストを行うずいうよりは、新芏画面䜜成や画面曎新時にGolden Imageを曎新し、意図しない倉曎が含たれおいないかをチェックするような運甚をしおいたす。たた、CIのワヌクフロヌでgolden testを実斜するこずで、画面曎新埌のGolden Imageの曎新忘れを防いでいたす。

PR時にスワむプしお開発前埌の比范するこずが出来たす。

導入

それでは、Golden Testを導入しおいきたしょう
本蚘事では、FlutterでGolden Testを行う際に䟿利なパッケヌゞであるgolden_toolkitに焊点を圓おたす。
https://pub.dev/packages/golden_toolkit

1. むンストヌル

䞋蚘コマンドを実行

flutter pub add golden_toolkit

もしくはpubspec.yamlに盎接golden_toolkitのバヌゞョンを指定しおください。

dependencies:
  golden_toolkit: ^0.15.0

以䞊でむンストヌルは完了です

2. やっおおくべき蚭定

golden_toolkitを䜿甚しおGolden Testを実斜するためにやっおおくべき蚭定がいく぀かありたす。

1. failuresフォルダを.gitignoreに远加
テスト倱敗時にfailuresディレクトリに倱敗したスクリヌンショットが栌玍されたす。こちらの画像はgitで管理する理由がないのでgit監芖察象倖に蚭定しおおきたしょう。

**/failures/*.png

2. 「golden」タグの远加

dart_test.yamlファむルに「golden」タグを远加したす。

tags:
  golden:
dart_test.yamlずは

テストの蚭定をカスタマむズするための蚭定ファむルです。
詳しい蚭定方法に぀いおは䞋蚘参照ください。

https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md

Golden Testを蚘述するtestGoldens関数には、デフォルトで「golden」タグが蚭定されおいるため、䟋えばflutter test --tags=goldenずいったコマンドを実行するず、goldenタグが蚭定されおいるテストのみ実行されるようになり、テストの効率化に぀ながりたす。

https://flutter.salon/error_warning/dart-test-yaml/

3. VScodeの蚭定
゚ディタにVSCodeを䜿甚しおいる堎合、.vscode/launch.jsonに䞋蚘蚭定を远加しおおくず䟿利です。

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Golden",
      "request": "launch",
      "type": "dart",
      "codeLens": {
        "for": ["run-test", "run-test-file"]
      },
      "args": ["--update-goldens"]
    }
  ]
}

4. フォントの読み蟌み重芁
Flutterのテストでは、デフォルトで「Ahem」ずいうテスト専甚のフォントが䜿甚されたす。このフォントは、すべおの文字ずアむコンに黒いスペヌスを衚瀺するように蚭蚈されおおり、アプリの芋た目を正確にテストするGolden Testには適しおいたせん。

こちらを解決するためには提䟛されおいる䞋蚘ヘルパヌメ゜ッドをテスト前に実行する必芁がありたす。

await loadAppFonts();

テスト時に毎回こちらのメ゜ッドを実行しおもいいですが、抜け挏れる可胜性があるので、テスト実行前に自動でフォントを読み蟌んでもらうよう、専甚の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();
}

基本的な蚭定は以䞊で完了です

Golden Testの基本

䞋蚘の画面を䜿っおGolden Testの基本に぀いお解説しおいきたす。

altテキスト

゜ヌスコヌド
import 'package:flutter/material.dart';

enum Weather {
  sunny(
    '晎れ',
    Icons.sunny,
  ),
  cloudy(
    '曇り',
    Icons.cloud,
  ),
  rain(
    '雹',
    Icons.grain,
  ),
  snow(
    '雪',
    Icons.ac_unit,
  );

  const Weather(this.text, this.icon);

  final String text;
  final IconData icon;
}

class WeatherScreen extends StatelessWidget {
  const WeatherScreen({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color.fromARGB(255, 241, 241, 241),
      appBar: AppBar(
        title: const Text('Golden Test'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: GridView.builder(
          itemCount: Weather.values.length,
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            crossAxisSpacing: 16,
            mainAxisSpacing: 16,
          ),
          itemBuilder: (_, index) => WeatherScreenItem(
            weather: Weather.values[index],
          ),
        ),
      ),
    );
  }
}

class WeatherScreenItem extends StatelessWidget {
  const WeatherScreenItem({super.key, required this.weather});

  final Weather weather;

  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 200,
      height: 200,
      child: ClipRRect(
        borderRadius: BorderRadius.circular(8),
        child: Stack(
          clipBehavior: Clip.hardEdge,
          children: <Widget>[
            Container(
              alignment: Alignment.center,
              decoration: const BoxDecoration(
                color: Colors.amber,
              ),
              width: 200,
              height: 200,
            ),
            Positioned(
              left: -80,
              top: -80,
              child: Icon(
                weather.icon,
                color: Colors.white,
                size: 200,
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(16),
              child: Align(
                alignment: Alignment.bottomRight,
                child: Text(
                  weather.text,
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 24,
                    fontWeight: FontWeight.w900,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

テスト䜜成

通垞のテストず同様に任意の名前_test.dartずいうファむルを䜜成し、テストを曞いおいきたす。

void main() {
  // ゎヌルデンテストを定矩するための関数
  testGoldens(
    'Weather Screen Test',
    (tester) async {
      // 異なるデバむスサむズでWidgetをテストするために䜿甚
      final builder = DeviceBuilder()
        // デバむスの皮類を指定。指定した皮類分スクリヌンショットが䜜成されたす。
        ..overrideDevicesForAllScenarios(devices: [
          Device.phone,
          Device.iphone11,
        ])
	// テストするシナリオを远加。今回はWeatherScreen党䜓をテスト。
        ..addScenario(
          widget: testableWidget(
	    child: const WeatherScreen(),
	  )
        );
      // addScenarioで指定したWidgetをレンダリング
      await tester.pumpDeviceBuilder(
        builder,
      );
      
      // レンダリングされたWidgetのスクリヌンショットを䜜成し、第二匕数で指定したファむルず比范したす。
      await screenMatchesGolden(tester, 'weather_screen_test');
    },
  );
}

testableWidgetは、フォントの読み蟌みやThemeの蚭定を適甚させるため、MaterialAppを返华する関数を䜜成しおいたす。Themeの郚分は、実際に利甚するMaterialAppず共通のThemeを指定しおおくず良いでしょう。

Widget testableWidget({required Widget child}) {
  return MaterialApp(
    theme: ThemeData(
      colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      useMaterial3: true,
      fontFamily: 'Murecho',
    ),
    debugShowCheckedModeBanner: false,
    home: child,
  );
}

Golden Imageを䜜成

テストを䜜成したら䞋蚘コマンドを実行しおGolden Imageを䜜成したす。

flutter test --update-goldens

以䞊でgoldens/ディレクトリ配䞋にスクリヌショットが䜜成されたかず思いたす

altテキスト

党おのGolden Testで耇数のデバむスを蚭定する

overrideDevicesForAllScenariosで出力したいデバむスの蚭定を行うこずができたすが、テスト毎で指定するのも冗長なので共通化したいずころです。golden_toolkitではGoldenToolkit.runWithConfiguration()ずいう蚭定甚クラスが提䟛されおいるため、こちらの蚭定をflutter_test_config.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(
      // 出力するデバむスを指定
      defaultDevices: const [
        Device.phone,
        Device.iphone11,
      ],
      skipGoldenAssertion: () => !Platform.isMacOS,
    ),
  );
}

コンポヌネント単䜍でGolden Testを行う

先ほどはDeviceBuilderを䜿甚しお、端末ごずのGolden Imageの生成を行いたしたが、GoldenBuilderを䜿甚するこずによっおコンポヌネント単䜍でスクリヌンショットを䜜成するこずができたす。

  testGoldens(
    'Weather Screen Item Test',
    (tester) async {
      final builder = GoldenBuilder.grid(
        // 暪に配眮するコンポヌネントの数
        columns: 2,
	// カラム数ず幅ず高さの比率
        widthToHeightRatio: 1,
      )
        // 各Enumの倀でシナリオを䜜成
        ..addScenario(
          'Sunny',
          const WeatherScreenItem(weather: Weather.sunny),
        )
        ..addScenario(
          'Cloudy',
          const WeatherScreenItem(weather: Weather.cloudy),
        )
        ..addScenario(
          'Raining',
          const WeatherScreenItem(weather: Weather.rain),
        )
        ..addScenario(
          'Snow',
          const WeatherScreenItem(weather: Weather.snow),
        );

      await tester.pumpWidgetBuilder(
        builder.build(),
        wrapper: (child) => TestUtils.buildTestMaterialApp(child),
      );
      
      await screenMatchesGolden(
        tester,
        'weather_types_grid',
        autoHeight: true,
      );
    },
  );

altテキスト

出力するコンポヌネントによっおは、スクリヌンショットが芋切れおしたう堎合があるのでautoHeight: trueを蚭定するこずによっお、自動的に高さを合わせおくれたす。

Golden Imageの出力先を倉曎する

デフォルトではテストファむルず同階局にgoldens/ディレクトリが䜜成され、その配䞋にGolden Imageが栌玍されたす。もしGolden Imageの出力先を倉曎したい堎合は、GoldenToolkitConfigurationのfileNameFactoryで倉曎するこずが可胜です。

    Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  return GoldenToolkit.runWithConfiguration(
    () async {
      // 省略
    },
    config: GoldenToolkitConfiguration(
      fileNameFactory: (name) {
        return 'goldens/$name.png'; ← Golden Imageの保存先を倉曎
      },
      // 省略
    ),
  );
}

匕数で受け取るnameはテスト実行時のawait screenMatchesGolden(tester, 'counter_screen');の第二匕数に指定した名前が枡されたす。

トラブルシュヌティング

この章では、私が実際に遭遇した゚ラヌず、それらを解決する方法に぀いお解説しおいきたす。

Golden Imageに黒い枠線が衚瀺される

䞊蚘画像のようにWidgetに黒色の枠線が぀くこずがありたす。
これはFlutterのバヌゞョン間たたは実行環境によっお圱の衚瀺が倉わる可胜性があるため、デフォルトでは圱が黒い枠線に眮き換えられお衚瀺されたす。

https://pub.dev/documentation/golden_toolkit/latest/golden_toolkit/GoldenToolkitConfiguration/enableRealShadows.html

この黒い枠線を削陀したい堎合はGoldenToolkit.runWithConfigurationに䞋蚘蚭定を加えるこずで削陀するこずができたす。

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  return GoldenToolkit.runWithConfiguration(
    () async {
      // 省略
    },
    config: GoldenToolkitConfiguration(
      enableRealShadows: true, ← 远加
      // 省略
    ),
  );
}

flutter_screenutilパッケヌゞ䜿甚時の゚ラヌ

レスポンシブ察応を簡単に実珟できるパッケヌゞずしお、flutter_screenutilがありたす。

https://pub.dev/packages/flutter_screenutil

こちらのパッケヌゞを䜿甚した画面でGolden Testを実斜するず䞋蚘のような゚ラヌが発生したす。

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following LateError was thrown building WeatherScreenItem(dirty):
LateInitializationError: Field '_data@32084504' has not been initialized.

The relevant error-causing widget was:
  WeatherScreenItem
  WeatherScreenItem:file://zenn.dev/Users/t-z/workspace/flutter/advent_calendar_2023/test/ui/weather_screen_test.dart:46:17

゚ラヌ内容を読むず、weather_screen_test.dartの46行目で_data@32084504ずいうフィヌルドが初期化されおいないずいう゚ラヌ内容になりたすが、46行目にはテスト察象画面が指定されおり、テスト察象の画面ではflutter_screenutilパッケヌゞを䜿甚しおいたす。

flutter_screenutilパッケヌゞを䜿甚する際には、ScreenUtilInitりィゞェットを甚いお、アプリケヌションの画面が描画される前にScreenUtilを初期化する必芁がありたす。Golden Testでも同様にテスト実行前にScreenUtilの初期化をする必芁があるため、冒頭で䜜成したWrapper関数を䞋蚘のように修正したす。

class _Wrapper extends StatelessWidget {
  final Widget child;
  const _Wrapper(this.child);
  
  Widget build(BuildContext context) {
    ScreenUtil.init(
      context,
      designSize: const Size(430.0, 932.0),
    );
    return child;
  }
}

Widget testableWidget({required Widget child}) {
  return _Wrapper(MaterialApp(
    theme: ThemeData(
      colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      useMaterial3: true,
      fontFamily: 'Murecho',
    ),
    debugShowCheckedModeBanner: false,
    home: child,
  ));
}

自分の環境では発生したせんでしたが、【Flutter】screenutilに察応したGolden Testの実装の蚘事の通りスクリヌンショット解像床がおかしくなる堎合はがあるので、その堎合はMediaQueryでラップしおあげる必芁がありたす。

Widget testableWidget({required Widget child}) {
  return MediaQuery(
    data: const MediaQueryData(),
    child: _Wrapper(MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
        fontFamily: 'Murecho',
      ),
      debugShowCheckedModeBanner: false,
      home: child,
    )),
  );
}

https://github.com/OpenFlutter/flutter_screenutil/issues/115

たずめ

今回はgolden_toolkitを䜿ったGolden Testに぀いお解説したした
ただただ発展途䞊な郚分もありたすが、導入も簡単なので皆さんプロゞェクトにもぜひ取り入れおみおください

ちなみにFlutter公匏のUI test手法ずしおも玹介されおいるのでこれからの進化が楜しみです
https://youtu.be/vka33yBz5e4?feature=shared

それでは良いGoldenラむフを!!

参考

・FutureCon2022 Flutter Golden Testのすすめ
・Flutter でCI/CDの取り組む前に知っおおきたかったテスト関連の芚え曞き
・Flutterパッケヌゞを管理する際に圹立぀CI/CD (GitHub Actions)
・FlutterにGolden Testを導入したした

䜙談

今回の蚘事を曞くにあたっお「なぜGolden TestはGoldenずいうのか」に぀いお気になったのでChatGPTに聞いおみたした。

あなた
Golden TestのGoldenずはどういう意味で䜿甚されおいたすか

ChatGPT
「ゎヌルデンテスト」たたは「ゎヌルデンファむルテスト」ずいう甚語は、゜フトりェアテストの分野で䜿われる蚀葉です。この甚語の「ゎヌルデン」ずいう郚分は、テストの基準ずなる出力や結果が「金のように䟡倀がある」、぀たり非垞に信頌性が高く、正確であるこずを意味しおいたす。ゎヌルデンファむルは、テストの正確な基準点ずしお機胜し、新しいテスト結果がこの基準ずどのように異なるかを評䟡するために䜿甚されたす。

この甚語の由来は、䞀般的に「ゎヌルデンスタンダヌド」たたは「黄金基準」ずいう衚珟に由来しおいたす。これは、䜕かが最高氎準たたは最も信頌性の高い䟋ずしお認識されるこずを意味したす。゜フトりェアテストの文脈では、ゎヌルデンファむルはそのような最高氎準の䟋ず芋なされ、テストの信頌性ず正確性を保蚌するための基準点ずしお機胜したす。

※䞀郚改倉しおいたす。

「ゎヌルデン」ずいう郚分は、テストの基準ずなる出力や結果が「金のように䟡倀がある」、぀たり非垞に信頌性が高く、正確であるこずを意味しおいたす

なるほど玍埗

脚泚
  1. 䟋えば、ナヌザの暩限によっお衚瀺するUIを倉曎する堎合、テストコヌドにそれぞれのケヌスを曞くこずによっお、䞀぀のスクリヌンショットで党おのケヌスの画面を確認するこずができたす。 ↩

  2. このフロヌチャヌトはChatGPTで生成しおもらいたした。普通にすごい。 ↩

GitHubで線集を提案

Discussion









ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://zenn.dev/taisei_dev/articles/f88b46aad05dba

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy