Flutter でCI/CDの取り組む前に知っておきたかったテスト関連の覚え書き

Flutter Advent Calendar 2022」に参加させて頂きます!18日目です。

現在FlutterでCI/CDを取り組んでいます。ユニットテスト、Widgetテスト、ゴールデンテスト、インテグレーションテストを実施して、Code Magicを使用してアプリストアにアップロードできればなぁ、と考えています。
その中で、実際に取り組む前に知っておいたら、楽だったのに、と思う点を記載いたします。

ユニットテスト

国際時間を使用するのであれば、時差を考慮する必要があるかもしれない。

国際時間を使っていると、手元のPCでは日本の時差を考えてテストします。しかしサーバでテストすると、サーバが日本にないことが多いです。そのため、時差を考慮する必要があるかも知れません。

final timeOffset = DateTime.now().timeZoneOffset.inHours;

test('datetime test', () async {
expect(
  DateTime(2022, 12, 6, 18 + timeOffset, 37, 57).toUtc().toIso8601String(),
  '2022-12-06T18:37:57.000Z',
);

Widgetテスト

特になし

ゴールデンテスト

実施環境のOSやバージョンによって、画面のキャプチャが微妙に異なる

WindowsとMacで、実施したときのキャプチャが微妙に異なる。また、MacでもOSによってはキャプチャーが異なります(「Golden テストがCIで実行すると失敗する場合の対策法」 からの「自MacOs13.1, CodeMagic 12.6.1でテストが通らないorz」)
)。
そのため、Windowsのキャプチャ画像を正しい画像としてMacでテストすると失敗します。逆も然り。
CI/CDで環境のOSとOSのバージョンを揃える必要がある。幸い手元にある8年前に買ったMacが12.5.1でCodeMagicが12.6.1でテストが通ったので、なんとかいけそうです。、

Windowsではゴールデンテストはせずに、Macだけで実施すると言う設定を記載します。

import 'dart:async';
import 'dart:io';
import 'package:golden_toolkit/golden_toolkit.dart';
/// Golden TestをMacでのみ実施する設定
Future testExecutable(FutureOr Function() testMain) async {
  return GoldenToolkit.runWithConfiguration(
    () async {
      await loadAppFonts();
      await testMain();
    },
    config: GoldenToolkitConfiguration(
      skipGoldenAssertion: () => !Platform.isMacOS,
    ),
  );
}

インテグレーションテスト

画面のキャプチャ

画面のキャプチャを、Androidはできる、iOSはできない(実行時に例外発生)
Androidで画面をキャプチャする場合は、binding.convertFlutterSurfaceToImage()をtestWidgetsごとに実施する必要がある

AndroidもiOSも挙動が残念なときがある

Androidでは、アニメーションがうまく動かない時がある。起動して待機中が続くときがある。(pumpではなく、pumpAndSettleだと出にくい気がします)
iOSはダイアログの中の項目をFinderが見つけてくれず、ダイアログのボタンが押せない。

プロダクトコード

テストとアプリとWEBのコードを分かつ

Platform.environment.containsKey(‘FLUTTER_TEST’)で、ユニットテスト、Widgetテスト、ゴールデンテスト実施時にコードがテストであることを知ることができる。インテグレーションテストでは使えない。
たとえば、Wiegetテストやゴールデンテストを実施時にネットワークの画像を使用すると、テストが失敗する。それを避けるために、テストであることを判断して、テスト時は別のローカル画像(FlutterLogoとか)に置き換えると良い。
ただ、FlutterWebはPlatformをサポートしていないので、FlutterWebでそのまま実施するとUnsupportedExceptionが発生する。そのため、kIsWebをその前に使って、Flutter Web時はPlatformのコードを実施させないことが重要。
また、ゴールデンテストにIsolateのcomputeを使用すると、タイムアウトになった。テストも考慮に入れるなら、Platform.environment.containsKey(‘FLUTTER_TEST’)を使って、テスト時はcomuteを使わない実装を入れる。

kIsWeb || !Platform.environment.containsKey('FLUTTER_TEST')
 ? Image.network(url)
 : FlutterLogo(size: size),

本当ならDIで切り分けると良い。

端末の向き

そのまますると、端末の置き方によって、端末の向きが変わってしまう。DIを使って、向きを固定させる。以下はGetItを使用した例。

final orientations = GetIt.I.get<List<DeviceOrientation>>();
SystemChrome.setPreferredOrientations(
  orientations,
);

「戻る」ボタン

「戻る」ボタンがOSによって異なるため、動的にIconDataを変える

  final backIcon = Platform.isAndroid
      ? Icons.arrow_back
      : Platform.isIOS
          ? Icons.arrow_back_ios
          : throw UnsupportedError('OS用のバックアイコンを設定してください');

まだ他にもありましたら記載していきます。その他、アドバイスがあればよろしくお願いします。