Upgrade to Pro — share decks privately, control downloads, hide ads and more …

An Encouragement of Flutter Golden Test

An Encouragement of Flutter Golden Test

Hiroki Takeda

July 20, 2022
Tweet

More Decks by Hiroki Takeda

Other Decks in Technology

Transcript

  1. ABOUT ME 武田 大輝(Takeda Hiroki) Technology Innovation Group Backend :

    Golang Frontend : Angular, Vue, Ionic, Flutter datake914 Software Design 2022年8月号 (2022年7月15日発売) 第4章 「OpenAPIを使ったWEB API開発の実際」を 執筆させていただきました。 2
  2. CONTENTS 1. Introduction: What is Golden Test? 2. How to

    Create Golden Test? 3. Golden Test with Golden Toolkit 4. Integrate with Widget Tree Test 5. Integrate with Riverpod 6. Summary 3
  3. Types of Testing in Flutter cf. https://docs.flutter.dev/testing Unit Test 単一の関数、メソッド単位

    でのホワイトボックステスト プログラムの内部構造に着目し、 様々な条件下でロジックが 確からしいことを確認する Low Widget Test Widget単位での コンポーネントテスト WidgetのUIが期待通りに 反応し、表示されることを確認する Higher Integration Test アプリケーション単位での 統合テスト 全てのWidgetとサービスが 期待通りに連携して機能することを 確認する Highest 信頼性 メンテナンスコスト 依存関係 実行速度 Low Higher Highest Few More Most Quick Quick Slow 5
  4. What is Golden Test? Golden TestはWidget Testの一種 Goldenとは・・・ 特定のウィジェットや状態の真となるレンダリングと見なされるマスタ画像 >

    The term golden file refers to a master image that is considered the true rendering of a given widget, state, application, or > other visual representation you have chosen to capture. cf. https://api.flutter.dev/flutter/flutter_test/matchesGoldenFile.html Widget Tree Test (通常のWidget Test) Widget Treeを探索評価 Widget Test Golden Test Widgetのスクリーンショットを ピクセルレベルで比較 6
  5. Golden Test Flow Goldenの生成 flutter test –-update-golden ソースコードの修正 テスト(イメージの生成 &

    比較) flutter test 事前準備 テスト イメージの比較 一致する場合:テスト成功 一致しない場合: テスト失敗 7
  6. Pure Golden Test - Write Test Code - 11 Load

    Fonts デフォルトだと Ahem と呼ばれる正方形フォントが使用されるため、 正確なゴールデンを生成するためにカスタムフォントをロードする。
  7. Pure Golden Test - Write Test Code - 12 tester.pumpWidget

    引数に指定されたWidgetをレンダリングする
  8. Pure Golden Test - Write Test Code - 13 matchesGoldenFile

    Widgetのスクリーンショットが、引数で指定された Goldenと一致することを検証する非同期のMatcher
  9. Pure Golden Test - Generate a Golden - matchesGoldenFile の引数に

    指定したパスにGoldenを生成 goldens/homepage.png 14
  10. Pure Golden Test - Execute Test - failures/ homepage_masterImage.png failures/

    homepage_testImage.png failures/ homepage_isolatedDiff.png failures/ homepage_maskedDiff.png 成功時 失敗時 15
  11. Golden Toolkit eBayが開発している、Golden Testを構築するためのライブラリ 主要な機能 <Test Builderの提供> • GoldenBuilder Widgetに対する様々なテストシナリオを定義し、全てのテストシナリオ

    のスクリーンショットを含む単一のGoldenイメージを生成することが できる。 • DeviceBuilder 事前に定義したデバイスに応じて、デバイスのサイズ別に各シナリオの スクリーンショットを単一のGoldenイメージに含めることができる。 <各種ユーティリティの提供> • Font Loader • Material Appのラッパー • matchedGoldenFileのラッパー などなど 18
  12. Golden Test with Golden Toolkit - Set Up - failuresディレクトリをGit管理対象外にする

    .gitignore テスト実行時の設定ファイルに "golden" タグを追加する dart_test.yaml Golden Toolkitによるテスト実行時に、各テストにはデフォルトで "golden" タグが付与されるため、 プロジェクト全体としてこのタグを既知のタグとして定義しておかないとテスト実行時に警告がでる。 19
  13. Golden Test with Golden Toolkit - Set Up - 共通的にフォントを読み込む

    Flutterは, テスト対象のファイルを起点として、上位階層のディレクトリに flutter_test_config.dart が存在するか スキャンを行い、 ファイルが見つかった場合は testExecutable() を実行する。 cf. https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html flutter_test_config.dart 20
  14. Golden Test with Golden Toolkit - Writing Test Code -

    testGoldens Flutter標準の testWidgets() のラッパーメソッド このメソッドを利用することで 各テストに “golden” タグが付与さ れ、 flutter test --tags golden でGolden Testのみ を実行することが可能になる。 後述する screenMatchesGolden() メソッドなどは、 このメソッドの内部で呼ばれていないとエラーとなるため、利用必須。 22
  15. Golden Test with Golden Toolkit - Writing Test Code -

    DeviceBuilder 複数のデバイスや複数のテストシナリオを定義するためのビルダ 後述する overrideDevicesForAllScenarios() メソッドや addScenario() メソッドを持つ。 23
  16. Golden Test with Golden Toolkit - Writing Test Code -

    overrideDevicesForAllScenarios テスト対象のWidgetを描画するデバイスを定義するメソッド あらかじめ定義された Device.tabletLandscape や Device.iphone11 などが利用できるほか、Device クラスを生 成して独自のデバイスサイズを定義することが可能。 24
  17. Golden Test with Golden Toolkit - Writing Test Code -

    addScenario テストシナリオを定義するメソッド テスト対象となるWidgetを引数で指定する。 ここでは、Flutter標準サンプルのカウンターページが、カウント値の 初期値を受け取れるよう修正し、初期値を受け取らないケース、受 け取るケースの2シナリオを定義している。 25
  18. Golden Test with Golden Toolkit - Writing Test Code -

    tester.pumpDeviceBuilder DeviceBuilder を受け取ってWidgetを描画するための WidgetTester の拡張メソッド 引数で WidgetWrapper を指定することでテスト対象の Widgetを任意のWidgetでラップすることが可能。 例えば、テスト対象のWidgetが特定のWidgetの配下で描画さ れることを前提としている場合などに指定が必要となる。 何も指定しない場合はデフォルトで、MaterialApp Widgetで ラップされる。 26
  19. Golden Test with Golden Toolkit - Writing Test Code -

    screenMatchesGolden Flutter標準の matchesGoldenFile のラッパーメソッド 第一引数に tester 第二引数に Goldenのファイル名を指定する。 27
  20. Golden Test with Golden Toolkit - Generate a Golden -

    goldens/homepage.png 定義したデバイス、テストシナリオ別の スクリーンショットが単一のGoldenとして出力 28
  21. Golden Test with Golden Toolkit - TIPs - Goldenの生成環境を統一する tools/docker-compose.yaml

    OSやFlutter/Dartのバージョンによって生成されるGoldenが微妙に異なるため、開発者の環境が分かれる場合は Dockerなどを利用して環境差異を吸収する。 Golden生成 30
  22. Integrate with Widget Tree Test Golden TestはWidget Tree Testのかわりとなるものではなく、 それぞれ異なる目的のために補い合うもの

    Widget Treeの状態が 期待通りであることを確認する 特定のアクションを実施したときに Widgetが存在するかどうかの確認、など Widgetの視覚的な状態が 期待通りであることを確認する 文字列の折り返しなど特定のデバイスサイズを 前提とした描画の確認、など Widget Tree Test (通常のWidget Test) Widget Treeを探索評価 Golden Test Widgetのスクリーンショットを ピクセルレベルで比較 32
  23. Integrate with Widget Tree Test - Integrate with the Widget

    Tree Test - onCreate 各シナリオのテスト対象のWidgetが作成されたときのHookを定 義することができる。Widget Tree Testはここに記述する。 Widgetを探索する場合は、必ず引数の key 配下のWidgetを 対象とする必要がある。 key は <シナリオ名 + デバイス名> となる。 Widget の描画イメージ 全シナリオ、全デバイスのWidgetが存在するため、keyで絞込を行わないと、 適切にFindできない Parent Widget Senario1 – Device① Widget Senario2 – Device① Widget Senario1 – Device② Widget Senario2 – Device② Widget 33
  24. Integrate with Widget Tree Test - Troubleshooting - Expected: exactly

    one matching node in the widget tree 先述の通り、各シナリオ、各デバイスごとのWidgetは1Widget内に描画されるため、 findsOneWidget などで対象のWidgetがユニークであることを確認しようとした際に、上記エラーが発生することがある。 • シナリオ名が重複していないか • Widgetを探索する際に onCreate の中で key による絞込を行っているか を確認する。 pumpAndSettle timed out tester.pumpAndSettle() メソッドを利用して、描画フレームがなくなるまで待機している場合に発生することがある。 ローディングアニメーションが表示しつづけていたり、テキストのカーソルが点滅していたりなど、 描画フレームが常に発生するようになっていないか確認する。 34
  25. Integrate with Riverpod - Background - • 通常のアプリケーションではAPI通信など アプリケーション外部に対するデータアクセスが発生する。 •

    Golden Test(Widget Test)時には、 これらのデータアクセスレイヤはモック化するのが定石となる。 • 今回は状態管理ライブラリとしてRiverpodを利用する前提で、 モック化含めどのようにGolden Testに組み込むか説明を行う。 36
  26. Integrate with Riverpod - Application Architecture - HomePage (UI) HomePageState

    (State) HomePageStateNotifier (Notifier) CountRepository (Repository) HomePageStateNotifierProvider (Provider) 38
  27. Integrate with Riverpod - Writing Application Code - home_page.dart ref.watch

    Stateを監視し、変更があった場合は、 Widgetをリビルドする。 ここでは、カウンターの値を監視し、Text Widget に描画している。 42
  28. Integrate with Riverpod - Writing Application Code - home_page.dart home_page.dart

    FutureBuilder 非同期処理の結果を待ってからWidgetを ビルドしてくれるWidget ここでは HomePageStateNotideir の initialize() を呼び出し、初期カウント 値をセットしている。 initialize() 実行中は サークルインジケータが表示される。 43
  29. Integrate with Riverpod - Writing Test Code - 45 @GenerateMocks

    Mockitoを利用してリポジトリのモックを生成し、 モックの振る舞いを定義
  30. Integrate with Riverpod - Writing Test Code - 46 ProviderScope

    の overrides を利用して MockをInject
  31. Integrate with Riverpod - Writing Test Code - 課題: DeviceBuilder#addScenario()

    ではMockのInjectがツライ home_page_test.dart device_builder.dart builderの外側でシナリオ数分Mockを定義しなければならず、 可読性が著しく低下する。 47
  32. Integrate with Riverpod - Writing Test Code - 対応: DeviceBuilder

    を拡張して DeviceScenario のBuilderを追加できるようにする home_page_test.dart custom_device_builder.dart 48
  33. Integrate with Riverpod - Writing Test Code - 課題: デバイスが複数定義されている場合に、同一のWidgetが使いまわされる。

    device_builder.dart Parent Widget Senario1 – Device① Widget Senario2 – Device① Widget Senario1 – Device② Widget Senario2 – Device② Widget ProviderScope や Mock が同一シナリオ内で共有されるため、 onCreate における verify でエラーとなる可能性がある。 例えば verify(mockCountRepository.get()).callCount は 「1」となって欲しいが、デバイス数分カウントされてしまう。 Mock Scope 49
  34. Integrate with Riverpod - Writing Test Code - 対応: addScenarioBuilder

    にてデバイス別にシナリオをセットするようにする custom_device_builder.dart custom_device_builder.dart 各デバイスごとに overrideDevicesForAllScenarios を 行うことで、シナリオ別、デバイス別にシナリオインスタンスを保持する。 50
  35. Integrate with Riverpod - Completed Test Code - ProviderScope の

    overrides を利用して MockをInject 53
  36. Summary • Golden TestはWidget Testの一種で、スクリーンショットを比較するテスト • Golden Toolkitを使うことで、よりリッチにGoldenを生成可能(使わなくてもできる) • 実際にはGolden

    Testに Widget Tree Test や Mock の検証なども統合する形で、 テストコードを記述していくのがよい。 資料中のコードの全量は下記のリポジトリにあります。 https://github.com/datake914/flutter-golden-test-sample 57