
Flutter Golden Test
- 6 installs
- 3 repo stars
- Updated June 5, 2026
- xtone/ai_development_tools
Helps with testing & qa tasks.
About
flutter-golden-test is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted development.
- flutter-golden-test
- Testing & QA
- AI-coding skill
Flutter Golden Test by the numbers
- 6 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,591 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/xtone/ai_development_tools --skill flutter-golden-testAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 3 |
| Last updated | June 5, 2026 |
| Repository | xtone/ai_development_tools ↗ |
What it does
Helps with testing & qa tasks.
Files
Flutter Golden Test スキル
概要
Flutter初心者でもGolden Test(Visual Regression Test)環境をセットアップし、コンポーネントや画面のGolden Testを作成・実行できるようにする対話型スキル。
発動トリガー:
- 「Golden Testを導入したい」「ゴールデンテストをセットアップ」
- 「スクリーンショットテストを作りたい」「UIテストを追加」
- 「コンポーネントのGolden Testを書いて」
- 「Golden Testを実行」「ゴールデンファイルを更新」
Role: Expert Interviewer
あなたは経験豊富なFlutterテストエンジニアとして、要件インタビューを行います。ゴールは:
- 明確で簡潔な質問を1つずつ行う
- ユーザーのプロジェクト環境を理解する
- 適切なGolden Test環境を構築する
- テストコードを生成する
重要: 複数の質問を一度にしない。ユーザーの回答を待ってから次に進む。
動作モード
モード判定フロー
1. まずプロジェクトを解析し、Golden Test環境の有無を確認 2. 環境がなければ「初期セットアップモード」 3. 環境があれば「テスト生成モード」または「テスト実行・更新モード」
モード1: 初期セットアップ
Golden Test環境が存在しない場合に実行。
重要: 以下の全ステップを順番に実行すること(スキップ禁止)
| Step | 内容 | 必須 |
|---|---|---|
| 0 | Golden Testの説明 | ○ |
| 1 | プロジェクト解析 | ○ |
| 2 | フォント設定確認 | ○ |
| 3 | Riverpod使用確認 | ○ |
| 4 | テーマ設定確認 | ○ |
| 5 | 画面サイズ確認 | ○ |
| 6 | 画像アセット使用確認 | ○ |
| 7 | テスト作成粒度の確認 | ○ |
| 8 | ファイル生成 | ○ |
Step 0: Golden Testの説明 [0/8]
最初に以下を伝える:
「Golden Test(ゴールデンテスト)の環境をセットアップしますね。【Step 0/8】
■ Golden Testとは?
ウィジェットのスクリーンショットを保存し、以降の変更で見た目が意図せず変わっていないかを自動検証するテストです。UIの品質を保ちながら安心してリファクタリングできるようになります。
■ このスキルでできること
対話形式で環境構築からテスト作成までサポートします。テスト画像がうまく生成されない場合も、会話しながら一緒に解決していきますので、お気軽にご質問ください。
■ 具体例:こんなテストが作れます
例えば、以下のようなボタンウィジェットがあるとします:」続けて、以下の具体例を提示する:
```` 「▼ 対象ウィジェット(例)
class PrimaryButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
const PrimaryButton({required this.text, this.onPressed});
@override
Widget build(BuildContext context) {
return ElevatedButton(onPressed: onPressed, child: Text(text));
}
}▼ 生成されるテストコード
testWidgets('有効状態', (tester) async {
await tester.pumpGoldenWidget(
PrimaryButton(onPressed: () {}, text: '保存する'),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile(
GoldenFilePath.component('primary_button', 'enabled'),
),
);
});▼ 生成されるファイル test/ui/components/ ├── primary_button_golden_test.dart ← テストコード └── goldens/components/primary_button/ ├── enabled.png ← 有効状態のスクリーンショット ├── disabled.png ← 無効状態のスクリーンショット └── all_states.png ← 全状態の一覧比較
▼ テスト実行コマンド
初回:Goldenファイル(正解画像)を生成
flutter test --update-goldens test/ui/components/primary_button_golden_test.dart
以降:画像に差分がないか検証(CIでも実行可能)
flutter test test/ui/components/primary_button_golden_test.dart
セットアップ完了後、対話形式でこのようなテストを自動生成していきます。 何か事前に質問があれば、今のうちにどうぞ。なければ、セットアップを始めましょう。」 ````
ユーザーの回答を待つ → 回答後、Step 1へ進む
Step 1: プロジェクト解析 [1/8]
プロジェクトの以下を確認:
test/flutter_test_config.dartの有無test/helpers/golden_test_helper.dartの有無pubspec.yamlのフォント設定- テーマファイルの場所
確認後の発言例:
「Golden Testの環境をセットアップしますね。まずプロジェクトを確認させてください。
プロジェクト構成を解析しました:
- テストディレクトリ: test/
- テーマファイル: lib/ui/core/themes/theme.dart(検出)
- 使用フォント: Noto Sans JP(assets/fontsから検出)
この認識で合っていますか?」ユーザーの回答を待つ → 回答後、Step 2へ進む
Step 2: フォント設定確認 [2/8]
「【Step 2/8】Golden Testではフォントの一貫性が重要です。
プロジェクトで使用しているフォントを教えてください:
1. Noto Sans JP(日本語対応)
2. Roboto(Material Design標準)
3. カスタムフォント(フォント名とパスを教えてください)
4. フォント設定は不要
どれを使用していますか?」ユーザーの回答を待つ → 回答後、Step 3へ進む
Step 3: Riverpod使用確認 [3/8]
Step 1のプロジェクト解析結果に基づいて、以下のA/B/Cいずれかのパターンで質問する。
A) Riverpodを使用していない(pubspec.yamlにriverpod関連パッケージがない)場合:
「【Step 3/8】プロジェクトでRiverpodを使用していますか?
1. はい - ProviderScopeでラップするヘルパーを追加します
2. いいえ - シンプルなMaterialAppのみで構成します
どちらですか?」B) Riverpodを使用しており、既存のテストヘルパー(例: `test/helpers/pump_app.dart`)が存在する場合:
必ず各選択肢のメリット・デメリットを明記し、推奨を示すこと。
「【Step 3/8】プロジェクトでRiverpodを使用していることを確認しました。
既存のテストヘルパー({検出したファイルパス})も検出しました。
Golden TestのProviderScope設定について、2つの方針があります:
1. 既存のヘルパーを活用する(推奨)
✅ メリット: 既存テストと設定が統一され、Providerのoverrideなどが一箇所で管理できる
⚠️ デメリット: 既存ヘルパーの変更がGolden Testにも影響する
→ 既にテストが整備されているプロジェクトにおすすめ
2. Golden Test専用に独立した設定にする
✅ メリット: Golden Test固有の設定を自由にカスタマイズでき、他のテストに影響しない
⚠️ デメリット: 設定が二重管理になり、変更時に両方を更新する必要がある
→ Golden Test特有のカスタマイズが多い場合におすすめ
迷ったら 1(推奨)を選んでください。どちらにしますか?」C) Riverpodを使用しているが、既存のテストヘルパーが存在しない場合:
「【Step 3/8】プロジェクトでRiverpodを使用していることを確認しました。
ProviderScopeでラップするGolden Test用ヘルパーを新規作成します。
この方針でよろしいですか?」ユーザーの回答を待つ → 回答後、Step 4へ進む
Step 4: テーマ設定確認 [4/8]
「【Step 4/8】テストで使用するテーマを教えてください:
1. カスタムテーマを使用(例: AppTheme.light())
2. デフォルトのMaterial Themeを使用
カスタムテーマの場合、インポートパスを教えてください。」ユーザーの回答を待つ → 回答後、Step 5へ進む
Step 5: 画面サイズ確認 [5/8]
「【Step 5/8】テスト用のサイズプリセットを設定します。
以下のデフォルト設定でよろしいですか?
コンポーネント用:
- component: 400x100 (単体)
- componentList: 400x400 (複数状態表示)
画面用:
- screenSmall: 375x667 (iPhone SE相当)
- screenMedium: 390x844 (iPhone 14相当)
- screenLarge: 430x932 (iPhone 14 Pro Max相当)
変更したいサイズがあれば教えてください。」ユーザーの回答を待つ → 回答後、Step 6へ進む
Step 6: 画像アセット使用確認 [6/8]
「【Step 6/8】プロジェクトで画像アセットを使用していますか?
1. はい - 画像をテスト環境でロードする設定を追加します
2. いいえ - 画像ローディングの設定は不要です
画像を使用している場合、テストでも正しく表示されるよう設定します。」ユーザーの回答を待つ → 回答後、Step 7へ進む
Step 7: テスト作成粒度の確認 [7/8]
「【Step 7/8】今後のテスト作成時、どの単位でテストを作成しますか?
1. コンポーネント単位 - ボタンやカードなど、再利用可能な部品ごとにテスト
2. 画面単位 - Screen/Pageごとにテスト
3. 都度確認 - テスト作成時に毎回確認する
この設定はプロジェクトのCLAUDE.mdに記録し、以降のテスト作成時に参照します。」ユーザーの回答を待つ → 回答後、Step 8(ファイル生成)へ進む
Step 8: ファイル生成 [8/8]
全ステップ完了後、収集した情報を基に以下のファイルを生成:
1. test/flutter_test_config.dart
- templates/flutter_test_config.dart.md を参照
- フォント設定を反映
- 画像アセット使用時はMaterialIconsのロード設定を追加
2. test/helpers/golden_test_helper.dart
- templates/golden_test_helper.dart.md を参照
- Riverpod使用時は templates/golden_test_helper_riverpod.dart.md も参照
- テーマとサイズ設定を反映
3. test/helpers/helpers.dart (バレルファイル)
- ヘルパーファイルをエクスポート
4. test/golden_test_issues.md (問題解決ログ)
- templates/golden_test_issues.md を参照
- トラブルシューティングの記録用
5. プロジェクトのCLAUDE.mdに追記
- Golden Test設定セクションを追加
- テスト作成粒度の設定を記録
CLAUDE.mdへの追記例:
## Golden Test 設定
### テスト作成粒度
- 方針: コンポーネント単位(または画面単位/都度確認)
### プロジェクト固有の注意点
- (問題が発生した場合、ここに記録される)生成後の発言例:
「以下のファイルを生成しました:
1. test/flutter_test_config.dart
- グローバルテスト設定
- フォントローディング
- アイコンフォントのロード
2. test/helpers/golden_test_helper.dart
- GoldenTestSizes: サイズプリセット
- GoldenTestExtension: pumpGoldenWidget(), pumpGoldenWidgetList()
- GoldenFilePath: パス生成ユーティリティ
3. test/golden_test_issues.md
- 問題解決ログ(トラブル発生時に記録)
4. CLAUDE.mdに設定を追記しました
- テスト作成粒度: {選択された粒度}
これでGolden Test環境の準備が完了しました!
試しに1つテストを作成してみますか?」モード2: テスト生成
Golden Test環境が存在し、新規テストを作成する場合。
Step 1: テスト対象の確認
「どのウィジェットのGolden Testを作成しますか?
例:
- lib/ui/core/components/primary_button.dart
- lib/ui/features/login/widgets/login_screen.dart
ファイルパスまたはウィジェット名を教えてください。」ユーザーの回答を待つ
Step 2: テスト種別の確認
CLAUDE.mdのテスト作成粒度設定を確認:
- 「コンポーネント単位」→ コンポーネントテストとして進行
- 「画面単位」→ 画面テストとして進行
- 「都度確認」または設定なし → 以下の質問を行う
「このウィジェットは以下のどちらですか?(番号でお答えください)
1. コンポーネント(ボタン、カード、入力フィールドなど)
2. 画面(Screen、Pageなど)」ユーザーの回答を待つ
Step 3: ウィジェット解析と警告チェック
ウィジェットコードを解析し、以下をチェック:
1. ネットワーク画像の検出 (Image.network, CachedNetworkImage など)
- 検出した場合、以下を案内:
「⚠️ ネットワーク画像を検出しました。
Golden Testではネットワーク画像は取得できないため、テスト用にダミー画像で置き換える必要があります。
対応方法:
1. モック画像プロバイダーを使用
2. テスト用のアセット画像に差し替え
詳しくは knowledge/common-issues.md の「ネットワーク画像の対応」を参照してください。
このまま進めますか?」2. 日本語テキストとフォントの確認
- 日本語テキストが含まれ、かつフォントがRobotoなど日本語非対応の場合:
「⚠️ 日本語テキストを検出しましたが、設定されているフォント(Roboto)は日本語に対応していません。
実機ではOSがフォールバックしますが、Golden Testでは日本語が正しく表示されない可能性があります。
対応方法:
1. Noto Sans JPなど日本語対応フォントを追加
2. flutter_test_config.dartでフォントをロード
詳しくは knowledge/common-issues.md の「日本語フォントの問題」を参照してください。
このまま進めますか?」Step 4: ウィジェットパラメータ解析
対象ウィジェットのコードを読み取り、コンストラクタパラメータを解析。
解析後の発言例:
「PrimaryButtonを解析しました。
コンストラクタパラメータ:
- text: String (必須)
- onPressed: VoidCallback? (オプション)
- icon: Widget? (オプション)
テストしたい状態を選んでください(複数選択可、番号でお答えください):
1. 有効状態(onPressed != null)
2. 無効状態(onPressed == null)
3. アイコン付き
4. 長いテキスト
5. 全状態の比較(一覧表示)
他にテストしたい状態はありますか?」ユーザーの回答を待つ
Step 5: テストコード生成
選択された状態に基づいてテストコードを生成。
- コンポーネントの場合: templates/component_golden_test.dart.md を参照
- 画面の場合: templates/screen_golden_test.dart.md を参照
生成後の発言例:
「以下のテストファイルを生成しました:
test/ui/core/components/primary_button_golden_test.dart
テストケース:
- 有効状態
- 無効状態
- アイコン付き
- 全状態の比較
Goldenファイルは以下に保存されます:
test/ui/core/components/goldens/components/primary_button/
次のコマンドでGoldenファイルを生成してください:
flutter test --update-goldens test/ui/core/components/primary_button_golden_test.dart
⚠️ もしテスト画像がうまく生成されない場合は、エラー内容を教えてください。一緒に解決していきましょう。」モード3: テスト実行・更新
テストの実行方法を案内。
「Golden Testの実行方法:
■ テスト実行(検証):
flutter test test/ui/core/components/primary_button_golden_test.dart
■ Goldenファイル更新:
flutter test --update-goldens test/ui/core/components/primary_button_golden_test.dart
■ 全Golden Testの実行:
flutter test --tags golden
■ 特定ディレクトリのGolden Test更新:
flutter test --update-goldens test/ui/core/components/
実行しますか?
⚠️ テストが失敗したり、画像が期待通りにならない場合は、エラー内容を教えてください。」モード4: トラブルシューティング
テストがうまくいかない場合の対応フロー。
Step 1: 問題の特定
ユーザーからエラー内容や問題の報告を受けたら、以下を確認:
1. エラーメッセージ: 具体的なエラー内容 2. 期待と実際の差: 何が違うのか 3. 環境情報: Flutter バージョン、OS など
Step 2: 原因の特定と解決
knowledge/common-issues.md を参照し、該当する問題と解決策を提案。
よくある問題:
- フォントが表示されない → フォントローディング設定
- 画像が表示されない → アセットローディングまたはモック
- CI環境で失敗 → devicePixelRatio、環境差異
- 日本語が文字化け → 日本語フォント設定
Step 3: 問題解決ログへの記録
問題を解決したら、test/golden_test_issues.md に記録を追加:
「問題を解決しました!
今後のために、この問題と解決方法を `test/golden_test_issues.md` に記録しておきますね。
---
## {日付}: {問題の概要}
### 問題
{問題の詳細}
### 原因
{原因の説明}
### 解決方法
{解決手順}
### 今後の注意点
{同様の問題を防ぐためのポイント}
---
これで同じ問題が発生しても、すぐに対応できます。」CLAUDE.mdへの追記(必要に応じて):
プロジェクト固有の注意点として、CLAUDE.mdの「Golden Test 設定」セクションにも追記:
### プロジェクト固有の注意点
- {解決した問題の要約と対策}テンプレート参照
テンプレートファイルは templates/ ディレクトリにあります:
| テンプレート | 用途 |
|---|---|
| flutter_test_config.dart.md | グローバルテスト設定 |
| golden_test_helper.dart.md | 標準ヘルパー |
| golden_test_helper_riverpod.dart.md | Riverpod対応ヘルパー |
| component_golden_test.dart.md | コンポーネントテスト |
| screen_golden_test.dart.md | 画面テスト |
| golden_test_issues.md | 問題解決ログ |
ナレッジ参照
困ったときは knowledge/ ディレクトリを参照:
| ファイル | 内容 |
|---|---|
| golden-test-concepts.md | Golden Testの基礎知識 |
| common-issues.md | よくある問題と解決方法 |
| best-practices.md | ベストプラクティス |
出力ファイル構造
生成されるテストファイル
test/
├── flutter_test_config.dart # グローバル設定
├── helpers/
│ ├── helpers.dart # バレルファイル
│ └── golden_test_helper.dart # ヘルパー
└── ui/
└── core/
└── components/
├── primary_button_golden_test.dart
└── goldens/
└── components/
└── primary_button/
├── enabled.png
├── disabled.png
└── all_states.pngGoldenファイルパス規則
- コンポーネント:
goldens/components/{component_name}/{state_name}.png - 画面:
goldens/screens/{screen_name}/{state_name}.png
重要な注意点
1. 1問ずつ質問する - 複数の質問を一度にしない 2. ユーザーの回答を確認 - 次のステップに進む前に確認 3. プロジェクト固有の設定を反映 - テーマ、フォント、Riverpodなど 4. テスト実行コマンドを案内 - 生成後に必ず実行方法を伝える 5. devicePixelRatio = 1.0 - スクリーンショットの一貫性のため必須
Golden Test ベストプラクティス
1. 環境設定
devicePixelRatio = 1.0 を設定する理由
view.devicePixelRatio = 1.0;理由:
- 異なるデバイスやCI環境でも一貫した結果を得るため
- 高DPIディスプレイ(Retinaなど)の影響を排除
- ファイルサイズを抑制(2x、3xの画像を生成しない)
debugShowCheckedModeBanner = false の重要性
MaterialApp(
debugShowCheckedModeBanner: false,
// ...
)理由:
- DEBUGバナーがGoldenに含まれると、リリースビルドとの比較が困難
- バナーの位置やサイズが環境で異なる可能性
2. 命名規則
ファイル名(snake_case)
test/
└── ui/
└── components/
└── primary_button_golden_test.dart ✅
└── PrimaryButton_golden_test.dart ❌状態名(snake_case)
// 良い例
GoldenFilePath.component('primary_button', 'enabled')
GoldenFilePath.component('primary_button', 'with_icon')
GoldenFilePath.component('primary_button', 'long_text')
// 悪い例
GoldenFilePath.component('primary_button', 'Enabled')
GoldenFilePath.component('primary_button', 'withIcon')一般的な状態名
| 状態 | 命名 |
|---|---|
| 有効状態 | enabled |
| 無効状態 | disabled |
| 選択状態 | selected |
| 未選択状態 | unselected |
| フォーカス状態 | focused |
| エラー状態 | error |
| ローディング | loading |
| 空状態 | empty |
| 全状態比較 | all_states |
| アイコン付き | with_icon |
| 長いテキスト | long_text |
3. テスト状態の設計
重要な状態を特定する
すべての状態をテストするのではなく、重要な状態を選ぶ:
1. デフォルト状態: 最も一般的な使用方法 2. エッジケース: 長いテキスト、空の入力など 3. インタラクション状態: 有効/無効、フォーカスなど 4. エラー状態: バリデーションエラーなど
all_states テストの活用
複数の状態を一覧で比較することで、変更の影響を俯瞰できる:
testWidgets('全状態の比較', (tester) async {
await tester.pumpGoldenWidgetList([
// 各状態を列挙
]);
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile(GoldenFilePath.component('button', 'all_states')),
);
});4. ディレクトリ構造
推奨構造
test/
├── flutter_test_config.dart # グローバル設定
├── helpers/
│ ├── helpers.dart # バレルファイル
│ └── golden_test_helper.dart # ヘルパー
├── mocks/ # モック定義
│ └── mock_providers.dart
└── ui/
├── core/
│ └── components/
│ ├── primary_button_golden_test.dart
│ └── goldens/
│ └── components/
│ └── primary_button/
│ ├── enabled.png
│ └── disabled.png
└── features/
└── login/
├── login_screen_golden_test.dart
└── goldens/
└── screens/
└── login_screen/
├── initial.png
└── loading.pngGoldenファイルをテストファイルの近くに配置
- 関連するテストとGoldenが近くにあると管理しやすい
- PRレビュー時に変更の影響を把握しやすい
5. テストの書き方
1テストケース1状態
// 良い例: 1テストケースで1状態をテスト
testWidgets('有効状態', (tester) async {
await tester.pumpGoldenWidget(Button(enabled: true));
// ...
});
testWidgets('無効状態', (tester) async {
await tester.pumpGoldenWidget(Button(enabled: false));
// ...
});
// 悪い例: 複数の状態を1テストで検証
testWidgets('有効/無効状態', (tester) async {
await tester.pumpGoldenWidget(Button(enabled: true));
await expectLater(...);
await tester.pumpGoldenWidget(Button(enabled: false));
await expectLater(...);
});クリーンアップを忘れない
testWidgets('状態テスト', (tester) async {
await tester.pumpGoldenWidget(widget);
await expectLater(...);
await tester.cleanUpGoldenTest(); // 必須!
});6. CI/CD統合
Goldenファイルの更新フロー
1. ローカルで変更を確認 2. CI環境でGoldenを更新(環境差異を避けるため) 3. PRでGoldenファイルの差分をレビュー 4. マージ後に自動テスト
CIでの実行コマンド
# テスト実行(検証のみ)
flutter test
# Goldenファイル更新(特定のブランチのみ)
flutter test --update-goldens7. パフォーマンス
テスト速度の最適化
1. 並列実行:
flutter test --concurrency=42. 適切なサイズ: コンポーネントには小さいサイズを使用。
3. 必要なテストのみ: すべてのバリエーションをテストしない。
テストの分類
Goldenテストにタグを付けて、必要に応じて実行:
@Tags(['golden'])
void main() {
// ...
}# Goldenテストのみ実行
flutter test --tags golden
# Goldenテストを除外
flutter test --exclude-tags golden8. レビューのポイント
Goldenファイル変更時の確認事項
1. 意図した変更か: UIの変更が期待通りか 2. 影響範囲: 他のGoldenに影響がないか 3. 一貫性: 他のコンポーネントとの整合性 4. アクセシビリティ: コントラスト、サイズは適切か
PRでのGolden差分の可視化
GitHubやGitLabでは画像の差分を表示できる。 差分を視覚的に確認してレビュー。
9. メンテナンス
不要なGoldenの削除
コンポーネントを削除した場合、Goldenファイルも削除:
# 未使用のGoldenを検出(手動確認)
find test -name "*.png" | while read f; do
grep -q "$(basename $f .png)" test/**/*_test.dart || echo "$f"
done定期的な見直し
- 四半期ごとにGoldenテストを見直し
- 不要なテストを削除
- 新しいコンポーネントにテストを追加
10. アンチパターン
避けるべきこと
1. すべての状態をテストする: 重要な状態のみに絞る 2. 実装詳細をテストする: 見た目の変更のみを検証 3. CIと異なる環境でGoldenを更新: 環境差異で失敗する 4. テスト後のクリーンアップを忘れる: 次のテストに影響 5. アニメーション途中でキャプチャ: 不安定なテストになる
よくある問題と解決方法
1. フォントが正しく表示されない
症状
Goldenファイルでテキストが「□」や空白で表示される。
原因
テスト環境でフォントがロードされていない。
解決策
test/flutter_test_config.dart でフォントをロードする:
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
TestWidgetsFlutterBinding.ensureInitialized();
// フォントをロード
final fontLoader = FontLoader('Noto Sans JP')
..addFont(_loadFontData('assets/fonts/NotoSansJP-Regular.ttf'));
await fontLoader.load();
await testMain();
}
Future<ByteData> _loadFontData(String path) async {
final file = File(path);
final bytes = await file.readAsBytes();
return ByteData.view(bytes.buffer);
}2. CI環境とローカル環境でGoldenが一致しない
症状
ローカルでは成功するがCIで失敗する。または逆。
原因
- OS間でのフォントレンダリングの違い
- devicePixelRatioの違い
- 画面サイズの違い
解決策
1. devicePixelRatioを固定:
view.devicePixelRatio = 1.0;2. 画面サイズを明示的に設定:
await binding.setSurfaceSize(const Size(400, 100));
view.physicalSize = const Size(400, 100);3. CI環境でGoldenを生成:
# CI環境でのみGoldenを更新
flutter test --update-goldens4. Docker/Containerを使用: ローカルでもCIと同じ環境でテストを実行。
3. Goldenファイルの更新を忘れる
症状
UIを変更したのにGoldenファイルを更新せず、テストが失敗し続ける。
解決策
1. CIでGolden更新を禁止: CI環境では --update-goldens を使用しない。
2. PR時にGoldenファイルの差分を確認: 画像の差分を視覚的に確認できるツールを導入。
3. チェックリストに追加: UI変更時にGolden更新をチェックリストに含める。
4. テストが遅い
症状
Golden Testの実行に時間がかかる。
原因
- 画像の比較に時間がかかる
- ウィジェットのビルドに時間がかかる
解決策
1. 必要な状態のみテスト: すべての状態をテストするのではなく、重要な状態のみをテスト。
2. 並列実行:
flutter test --concurrency=43. 適切なサイズを使用: コンポーネントには小さいサイズを使用。
5. 画面サイズが正しく設定されない
症状
設定したサイズと異なるGoldenファイルが生成される。
解決策
setSurfaceSize と physicalSize の両方を設定:
await binding.setSurfaceSize(surfaceSize);
view.physicalSize = surfaceSize;
view.devicePixelRatio = 1.0;テスト後にクリーンアップ:
await binding.setSurfaceSize(null);6. アニメーションが途中で止まる
症状
アニメーションが完了していない状態でGoldenが取得される。
解決策
1. アニメーションを完了させる:
await tester.pumpAndSettle();2. 特定のフレームでキャプチャ:
await tester.pump(const Duration(milliseconds: 500));7. debugShowCheckedModeBannerが表示される
症状
Goldenファイルに「DEBUG」バナーが表示される。
解決策
MaterialAppで無効化:
MaterialApp(
debugShowCheckedModeBanner: false,
// ...
)8. 非同期処理の結果が反映されない
症状
APIからのデータ取得後の状態がテストできない。
解決策
1. モックを使用:
await tester.pumpGoldenWidgetWithRiverpod(
const MyScreen(),
overrides: [
dataProvider.overrideWith((ref) => AsyncData(mockData)),
],
);2. 適切な待機:
await tester.pumpAndSettle();9. OverflowErrorが発生する
症状
RenderFlexが「overflowed」というエラーを出す。
解決策
テストサイズを大きくする:
await tester.pumpGoldenWidget(
widget,
surfaceSize: const Size(400, 200), // 高さを増やす
);10. 画像が表示されない
症状
Image.asset() や Image.network() が表示されない。
解決策
1. アセット画像の場合: アセットがテスト環境で読み込めることを確認。
2. ネットワーク画像の場合: モックを使用(詳細は「11. ネットワーク画像の対応」を参照)
3. プレースホルダーを使用: テスト用にプレースホルダー画像を表示。
11. ネットワーク画像の対応
症状
Image.network() や CachedNetworkImage を使用しているウィジェットで、画像が表示されない、またはエラーになる。
原因
Golden Testはネットワーク通信を行わないため、ネットワーク画像は取得できない。
解決策
方法1: ダミー画像ウィジェットで置き換え(推奨)
テスト用のラッパーウィジェットを作成し、テスト時はダミー画像を表示:
// lib/ui/core/widgets/network_image_wrapper.dart
import 'package:flutter/material.dart';
class NetworkImageWrapper extends StatelessWidget {
final String imageUrl;
final double? width;
final double? height;
final BoxFit? fit;
const NetworkImageWrapper({
super.key,
required this.imageUrl,
this.width,
this.height,
this.fit,
});
@override
Widget build(BuildContext context) {
return Image.network(
imageUrl,
width: width,
height: height,
fit: fit,
// エラー時のフォールバック
errorBuilder: (context, error, stackTrace) {
return _buildPlaceholder();
},
// ロード中の表示
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return _buildPlaceholder();
},
);
}
Widget _buildPlaceholder() {
return Container(
width: width,
height: height,
color: Colors.grey[300],
child: const Icon(Icons.image, color: Colors.grey),
);
}
}方法2: mocktail/mockitoでHttpClientをモック
// test/mocks/mock_http_client.dart
import 'dart:io';
import 'package:mocktail/mocktail.dart';
class MockHttpClient extends Mock implements HttpClient {}
class MockHttpClientRequest extends Mock implements HttpClientRequest {}
class MockHttpClientResponse extends Mock implements HttpClientResponse {}
// テストで使用
void main() {
setUpAll(() {
// HttpClientをモック
HttpOverrides.global = MockHttpOverrides();
});
}
class MockHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
return MockHttpClient();
}
}方法3: CachedNetworkImageの場合
cached_network_image パッケージを使用している場合:
// テスト用のモックプロバイダー
testWidgets('画像表示テスト', (tester) async {
await tester.pumpGoldenWidgetWithRiverpod(
MyWidget(),
overrides: [
// 画像URLをローカルアセットに置き換え
imageUrlProvider.overrideWithValue('assets/test/dummy_image.png'),
],
);
});ベストプラクティス
1. 本番コードの変更を最小限に: テスト用のモックを使用し、本番コードには影響を与えない 2. プレースホルダーの一貫性: ダミー画像は常に同じ見た目にする(Goldenの安定性のため) 3. サイズを固定: 画像のサイズは明示的に指定する
12. 日本語フォントの問題(フォールバック)
症状
- 日本語テキストが「□□□」や空白で表示される
- 実機では正しく表示されるが、Golden Testでは文字化けする
原因
プロジェクトのデフォルトフォント(例: Roboto)が日本語に対応していない場合、 実機ではOSが自動的に日本語フォントにフォールバックするが、 テスト環境ではこのフォールバックが機能しない。
解決策
Step 1: 日本語対応フォントを追加
# pubspec.yaml
flutter:
fonts:
- family: NotoSansJP
fonts:
- asset: assets/fonts/NotoSansJP-Regular.ttf
- asset: assets/fonts/NotoSansJP-Medium.ttf
weight: 500
- asset: assets/fonts/NotoSansJP-Bold.ttf
weight: 700Step 2: flutter_test_config.dartでフォントをロード
Future<void> _loadFonts() async {
final notoSansJp = FontLoader('NotoSansJP')
..addFont(_loadFontData('assets/fonts/NotoSansJP-Regular.ttf'))
..addFont(_loadFontData('assets/fonts/NotoSansJP-Medium.ttf'))
..addFont(_loadFontData('assets/fonts/NotoSansJP-Bold.ttf'));
await notoSansJp.load();
}Step 3: テーマでフォントを指定
// テスト用のテーマ
ThemeData testTheme = ThemeData(
fontFamily: 'NotoSansJP',
// ...
);
// golden_test_helper.dart で使用
await tester.pumpGoldenWidget(
widget,
theme: testTheme,
);日本語非対応フォントの例
以下のフォントは日本語に対応していないため注意:
| フォント | 対応言語 | 備考 |
|---|---|---|
| Roboto | ラテン文字 | Material Designデフォルト |
| Open Sans | ラテン文字 | |
| Lato | ラテン文字 | |
| Montserrat | ラテン文字 |
日本語対応フォントの例
| フォント | 特徴 | 推奨用途 |
|---|---|---|
| Noto Sans JP | Google製、可読性高い | 一般的なUI |
| BIZ UDGothic | 視認性重視 | 業務アプリ |
| M PLUS 1p | モダンなデザイン | デザイン重視 |
| Kosugi Maru | 丸ゴシック | カジュアルなアプリ |
フォント設定のチェック方法
テスト実行前に、フォント設定が正しいか確認:
// デバッグ用: 現在ロードされているフォントを確認
debugPrint('Loaded fonts: ${fontLoader.fontFamily}');トラブルシューティングチェックリスト
1. [ ] flutter_test_config.dart でフォントをロードしているか 2. [ ] devicePixelRatio = 1.0 を設定しているか 3. [ ] debugShowCheckedModeBanner = false を設定しているか 4. [ ] テスト後に cleanUpGoldenTest() を呼んでいるか 5. [ ] 適切なサイズを設定しているか 6. [ ] CIとローカルで同じ環境を使用しているか 7. [ ] 日本語テキストに対応したフォントを使用しているか 8. [ ] ネットワーク画像をモックまたはダミーに置き換えているか 9. [ ] 問題が発生したら test/golden_test_issues.md に記録しているか
Golden Testの基礎知識
Golden Testとは
Golden Test(ゴールデンテスト)は、UIの視覚的な変更を検出するためのテスト手法です。 「Golden」は「正解」「基準」を意味し、事前に保存した正解画像(Goldenファイル)と現在の表示を比較します。
別名:
- Visual Regression Test(視覚的回帰テスト)
- Snapshot Test(スナップショットテスト)
- Screenshot Test(スクリーンショットテスト)
なぜGolden Testが必要か
1. 意図しないUI変更の検出
コードの変更が予期しないUIの変化を引き起こすことがあります:
- スタイルの変更(パディング、マージン、色など)
- レイアウトの崩れ
- フォントサイズの変更
- アイコンの変更
Golden Testは、これらの変更を自動的に検出します。
2. リファクタリングの安全性
コンポーネントのリファクタリング時に、見た目が変わっていないことを保証できます。
3. デザインシステムの一貫性
複数の開発者が同じコンポーネントを使用する際、一貫した見た目を維持できます。
4. ドキュメントとしての役割
Goldenファイルは、コンポーネントの各状態を視覚的に記録します。 新しいチームメンバーがUIの期待される見た目を理解するのに役立ちます。
FlutterでのGolden Testの仕組み
基本的な流れ
1. ウィジェットのレンダリング: tester.pumpWidget() でウィジェットを描画 2. スクリーンショットの取得: matchesGoldenFile() で画像を取得 3. 比較: 既存のGoldenファイルと比較(なければ新規作成) 4. 結果: 一致すればパス、不一致ならフェイル
主要なAPI
// Goldenファイルと比較
await expectLater(
find.byType(MyWidget),
matchesGoldenFile('path/to/golden.png'),
);Goldenファイルの管理
- 生成/更新:
flutter test --update-goldens - 検証:
flutter test
Goldenファイルの保存場所
Flutterでは、Goldenファイルは テストファイルからの相対パス で保存されます。
test/
├── widgets/
│ ├── my_button_test.dart
│ └── goldens/
│ └── components/
│ └── my_button/
│ ├── enabled.png
│ └── disabled.pngテストコード内での指定:
matchesGoldenFile('goldens/components/my_button/enabled.png')Golden Testの種類
1. コンポーネントテスト
個々のウィジェット(ボタン、カード、入力フィールドなど)をテスト。
- 小さいサイズ(400x100程度)
- 各状態を個別にテスト
2. 画面テスト
画面全体をテスト。
- 実際のデバイスサイズ(iPhone SE、iPhone 14など)
- ローディング、エラー、データ表示などの状態をテスト
3. レスポンシブテスト
複数のデバイスサイズで画面をテスト。
- 小画面、中画面、大画面
- レイアウトの崩れを検出
Golden Test vs ユニットテスト vs ウィジェットテスト
| 種類 | 対象 | 速度 | 目的 |
|---|---|---|---|
| ユニットテスト | ロジック | 高速 | 関数の動作検証 |
| ウィジェットテスト | ウィジェット | 中速 | インタラクション検証 |
| Golden Test | 見た目 | 低速 | 視覚的変更検出 |
Golden Testは他のテストを 補完 するものであり、置き換えるものではありません。
CI/CD環境での考慮事項
環境差異の問題
異なるOS(macOS、Linux、Windows)では、フォントレンダリングが異なる場合があります。 これにより、同じコードでもGoldenファイルが一致しないことがあります。
解決策
1. CIと同じ環境でGoldenファイルを生成: CI環境でのみGoldenを更新 2. フォントの固定: プロジェクトにフォントを含め、flutter_test_config.dart でロード 3. devicePixelRatioの固定: 1.0 に固定して一貫性を確保
参考リンク
Flutter Golden Test スキル
Flutter初心者でもGolden Test(Visual Regression Test)環境をセットアップし、コンポーネントや画面のGolden Testを作成・実行できるようにする対話型スキル。
使用方法
以下のいずれかのリクエストでスキルが起動します:
- 「Golden Testを導入したい」
- 「ゴールデンテストをセットアップ」
- 「スクリーンショットテストを作りたい」
- 「コンポーネントのGolden Testを書いて」
- 「Golden Testを実行」
機能
1. 初期セットアップモード
Golden Test環境がないプロジェクトに以下を自動生成:
test/flutter_test_config.dart- グローバルテスト設定test/helpers/golden_test_helper.dart- テストヘルパーtest/golden_test_issues.md- 問題解決ログ- CLAUDE.mdへの設定追記
対話で以下を確認:
- Golden Testの説明(初心者向け)
- 使用フォント
- Riverpod使用有無
- テーマ設定
- 画面サイズプリセット
- 画像アセット使用有無
- テスト作成粒度(コンポーネント/画面/都度確認)
2. テスト生成モード
既存のウィジェットに対してGolden Testを生成:
- コンポーネントテスト(ボタン、カード、入力フィールドなど)
- 画面テスト(Screen、Page)
- 各状態のテストケース自動生成
- 全状態比較テスト(all_states)
- ネットワーク画像の検出と対応案内
- 日本語フォントフォールバック問題の警告
3. テスト実行・更新モード
テストの実行方法を案内:
# Goldenファイルを生成/更新
flutter test --update-goldens path/to/test.dart
# テストを実行(検証)
flutter test path/to/test.dart4. トラブルシューティングモード
テスト画像がうまく生成されない場合の対応:
- 問題の特定と原因分析
- 解決策の提案
test/golden_test_issues.mdへの記録- CLAUDE.mdへの注意点追記(プロジェクト固有の知見として蓄積)
ファイル構造
flutter-golden-test/
├── SKILL.md # スキル定義
├── README.md # このファイル
├── knowledge/
│ ├── golden-test-concepts.md # 基礎知識
│ ├── common-issues.md # よくある問題
│ └── best-practices.md # ベストプラクティス
└── templates/
├── flutter_test_config.dart.md # テスト設定
├── golden_test_helper.dart.md # ヘルパー
├── golden_test_helper_riverpod.dart.md # Riverpod対応
├── component_golden_test.dart.md # コンポーネントテスト
├── screen_golden_test.dart.md # 画面テスト
└── golden_test_issues.md # 問題解決ログ生成されるファイル例
プロジェクト構造
test/
├── flutter_test_config.dart
├── helpers/
│ ├── helpers.dart
│ └── golden_test_helper.dart
└── ui/
└── components/
├── primary_button_golden_test.dart
└── goldens/
└── components/
└── primary_button/
├── enabled.png
├── disabled.png
└── all_states.pngテストコード例
testWidgets('有効状態', (tester) async {
await tester.pumpGoldenWidget(
PrimaryButton(onPressed: () {}, text: '保存'),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile(
GoldenFilePath.component('primary_button', 'enabled'),
),
);
await tester.cleanUpGoldenTest();
});対応機能
- [x] 日本語フォント対応
- [x] Riverpod対応(ProviderScopeラップ)
- [x] カスタムテーマ対応
- [x] コンポーネントテスト
- [x] 画面テスト
- [x] レスポンシブテスト(複数画面サイズ)
- [x] 全状態比較テスト
- [x] 画像・アイコンのプリロード設定
- [x] ネットワーク画像の検出と対応案内
- [x] フォントフォールバック問題の警告
- [x] 問題解決ログの自動記録
- [x] テスト作成粒度の設定
参考情報
- knowledge/golden-test-concepts.md - Golden Testの基礎
- knowledge/common-issues.md - よくある問題と解決方法
- knowledge/best-practices.md - ベストプラクティス
component_golden_test.dart テンプレート
概要
コンポーネント(ボタン、カード、入力フィールドなど)のGolden Testテンプレート。 各状態を個別にテストし、最後に全状態を一覧表示するパターン。
プレースホルダー
| プレースホルダー | 説明 | 例 |
|---|---|---|
{COMPONENT_IMPORT} | コンポーネントのインポートパス | package:your_app/ui/components/primary_button.dart |
{HELPER_IMPORT} | ヘルパーのインポートパス | ../../helpers/golden_test_helper.dart |
{COMPONENT_NAME} | コンポーネント名(表示用) | PrimaryButton |
{COMPONENT_SNAKE_CASE} | コンポーネント名(snake_case) | primary_button |
{TEST_CASES} | 各状態のテストケース | 下記参照 |
{ALL_STATES_WIDGETS} | 全状態のウィジェット一覧 | 下記参照 |
{LIST_WIDTH} | リスト表示時の幅 | 400 |
{LIST_HEIGHT} | リスト表示時の高さ | 400 |
テンプレート
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '{COMPONENT_IMPORT}';
import '{HELPER_IMPORT}';
void main() {
group('{COMPONENT_NAME} Golden Tests', () {
{TEST_CASES}
testWidgets('全状態の比較', (tester) async {
await tester.pumpGoldenWidgetList(
[
{ALL_STATES_WIDGETS}
],
surfaceSize: const Size({LIST_WIDTH}, {LIST_HEIGHT}),
);
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile(
GoldenFilePath.component('{COMPONENT_SNAKE_CASE}', 'all_states'),
),
);
await tester.cleanUpGoldenTest();
});
});
}TEST_CASES の例
ボタンコンポーネントの場合
testWidgets('有効状態', (tester) async {
await tester.pumpGoldenWidget(
PrimaryButton(
onPressed: () {},
text: '保存する',
),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile(
GoldenFilePath.component('primary_button', 'enabled'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('無効状態', (tester) async {
await tester.pumpGoldenWidget(
const PrimaryButton(
onPressed: null,
text: '保存する',
),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile(
GoldenFilePath.component('primary_button', 'disabled'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('アイコン付き', (tester) async {
await tester.pumpGoldenWidget(
PrimaryButton(
onPressed: () {},
text: '保存する',
icon: const Icon(Icons.save),
),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile(
GoldenFilePath.component('primary_button', 'with_icon'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('長いテキスト', (tester) async {
await tester.pumpGoldenWidget(
PrimaryButton(
onPressed: () {},
text: 'これは非常に長いテキストのボタンです',
),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile(
GoldenFilePath.component('primary_button', 'long_text'),
),
);
await tester.cleanUpGoldenTest();
});入力フィールドの場合
testWidgets('通常状態', (tester) async {
await tester.pumpGoldenWidget(
const CustomTextFormField(
labelText: 'メールアドレス',
),
);
await expectLater(
find.byType(CustomTextFormField),
matchesGoldenFile(
GoldenFilePath.component('custom_text_form_field', 'normal'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('エラー状態', (tester) async {
await tester.pumpGoldenWidget(
const CustomTextFormField(
labelText: 'メールアドレス',
errorText: '正しいメールアドレスを入力してください',
),
);
await expectLater(
find.byType(CustomTextFormField),
matchesGoldenFile(
GoldenFilePath.component('custom_text_form_field', 'error'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('無効状態', (tester) async {
await tester.pumpGoldenWidget(
const CustomTextFormField(
labelText: 'メールアドレス',
enabled: false,
),
);
await expectLater(
find.byType(CustomTextFormField),
matchesGoldenFile(
GoldenFilePath.component('custom_text_form_field', 'disabled'),
),
);
await tester.cleanUpGoldenTest();
});ALL_STATES_WIDGETS の例
// 有効状態
PrimaryButton(
onPressed: () {},
text: '有効',
),
// 無効状態
const PrimaryButton(
onPressed: null,
text: '無効',
),
// アイコン付き
PrimaryButton(
onPressed: () {},
text: 'アイコン付き',
icon: const Icon(Icons.save),
),完成例
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:your_app/ui/components/primary_button.dart';
import '../../helpers/golden_test_helper.dart';
void main() {
group('PrimaryButton Golden Tests', () {
testWidgets('有効状態', (tester) async {
await tester.pumpGoldenWidget(
PrimaryButton(
onPressed: () {},
text: '保存する',
),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile(
GoldenFilePath.component('primary_button', 'enabled'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('無効状態', (tester) async {
await tester.pumpGoldenWidget(
const PrimaryButton(
onPressed: null,
text: '保存する',
),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile(
GoldenFilePath.component('primary_button', 'disabled'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('全状態の比較', (tester) async {
await tester.pumpGoldenWidgetList(
[
PrimaryButton(
onPressed: () {},
text: '有効',
),
const PrimaryButton(
onPressed: null,
text: '無効',
),
],
surfaceSize: const Size(400, 200),
);
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile(
GoldenFilePath.component('primary_button', 'all_states'),
),
);
await tester.cleanUpGoldenTest();
});
});
}テスト実行コマンド
# Goldenファイルを生成/更新
flutter test --update-goldens test/ui/components/primary_button_golden_test.dart
# テストを実行(検証)
flutter test test/ui/components/primary_button_golden_test.dart注意事項
- 各テストケースの最後に必ず
cleanUpGoldenTest()を呼び出す - 状態名は snake_case で統一(例:
enabled,disabled,with_icon) all_statesテストの高さは状態数 × 各コンポーネント高さ + パディングで計算
flutter_test_config.dart テンプレート
概要
test/flutter_test_config.dart に配置するグローバルテスト設定ファイル。 全てのテストの前に自動的に実行され、フォントのロードなどを行う。
プレースホルダー
| プレースホルダー | 説明 | 例 |
|---|---|---|
{FONT_FAMILY} | フォントファミリー名 | Noto Sans JP |
{FONT_LOADERS} | フォントローダーの列挙 | 下記参照 |
{USE_IMAGES} | 画像アセットを使用するか | true / false |
{IMAGE_LOADERS} | 画像アセットローダー(画像使用時) | 下記参照 |
FONT_LOADERS の例
..addFont(_loadFontData('assets/fonts/NotoSansJP-Regular.ttf'))
..addFont(_loadFontData('assets/fonts/NotoSansJP-Medium.ttf'))
..addFont(_loadFontData('assets/fonts/NotoSansJP-Bold.ttf'))テンプレート
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
/// グローバルテスト設定
/// このファイルはtestディレクトリ直下に配置することで、
/// 全てのテストの前に自動的に実行されます。
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
TestWidgetsFlutterBinding.ensureInitialized();
// フォントをロード
await _loadFonts();
// テスト実行
await testMain();
}
/// {FONT_FAMILY}フォントをロードする
Future<void> _loadFonts() async {
final fontLoader = FontLoader('{FONT_FAMILY}')
{FONT_LOADERS};
await fontLoader.load();
}
/// フォントファイルをByteDataとして読み込む
Future<ByteData> _loadFontData(String path) async {
final file = File(path);
final bytes = await file.readAsBytes();
return ByteData.view(bytes.buffer);
}フォント設定不要の場合
フォント設定が不要な場合は、シンプルな設定を使用:
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
/// グローバルテスト設定
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
TestWidgetsFlutterBinding.ensureInitialized();
await testMain();
}複数フォントファミリーの場合
複数のフォントファミリーを使用する場合:
Future<void> _loadFonts() async {
// 日本語フォント
final notoSansJp = FontLoader('Noto Sans JP')
..addFont(_loadFontData('assets/fonts/NotoSansJP-Regular.ttf'))
..addFont(_loadFontData('assets/fonts/NotoSansJP-Bold.ttf'));
// アイコンフォント
final materialIcons = FontLoader('MaterialIcons')
..addFont(_loadFontData('assets/fonts/MaterialIcons-Regular.ttf'));
await Future.wait([
notoSansJp.load(),
materialIcons.load(),
]);
}画像アセットを使用する場合
画像アセットとアイコンフォントをテスト環境でロードする設定:
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
/// グローバルテスト設定
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
TestWidgetsFlutterBinding.ensureInitialized();
// フォントをロード
await _loadFonts();
// テスト実行
await testMain();
}
Future<void> _loadFonts() async {
// カスタムフォント
final customFont = FontLoader('{FONT_FAMILY}')
{FONT_LOADERS};
// Material Icons(アイコンフォント)
// Flutter SDKからMaterialIconsを取得
final materialIconsFont = FontLoader('MaterialIcons');
await Future.wait([
customFont.load(),
materialIconsFont.load(),
]);
}
/// フォントファイルをByteDataとして読み込む
Future<ByteData> _loadFontData(String path) async {
final file = File(path);
final bytes = await file.readAsBytes();
return ByteData.view(bytes.buffer);
}MaterialIconsのロードについて
MaterialIconsはFlutter SDKに含まれているため、特別な設定なしでもテスト環境で使用できることが多いですが、 アイコンが表示されない場合は上記のように明示的にロードしてください。
アイコンが空白で表示される場合、以下を確認: 1. pubspec.yaml に uses-material-design: true が設定されているか 2. アイコンフォントが正しくロードされているか
注意事項
- このファイルは必ず
test/ディレクトリ直下に配置する - ファイル名は必ず
flutter_test_config.dartにする testExecutable関数名は変更しない(Flutterが自動的に呼び出す)- 画像アセットは
rootBundle経由でロードする必要がある場合がある
golden_test_helper_riverpod.dart テンプレート
概要
Riverpodを使用するプロジェクト向けのGolden Testヘルパー。 ProviderScope でラップし、モックプロバイダーのオーバーライドをサポート。
標準ヘルパーからの追加点
pumpGoldenWidgetWithRiverpodメソッドpumpGoldenScreenWithRiverpodメソッドoverridesパラメータでモックプロバイダーを注入可能
テンプレート(Riverpod対応追加部分)
以下を golden_test_helper.dart に追加するか、別ファイルとして作成:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
{THEME_IMPORT}
/// Golden Test用のWidgetTester拡張(Riverpod対応)
extension GoldenTestRiverpodExtension on WidgetTester {
/// Riverpod対応のGolden Testウィジェットセットアップ
///
/// [widget] テスト対象のウィジェット
/// [overrides] プロバイダーのオーバーライド(モック用)
/// [surfaceSize] テスト画面サイズ
/// [theme] 使用するテーマ
Future<void> pumpGoldenWidgetWithRiverpod(
Widget widget, {
List<Override> overrides = const [],
Size surfaceSize = GoldenTestSizes.component,
ThemeData? theme,
}) async {
await binding.setSurfaceSize(surfaceSize);
view.physicalSize = surfaceSize;
view.devicePixelRatio = 1.0;
await pumpWidget(
ProviderScope(
overrides: overrides,
child: MaterialApp(
theme: theme ?? {DEFAULT_THEME},
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: widget,
),
),
),
),
),
);
await pump();
}
/// Riverpod対応の画面Golden Testセットアップ
///
/// 画面全体をテストする場合に使用
Future<void> pumpGoldenScreenWithRiverpod(
Widget screen, {
List<Override> overrides = const [],
Size surfaceSize = GoldenTestSizes.screenMedium,
ThemeData? theme,
}) async {
await binding.setSurfaceSize(surfaceSize);
view.physicalSize = surfaceSize;
view.devicePixelRatio = 1.0;
await pumpWidget(
ProviderScope(
overrides: overrides,
child: MaterialApp(
theme: theme ?? {DEFAULT_THEME},
debugShowCheckedModeBanner: false,
home: screen,
),
),
);
await pump();
}
/// Riverpod対応のウィジェットリストセットアップ
Future<void> pumpGoldenWidgetListWithRiverpod(
List<Widget> widgets, {
List<Override> overrides = const [],
Size surfaceSize = GoldenTestSizes.componentList,
ThemeData? theme,
}) async {
await binding.setSurfaceSize(surfaceSize);
view.physicalSize = surfaceSize;
view.devicePixelRatio = 1.0;
await pumpWidget(
ProviderScope(
overrides: overrides,
child: MaterialApp(
theme: theme ?? {DEFAULT_THEME},
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: widgets
.map(
(w) => Padding(
padding: const EdgeInsets.only(bottom: 16),
child: w,
),
)
.toList(),
),
),
),
),
),
);
await pump();
}
}使用例
基本的な使用
testWidgets('ログイン画面の表示', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const LoginScreen(),
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType(LoginScreen),
matchesGoldenFile(GoldenFilePath.screen('login_screen', 'initial')),
);
await tester.cleanUpGoldenTest();
});モックプロバイダーを使用
testWidgets('ログイン済み状態の表示', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const HomeScreen(),
overrides: [
// ユーザー認証状態をモック
authStateProvider.overrideWith(
(ref) => AuthState.authenticated(
user: User(name: 'テストユーザー'),
),
),
],
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType(HomeScreen),
matchesGoldenFile(GoldenFilePath.screen('home_screen', 'authenticated')),
);
await tester.cleanUpGoldenTest();
});複数の状態をテスト
testWidgets('ユーザーカード全状態', (tester) async {
await tester.pumpGoldenWidgetListWithRiverpod(
[
const UserCard(user: User(name: 'ユーザーA', isPremium: false)),
const UserCard(user: User(name: 'ユーザーB', isPremium: true)),
const UserCard(user: null), // 未ログイン状態
],
overrides: [
// 必要に応じてプロバイダーをオーバーライド
],
);
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile(GoldenFilePath.component('user_card', 'all_states')),
);
await tester.cleanUpGoldenTest();
});統合版テンプレート
標準ヘルパーとRiverpod対応を1ファイルにまとめる場合:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
{THEME_IMPORT}
/// Golden Test用の画面サイズ定義
class GoldenTestSizes {
GoldenTestSizes._();
static const Size component = Size(400, 100);
static const Size componentList = Size(400, 400);
static const Size screenSmall = Size(375, 667);
static const Size screenMedium = Size(390, 844);
static const Size screenLarge = Size(430, 932);
}
/// Golden Test用のWidgetTester拡張
extension GoldenTestExtension on WidgetTester {
// 標準メソッド(Riverpodなし)
Future<void> pumpGoldenWidget(
Widget widget, {
Size surfaceSize = GoldenTestSizes.component,
ThemeData? theme,
}) async {
// ... 標準実装 ...
}
// Riverpod対応メソッド
Future<void> pumpGoldenWidgetWithRiverpod(
Widget widget, {
List<Override> overrides = const [],
Size surfaceSize = GoldenTestSizes.component,
ThemeData? theme,
}) async {
// ... Riverpod実装 ...
}
Future<void> cleanUpGoldenTest() async {
await binding.setSurfaceSize(null);
}
}
/// Golden Testのファイルパスを生成するユーティリティ
class GoldenFilePath {
GoldenFilePath._();
static String component(String componentName, String stateName) {
return 'goldens/components/$componentName/$stateName.png';
}
static String screen(String screenName, String stateName) {
return 'goldens/screens/$screenName/$stateName.png';
}
}注意事項
flutter_riverpodパッケージがdev_dependenciesに必要Override型はflutter_riverpodから提供される- テスト用のモックプロバイダーは別ファイルで定義することを推奨
golden_test_helper.dart テンプレート
概要
test/helpers/golden_test_helper.dart に配置するGolden Test用ヘルパー。 画面サイズ定義、WidgetTester拡張、ファイルパスユーティリティを提供。
プレースホルダー
| プレースホルダー | 説明 | デフォルト値 |
|---|---|---|
{THEME_IMPORT} | テーマのインポート文 | 空(デフォルトテーマ使用時) |
{DEFAULT_THEME} | デフォルトテーマ | ThemeData.light() |
{COMPONENT_WIDTH} | コンポーネントサイズ幅 | 400 |
{COMPONENT_HEIGHT} | コンポーネントサイズ高さ | 100 |
{COMPONENT_LIST_WIDTH} | コンポーネントリスト幅 | 400 |
{COMPONENT_LIST_HEIGHT} | コンポーネントリスト高さ | 400 |
{SCREEN_SMALL_WIDTH} | 小画面幅 | 375 |
{SCREEN_SMALL_HEIGHT} | 小画面高さ | 667 |
{SCREEN_MEDIUM_WIDTH} | 中画面幅 | 390 |
{SCREEN_MEDIUM_HEIGHT} | 中画面高さ | 844 |
{SCREEN_LARGE_WIDTH} | 大画面幅 | 430 |
{SCREEN_LARGE_HEIGHT} | 大画面高さ | 932 |
テンプレート
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
{THEME_IMPORT}
/// Golden Test用の画面サイズ定義
class GoldenTestSizes {
GoldenTestSizes._();
/// コンポーネント単体テスト用の小さなサイズ
static const Size component = Size({COMPONENT_WIDTH}, {COMPONENT_HEIGHT});
/// コンポーネントをリスト表示するテスト用のサイズ
static const Size componentList = Size({COMPONENT_LIST_WIDTH}, {COMPONENT_LIST_HEIGHT});
/// 画面全体のテスト用サイズ(iPhone SE相当)
static const Size screenSmall = Size({SCREEN_SMALL_WIDTH}, {SCREEN_SMALL_HEIGHT});
/// 画面全体のテスト用サイズ(iPhone 14相当)
static const Size screenMedium = Size({SCREEN_MEDIUM_WIDTH}, {SCREEN_MEDIUM_HEIGHT});
/// 画面全体のテスト用サイズ(iPhone 14 Pro Max相当)
static const Size screenLarge = Size({SCREEN_LARGE_WIDTH}, {SCREEN_LARGE_HEIGHT});
}
/// Golden Test用のWidgetTester拡張
extension GoldenTestExtension on WidgetTester {
/// Golden Test用のウィジェットをセットアップ
///
/// [widget] テスト対象のウィジェット
/// [surfaceSize] テスト画面サイズ(デフォルト: コンポーネントサイズ)
/// [theme] 使用するテーマ(デフォルト: ライトテーマ)
Future<void> pumpGoldenWidget(
Widget widget, {
Size surfaceSize = GoldenTestSizes.component,
ThemeData? theme,
}) async {
await binding.setSurfaceSize(surfaceSize);
view.physicalSize = surfaceSize;
view.devicePixelRatio = 1.0;
await pumpWidget(
MaterialApp(
theme: theme ?? {DEFAULT_THEME},
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: widget,
),
),
),
),
);
await pump();
}
/// Golden Test用のウィジェットリストをセットアップ
///
/// 複数の状態を一度にキャプチャする場合に使用
Future<void> pumpGoldenWidgetList(
List<Widget> widgets, {
Size surfaceSize = GoldenTestSizes.componentList,
ThemeData? theme,
}) async {
await binding.setSurfaceSize(surfaceSize);
view.physicalSize = surfaceSize;
view.devicePixelRatio = 1.0;
await pumpWidget(
MaterialApp(
theme: theme ?? {DEFAULT_THEME},
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: widgets
.map(
(w) => Padding(
padding: const EdgeInsets.only(bottom: 16),
child: w,
),
)
.toList(),
),
),
),
),
);
await pump();
}
/// テスト後のクリーンアップ
Future<void> cleanUpGoldenTest() async {
await binding.setSurfaceSize(null);
}
}
/// Golden Testのファイルパスを生成するユーティリティ
class GoldenFilePath {
GoldenFilePath._();
/// コンポーネントのGoldenファイルパスを生成
///
/// 例: `goldens/components/custom_text_form_field/normal.png`
static String component(String componentName, String stateName) {
return 'goldens/components/$componentName/$stateName.png';
}
/// 画面のGoldenファイルパスを生成
///
/// 例: `goldens/screens/login_screen/initial.png`
static String screen(String screenName, String stateName) {
return 'goldens/screens/$screenName/$stateName.png';
}
}カスタムテーマ使用時の例
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:your_app/ui/core/themes/theme.dart';
// ... 上記テンプレートと同様 ...
// {DEFAULT_THEME} を AppTheme.light() に置換注意事項
devicePixelRatio = 1.0は必須(異なる環境でのスクリーンショット一貫性のため)debugShowCheckedModeBanner = falseでデバッグバナーを非表示- テスト後は必ず
cleanUpGoldenTest()を呼び出す
golden_test_issues.md テンプレート
概要
test/golden_test_issues.md に配置する問題解決ログファイル。 Golden Testで発生した問題とその解決方法を記録し、プロジェクト固有のナレッジとして蓄積する。
目的
- 同じ問題が発生した際に素早く対応できる
- プロジェクト固有の注意点を明文化する
- チームメンバー間で知見を共有する
テンプレート
# Golden Test 問題解決ログ
このファイルはGolden Testで発生した問題と解決方法を記録します。
同じ問題が発生した場合は、このファイルを参照してください。
---
## 問題一覧
| 日付 | 問題 | 原因 | 対応済み |
|-----|------|------|---------|
| | | | |
---
## 詳細記録
<!-- 問題が発生したら、以下のテンプレートをコピーして追記 -->
<!--
## YYYY-MM-DD: 問題の概要
### 問題
問題の詳細な説明
### 原因
原因の説明
### 解決方法
1. 手順1
2. 手順2
3. 手順3
### 今後の注意点
- 注意点1
- 注意点2
### 関連ファイル
- path/to/file.dart
-->記録例
## 2024-01-15: 日本語テキストが文字化けする
### 問題
PrimaryButtonのGolden Testで、日本語テキスト「保存する」が□□□と表示される。
### 原因
flutter_test_config.dartでNoto Sans JPフォントをロードしていなかった。
プロジェクトはRobotoをデフォルトフォントとして使用しており、
実機ではOSがフォールバックするが、テスト環境ではフォールバックが機能しない。
### 解決方法
1. assets/fonts/にNoto Sans JPフォントを追加
2. pubspec.yamlにフォントを登録
3. flutter_test_config.dartでフォントをロード
final notoSansJp = FontLoader('Noto Sans JP') ..addFont(_loadFontData('assets/fonts/NotoSansJP-Regular.ttf')); await notoSansJp.load();
### 今後の注意点
- 日本語を含むウィジェットのテストでは、必ず日本語対応フォントを設定する
- 新しいフォントを追加した場合は、flutter_test_config.dartも更新する
### 関連ファイル
- test/flutter_test_config.dart
- pubspec.yaml使用方法
1. 問題発生時: 問題の内容をAIアシスタントに伝える 2. 解決後: AIアシスタントがこのファイルに記録を追加 3. 参照時: 同様の問題が発生したら、このファイルを検索
注意事項
- 解決方法は具体的に、再現できるように記録する
- コード例がある場合は必ず含める
- 関連ファイルへのパスを明記する
- 機密情報(APIキーなど)は絶対に記録しない
screen_golden_test.dart テンプレート
概要
画面(Screen、Page)のGolden Testテンプレート。 複数の画面サイズと状態でテストし、レスポンシブ対応も検証。
プレースホルダー
| プレースホルダー | 説明 | 例 |
|---|---|---|
{SCREEN_IMPORT} | 画面のインポートパス | package:your_app/ui/screens/login_screen.dart |
{HELPER_IMPORT} | ヘルパーのインポートパス | ../../helpers/golden_test_helper.dart |
{SCREEN_NAME} | 画面名(表示用) | LoginScreen |
{SCREEN_SNAKE_CASE} | 画面名(snake_case) | login_screen |
{TEST_CASES} | 各状態のテストケース | 下記参照 |
{USE_RIVERPOD} | Riverpod使用有無 | true / false |
標準テンプレート(Riverpodなし)
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '{SCREEN_IMPORT}';
import '{HELPER_IMPORT}';
void main() {
group('{SCREEN_NAME} Golden Tests', () {
{TEST_CASES}
group('レスポンシブ対応', () {
testWidgets('iPhone SE(小画面)', (tester) async {
await tester.pumpGoldenWidget(
const {SCREEN_NAME}(),
surfaceSize: GoldenTestSizes.screenSmall,
);
await expectLater(
find.byType({SCREEN_NAME}),
matchesGoldenFile(
GoldenFilePath.screen('{SCREEN_SNAKE_CASE}', 'screen_small'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('iPhone 14(中画面)', (tester) async {
await tester.pumpGoldenWidget(
const {SCREEN_NAME}(),
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType({SCREEN_NAME}),
matchesGoldenFile(
GoldenFilePath.screen('{SCREEN_SNAKE_CASE}', 'screen_medium'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('iPhone 14 Pro Max(大画面)', (tester) async {
await tester.pumpGoldenWidget(
const {SCREEN_NAME}(),
surfaceSize: GoldenTestSizes.screenLarge,
);
await expectLater(
find.byType({SCREEN_NAME}),
matchesGoldenFile(
GoldenFilePath.screen('{SCREEN_SNAKE_CASE}', 'screen_large'),
),
);
await tester.cleanUpGoldenTest();
});
});
});
}Riverpod対応テンプレート
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import '{SCREEN_IMPORT}';
// モックプロバイダーがある場合
// import '../mocks/mock_providers.dart';
import '{HELPER_IMPORT}';
void main() {
group('{SCREEN_NAME} Golden Tests', () {
{TEST_CASES}
group('レスポンシブ対応', () {
testWidgets('iPhone SE(小画面)', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const {SCREEN_NAME}(),
overrides: [
// 必要に応じてプロバイダーをオーバーライド
],
surfaceSize: GoldenTestSizes.screenSmall,
);
await expectLater(
find.byType({SCREEN_NAME}),
matchesGoldenFile(
GoldenFilePath.screen('{SCREEN_SNAKE_CASE}', 'screen_small'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('iPhone 14(中画面)', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const {SCREEN_NAME}(),
overrides: [],
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType({SCREEN_NAME}),
matchesGoldenFile(
GoldenFilePath.screen('{SCREEN_SNAKE_CASE}', 'screen_medium'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('iPhone 14 Pro Max(大画面)', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const {SCREEN_NAME}(),
overrides: [],
surfaceSize: GoldenTestSizes.screenLarge,
);
await expectLater(
find.byType({SCREEN_NAME}),
matchesGoldenFile(
GoldenFilePath.screen('{SCREEN_SNAKE_CASE}', 'screen_large'),
),
);
await tester.cleanUpGoldenTest();
});
});
});
}TEST_CASES の例
ログイン画面の場合
testWidgets('初期表示', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const LoginScreen(),
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType(LoginScreen),
matchesGoldenFile(
GoldenFilePath.screen('login_screen', 'initial'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('入力中', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const LoginScreen(),
surfaceSize: GoldenTestSizes.screenMedium,
);
// メールアドレス入力
await tester.enterText(
find.byKey(const Key('email_field')),
'test@example.com',
);
await tester.pump();
await expectLater(
find.byType(LoginScreen),
matchesGoldenFile(
GoldenFilePath.screen('login_screen', 'with_email'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('バリデーションエラー', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const LoginScreen(),
overrides: [
// エラー状態をモック
loginFormStateProvider.overrideWith(
(ref) => LoginFormState(
emailError: 'メールアドレスを入力してください',
passwordError: 'パスワードを入力してください',
),
),
],
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType(LoginScreen),
matchesGoldenFile(
GoldenFilePath.screen('login_screen', 'validation_error'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('ローディング中', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const LoginScreen(),
overrides: [
loginStateProvider.overrideWith(
(ref) => const AsyncLoading(),
),
],
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType(LoginScreen),
matchesGoldenFile(
GoldenFilePath.screen('login_screen', 'loading'),
),
);
await tester.cleanUpGoldenTest();
});一覧画面の場合
testWidgets('データなし', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const ItemListScreen(),
overrides: [
itemListProvider.overrideWith((ref) => const AsyncData([])),
],
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType(ItemListScreen),
matchesGoldenFile(
GoldenFilePath.screen('item_list_screen', 'empty'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('データあり', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const ItemListScreen(),
overrides: [
itemListProvider.overrideWith(
(ref) => AsyncData([
Item(id: '1', name: 'アイテム1'),
Item(id: '2', name: 'アイテム2'),
Item(id: '3', name: 'アイテム3'),
]),
),
],
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType(ItemListScreen),
matchesGoldenFile(
GoldenFilePath.screen('item_list_screen', 'with_items'),
),
);
await tester.cleanUpGoldenTest();
});
testWidgets('エラー', (tester) async {
await tester.pumpGoldenScreenWithRiverpod(
const ItemListScreen(),
overrides: [
itemListProvider.overrideWith(
(ref) => AsyncError(Exception('データの取得に失敗しました'), StackTrace.current),
),
],
surfaceSize: GoldenTestSizes.screenMedium,
);
await expectLater(
find.byType(ItemListScreen),
matchesGoldenFile(
GoldenFilePath.screen('item_list_screen', 'error'),
),
);
await tester.cleanUpGoldenTest();
});生成されるGoldenファイル構造
test/ui/screens/login/goldens/
└── screens/
└── login_screen/
├── initial.png
├── with_email.png
├── validation_error.png
├── loading.png
├── screen_small.png
├── screen_medium.png
└── screen_large.pngテスト実行コマンド
# Goldenファイルを生成/更新
flutter test --update-goldens test/ui/screens/login/login_screen_golden_test.dart
# テストを実行(検証)
flutter test test/ui/screens/login/login_screen_golden_test.dart
# 全画面のGoldenテストを更新
flutter test --update-goldens test/ui/screens/注意事項
- 画面テストでは
surfaceSizeにスクリーンサイズを使用する - 非同期状態(ローディング、エラー)もテストする
- レスポンシブ対応の場合は複数サイズでテストする
- Riverpod使用時は適切なモックプロバイダーを準備する