Flutterアプリに画像回帰テストを導入する方法(Alchemist + GitHub Actions)
こんにちは、takuroです。
今回は、Flutterアプリに画像回帰テスト(Visual Regression Test)を導入方法を紹介します。
背景
開発中のUIコンポーネントに対し、以下のような課題を感じていました
- デザインの崩れやリグレッションがレビューでしか検出されない
- PRごとにUIの変化を確認するのが手間
- コンポーネントが増えるほど、人的チェックのコストが増大
これらの課題を解決するために、「UIの見た目をテストする仕組み」として画像回帰テストの自動化を導入しました。
Alchemistとは
Alchemistは、FlutterアプリケーションのUIコンポーネントを対象に、スクリーンショットを自動生成してGoldenテストを行うためのライブラリです。
Goldenテストとは、UIの見た目を画像として保存し、変更が加えられた際に差分を検出するテスト手法です。これにより、デザインの崩れやリグレッションを効率的に検出できます。
主な特徴:
- 複数のUIバリエーションを一括でテスト可能
- テーマやデバイスごとの描画をサポート
Alchemistを活用することで、UI品質を保ちながら開発効率を向上させることができます。
実装
実装環境
flutter: 3.29.3
alchemist: 0.10.0
1. alchemistの設定
pubspec.yamlに依存関係を追加
dev_dependencies:
alchemist: ^0.10.0
2. Goldenテストの実装
まず、Alchemistを使ってGoldenテストを実装します。以下のコードは、ボタンコンポーネントのさまざまなバリエーションをテストする例です。
Alchemistを使うことで、複数のバリエーションのUIを一括でGoldenテストできます。
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:alchemist/alchemist.dart';
void main() {
goldenTest(
'Button variants render correctly',
fileName: 'button_variants',
builder: () => GoldenTestGroup(
children: [
GoldenTestScenario(
name: 'Default (enabled)',
child: ElevatedButton(
onPressed: () {},
child: const Text('Default'),
),
),
GoldenTestScenario(
name: 'Disabled',
child: const ElevatedButton(
onPressed: null,
child: Text('Disabled'),
),
),
GoldenTestScenario(
name: 'Primary color',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
),
onPressed: () {},
child: const Text('Primary'),
),
),
GoldenTestScenario(
name: 'Secondary color',
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
),
onPressed: () {},
child: const Text('Secondary'),
),
),
GoldenTestScenario(
name: 'Small size',
child: SizedBox(
height: 30,
child: ElevatedButton(
onPressed: () {},
child: const Text('Small'),
),
),
),
GoldenTestScenario(
name: 'Large size',
child: SizedBox(
height: 60,
child: ElevatedButton(
onPressed: () {},
child: const Text('Large'),
),
),
),
],
),
);
}
4. ゴールデン画像の生成
ゴールデン画像を生成するために、以下のコマンドを実行します。
flutter test --update-goldens
これにより、goldens
ディレクトリに画像が保存されます。
5. CI/CDの設定
ワークフローでは、baseブランチでスナップショットを取得し、targetブランチでスナップショットを撮影して比較します。
これにより、画像の取り直しの手間を削減し、効率的なテストが可能になります。
6. ベースライン管理用のWorkflow追加
developやfeature/**ブランチにPushされた際に、ベースライン画像を生成・アップロード。
このワークフローは:
- 依存関係をインストール
- ゴールデンテストを実行し、ゴールデン画像を生成
- ゴールデン画像をアーティファクトとしてアップロード
name: golden-baseline
permissions:
contents: read
on:
push:
branches:
- develop
- feature/**
jobs:
build-goldens:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: 'stable'
- name: Install dependencies
run: flutter pub get
- name: Run golden tests
run: flutter test --update-goldens
- name: Upload golden images
uses: actions/upload-artifact@v4
with:
name: golden-baseline
path: test/**/goldens/*.png
これにより、比較対象のゴールデン画像を常に最新状態に保てます。
7. PR時の差分チェック用Workflow追加
PRが作成された際に、ゴールデン画像と現在のUIを比較し、差分がある場合はPRを失敗させるWorkflowを追加します。
このワークフローは:
-
PR の base ブランチから golden baseline PNG をダウンロード
-
現在のブランチで golden を生成
-
差分画像を生成(flutter test のビルトイン diff 機能を利用)
-
差分があれば PR を失敗させ、差分画像をアップロード
name: golden-diff-check
permissions:
contents: read
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
golden-diff:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: stable
- name: Install dependencies
run: flutter pub get
# ダウンロード:baseブランチの golden PNG(事前に生成されている前提)
- name: Download golden baseline PNG
uses: dawidd6/action-download-artifact@v3.1.4
with:
name: golden-baseline
workflow: golden-baseline.yml
branch: ${{ github.base_ref }}
path: test/goldens/base
# カレントブランチで golden test 実行(差分があれば *_diff.png が出る)
- name: Run golden tests
run: flutter test
# 差分画像をアップロード(失敗時のみ)
- name: Upload golden diffs
if: failure()
uses: actions/upload-artifact@v4
with:
name: golden-diff
path: "**/*_diff.png"
まとめ
画像回帰テストは、UI品質を保ちつつレビューコストを下げる有力な武器です。
今回のPRでは、その基盤構築とCI連携を行いました。
「UIの変化に強い開発体制」を目指して、今後もカバレッジ拡大や仕組みの強化を進めていきます!
Discussion