見出し画像

Flutterのgolden testに設定を追加してより便利にテストしよう


はじめに

みなさんお元気ですか。SHIFT DAAE 開発グループ所属のsakuraiです。

前回のブログではFlutterの開発にGolden Testを導入した旨を書きました。
その後の開発でも引き続き利用していますが

  • 変更点の確認が視覚的にわかりやすい

  • テスト結果の確認も通常のWidgetテストより簡単かつわかりやすい

と非常に役立っております。

今回はさらに追加の設定を行うことでより便利に利用できましたので
そのあたりのことについて記載したいと思います。

golden testの設定追加で何ができるようになる?

1.システムで文字表示倍率を変更した場合のレイアウトが確認できる

今回のFlutterを利用した開発ではAndroidiOSを対象としています。
どちらもシステム設定で文字のサイズの拡大・縮小が可能です。

この設定で拡大、縮小される倍率について
FlutterではtextScaleFactorというプロパティに反映され、
各テキストに設定されている文字サイズの表示倍率として利用されます。(拡大縮小がない場合は1.0)
参考: https://api.flutter.dev/flutter/widgets/MediaQueryData/textScaleFactor.html

システム設定で文字のサイズを変更している場合、
レイアウトの崩れの発生やoverflowエラーが発生してしまう可能性があるため、確認が必要です。

実機やエミュレーターで都度設定を変更して各画面を手作業で確認すると大変なため、
Golden Testを利用できないか確認しました。
結果少しの設定を追加することでtextScaleFactorを変更した場合の確認ができることがわかりました!

2.ライトモード・ダークモードの表示が確認できる

文字サイズと同じ様に、AndroidとiOSでは画面表示をライトモード・ダークモードの2つの表示モードに切り替えることができます。
こちらも一括でいい感じに確認する方法が必要でしたがテキストの表示倍率と似た対応で
Golden Testでの確認ができることがわかりました!

それでは以下に詳細な導入手順を記載していこうと思います。

導入環境と事前準備

導入前提

導入する環境については前回のブログの 環境に追記する形とします。
よろしければ前のブログも見てください! よかったらさらに前のブログも見てください!!

表示倍率の対応範囲について

2023年7月現在、AndroidとiOSのシステム設定ではそれぞれ以下の範囲で拡大・縮小の倍率を指定することができます。

  • Android: 0.85 〜 1.3

  • iOS:  0.823529... 〜 1.3529... ※「さらに大きな文字」オプションを設定すると最大3.12程度まで設定可能

iOSでは最大3倍近くの文字の大きさに設定できますが、
今回は0.8〜1.3までを拡大縮小可能とし、それ以上以下の表示倍率の場合も
この範囲内で拡大縮小を行うアプリという前提とします。

事前準備

事前準備として、追加した設定がどの様に影響するかをわかりやすくするため、
mainプログラムとテストケースに追記を行います。

まずは、main.dartに現在のtextScaleFactorの倍率表示とダークモード表示を行うための設定を追記します。

# ./lib/main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.blue,
        fontFamily: 'Noto Sans JP',
      ),
      // ### ダークモードの設定を追加 ###
      darkTheme: ThemeData( 
        brightness: Brightness.dark,
        primarySwatch: Colors.blue,
        fontFamily: 'Noto Sans JP',
      ),
      // ### ダークモードの判定にデバイスの設定を利用する ###
      themeMode: ThemeMode.system, 
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // ### デバイスのフォント拡大率の表示を追加 ###          
            const Text(
              'textScaleFactor:',
            ),
            Text(
              '${MediaQuery.of(context).textScaleFactor}', 
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

次にテストケースのテーマにもダークモードを利用できる様に設定を追記します。

# ./test/my_home_page_golden_test.dart
import 'package:blog/main.dart';
import 'package:flutter/material.dart';
import 'package:golden_toolkit/golden_toolkit.dart';

void main() {
  testGoldens('MyHomePage', (tester) async {
    final builder = DeviceBuilder()
      ..addScenario(
        name: 'MyHomePage',
        widget: MaterialApp(
      theme: ThemeData(
        brightness: Brightness.light,
        primarySwatch: Colors.blue,
        fontFamily: 'Noto Sans JP',
      ),
      // ### ダークモードの設定を追加 ### 
      darkTheme: ThemeData( 
        brightness: Brightness.dark,
        primarySwatch: Colors.blue,
        fontFamily: 'Noto Sans JP',
      ),
      themeMode: ThemeMode.system,
          home: const MyHomePage(title: 'GoldenTestサンプル'),
        ),
      );

    await tester.pumpDeviceBuilder(builder);
    await screenMatchesGolden(tester, 'my_home_page');
  });
}

事前準備が完了しました。
次にGolden Testでテキスト表示倍率と表示モードのテストを
行うための設定を追記します。

Golden Test実行設定の追記

テストの設定ファイルを変更し、以下の処理を実行できる様にしました。

  • テキスト表示倍率と表示モードをコマンドラインの引数で指定可能にする

  • テキスト表示倍率と表示モードによって画像ファイルの名前、出力先を変更する。

以下コードです。

# ./test/flutter_test_config.dart
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:golden_toolkit/golden_toolkit.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  // ### テキストの表示倍率指定をコマンドラインより取得 ### 
  final textScale = TextScale.values.byName(
      const String.fromEnvironment('TEXT_SCALE', defaultValue: 'normal'));

  // ### 表示モードをコマンドラインより取得 ### 
  final brightness = Brightness.values.byName(
      const String.fromEnvironment('BRIGHTNESS', defaultValue: 'light'));

  return GoldenToolkit.runWithConfiguration(
    () async {
      await loadAppFonts();
      await testMain();
    },
    config: GoldenToolkitConfiguration(
      //  ### テキスト表示倍率と表示モードによって画像ファイルの名前、出力先を変更する ### 
      fileNameFactory: (name) {
        return 'goldens/${textScale.directoryName}$name${brightness.fileName}.png';
      },

      defaultDevices: [
        Device(
          name: 'phoneS',
          size: const Size(360, 720),
          devicePixelRatio: 3,
          safeArea: const EdgeInsets.only(top: 44, bottom: 34),
          textScale: textScale.value, // ### デバイス設定にテキスト表示倍率を追加 ### 
          brightness: brightness, //   ### デバイス設定に表示モードを追加 ### 
        ),
        Device(
          name: 'phoneM',
          size: const Size(390, 844),
          devicePixelRatio: 3,
          safeArea: const EdgeInsets.only(top: 44, bottom: 34),
          textScale: textScale.value,
          brightness: brightness,
        ),
        Device(
          name: 'phoneL',
          size: const Size(428, 926),
          devicePixelRatio: 3,
          safeArea: const EdgeInsets.only(top: 44, bottom: 34),
          textScale: textScale.value,
          brightness: brightness,
        ),
      ],

      // 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,
    ),
  );
}

enum TextScale {
  normal(1.0, ''),
  min(0.8, 'min/'),
  max(3, 'max/'),
  ;

  final double value;
  final String directoryName;

  const TextScale(this.value, this.directoryName);
}

extension BrightnessExt on Brightness {
  String get fileName => this == Brightness.dark ? '_$name' : '';
}

これでテスト実行時にdart-defineオプションを使うことで
テキスト表示倍率と表示モードを変更してGolden Testを実行することができます。
都度オプションを指定するのも面倒ですので、最終的にmakefileにまとめます。

# ./makefile
golden: ## デフォルトの表示倍率と表示モードで実行する
	@echo "╠ run golden..."
	@flutter test --update-goldens --tags=golden

golden-all: ## 表示倍率と表示モードを変更して複数回実行する
	@echo "╠  run golden-all.."
	@flutter test --update-goldens --tags=golden
	@flutter test --update-goldens --tags=golden --dart-define=TEXT_SCALE=min	
	@flutter test --update-goldens --tags=golden --dart-define=TEXT_SCALE=max
	@flutter test --update-goldens --tags=golden --dart-define=BRIGHTNESS=dark

これで

make golden-all

を実行することで、テキスト表示倍率と表示モードの確認を一括で確認できる様になりました!
実行後以下の様にスクリーンショットが生成されます。

./test/goldens/my_home_page.png (表示倍率1.0)

./test/goldens/min/my_home_page.png (表示倍率0.8)

./test/goldens/max/my_home_page.png (表示倍率1.3)

./test/goldens/my_home_page_dark.png (ダークモード)

ということでGolden Testがより便利になりました。

終わりに

Golden Testで表示倍率と表示モードの確認ができるようなったことは良かったのですが、
全ての確認を行う場合はテストを4回実行するしなければならなくなりました。
また作成されるスクリーンショットも4倍となります。

プルリクエストごとの確認で毎回全テストを実行するのはあまり現実的ではないと思うので、

  • プルリクエストでの確認ではデフォルトの表示倍率、表示モード

  • nightly buildで表示倍率と表示モードの確認を行う

というようにできるかなど
このあたりは、導入後良い感じに調整していこうと思います。

お読みいただき、ありがとうございました。

\もっと身近にもっとリアルに!DAAE公式Twitter/


執筆者プロフィール:sakurai
SHIFT DAAE部所属の開発エンジニアです。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/