flutter-add-integration-test について
flutter-add-integration-testは、Flutterプロジェクトに統合テストを追加して実行する手順を案内するスキルです。テストの設定と作成、デバイス・Webでの実行、パフォーマンス記録、CIの構成を扱います。
主な機能
- 01統合テスト依存関係の設定
- 02ウィジェット操作とアサーションの作成
- 03デバイス・Webでのテスト実行
- 04パフォーマンスタイムラインの取得
- 05CI統合テストの設定
活用シーン
- 01画面をまたぐアプリフローのテスト
- 02キー付きウィジェットの操作確認
- 03Chromeでの統合テスト実行
- 04テスト操作の性能分析
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dhruvanbhalara/ skills --skill flutter-add-integration-testインストール後、Claude Code で/flutter-add-integration-testを入力して読み込まれたか確認します。
詳細
目次
プロジェクトのセットアップ
-
必要な開発依存関係を
pubspec.yamlに追加します:flutter pub add 'dev:integration_test:{"sdk":"flutter"}' flutter pub add 'dev:flutter_test:{"sdk":"flutter"}' -
ディレクトリ構造を作成します:
project_root/ ├── integration_test/ │ └── app_test.dart # テストケース └── test_driver/ └── integration_test.dart # ホストドライバースクリプト -
ホストドライバースクリプトを
test_driver/integration_test.dartに作成します:import 'package:integration_test/integration_test_driver.dart'; Future<void> main() => integrationDriver(); -
確実にターゲット指定できるよう、本番コード内の重要なウィジェットに
ValueKeyを追加します:FloatingActionButton( key: const ValueKey('increment_fab'), onPressed: _increment, child: const Icon(Icons.add), )
テストの作成
main()の先頭でバインディングを初期化します。これはデフォルトのテストバインディングを置き換えます。tester.pumpWidget(const MyApp())でアプリケーション全体をロードします。- アニメーションや非同期処理を待つため、すべての操作の後に
tester.pumpAndSettle()を使用します。 expect(find.byKey(ValueKey('foo')), findsOneWidget)を使用してウィジェットの表示を検証します。- 画面外のウィジェットまでスクロールするには
tester.scrollUntilVisible(finder, 500.0)を使用します。
テストファイルの構造
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('End-to-end test', () {
testWidgets('complete user flow', (tester) async {
// Load full app
await tester.pumpWidget(const MyApp());
// Interact with widgets
await tester.tap(find.byKey(const ValueKey('login_button')));
await tester.pumpAndSettle();
// Assert navigation happened
expect(find.byType(HomePage), findsOneWidget);
});
});
}
実行ターゲット
対象プラットフォームに基づいて実行方法を選択します:
ローカルデバイス (Android/iOS)
flutter test integration_test/
iOS の場合、デフォルトの Swift Package Manager (SPM) 連携 (Flutter 3.44+) で実行する際は、先にパッケージ解決を実行してください:
xcodebuild -resolvePackageDependencies -workspace ios/Runner.xcworkspace -scheme Runner
Chrome (Web)
# Terminal 1: Start ChromeDriver
chromedriver --port=4444
# Terminal 2: Run tests
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/app_test.dart \
-d chrome
ヘッドレス Web
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/app_test.dart \
-d web-server
Firebase Test Lab (Android)
# 1. Build debug APK
flutter build apk --debug
# 2. Build instrumentation test APK
pushd android && ./gradlew app:assembleAndroidTest && popd
# 3. Upload both APKs to Firebase Test Lab via console or gcloud:
gcloud firebase test android run \
--type instrumentation \
--app build/app/outputs/flutter-apk/app-debug.apk \
--test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk
パフォーマンスプロファイリング
パフォーマンスのタイムラインを取得するには、テストアクションを binding.traceAction() でラップします:
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('scrolling performance', (tester) async {
await tester.pumpWidget(const MyApp());
await binding.traceAction(() async {
final listFinder = find.byType(Scrollable);
await tester.fling(listFinder, const Offset(0, -500), 10000);
await tester.pumpAndSettle();
}, reportKey: 'scrolling_timeline');
});
}
パフォーマンスプロファイリングドライバー
このドライバーを使用してタイムラインデータを取得し書き出します:
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline = driver.Timeline.fromJson(
data['scrolling_timeline'] as Map<String, dynamic>,
);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(
'scrolling_timeline',
pretty: true,
includeSummary: true,
);
}
},
);
}
CI/CD 連携
GitHub Actions ワークフロー
- name: Run integration tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
script: flutter test integration_test/ --flavor dev
- Android エミュレーターには
reactivecircus/android-emulator-runnerを使用します。 actions/upload-artifactでテスト結果のアーティファクトを収集します。- Web の場合は、
chromedriverをサービスとして実行し、-d chromeでテストします。
よくある落とし穴
| エラー | 原因 | 修正 |
|---|---|---|
PumpAndSettleTimedOutException | 無限アニメーション (例: CircularProgressIndicator) | 代わりに pump() を使用するか、読み込み状態を解除します |
| ウィジェットが見つからない | SliverList や ListView での遅延読み込み | 操作する前に scrollUntilVisible() を呼び出します |
| テストがハングする | 本番コード内のネットワーク呼び出し | HTTP クライアントをモックするか、--dart-define を使用して回避します |
No host driver specified | test_driver/integration_test.dart が存在しない | ホストドライバーファイルを作成します |
ワークフロー: 統合テストの追加
タスクの進捗
- ステップ 1:
integration_testとflutter_testをdev_dependenciesに追加します。 - ステップ 2: 本番コード内の対象ウィジェットに
ValueKeyを割り当てます。 - ステップ 3: バインディングの初期化を含む
integration_test/app_test.dartを作成します。 - ステップ 4:
integrationDriver()を含むtest_driver/integration_test.dartを作成します。 - ステップ 5: テストケースを作成します: アプリのロード、操作、検証。
- ステップ 6: 実行ターゲットを選択します:
- ローカルデバイス:
flutter test integration_test/ - Chrome:
flutter drive ... -d chrome - Firebase Test Lab: APK をビルドしてアップロード
- ローカルデバイス:
- ステップ 7: フィードバックループ:
PumpAndSettleTimedOutExceptionが発生した場合 → 無限アニメーションを確認します。- ウィジェットが見つからない場合 →
scrollUntilVisibleを追加します。 - すべてのテストがパスするまで再実行します。
例
標準的な統合テスト
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Counter app', () {
testWidgets('tap FAB, verify counter increments', (tester) async {
await tester.pumpWidget(const MyApp());
// Verify initial state
expect(find.text('0'), findsOneWidget);
// Tap the increment button
final fab = find.byKey(const ValueKey('increment_fab'));
await tester.tap(fab);
await tester.pumpAndSettle();
// Verify counter incremented
expect(find.text('1'), findsOneWidget);
});
});
}
Multi-Screen Navigation Flow
testWidgets('login and navigate to home', (tester) async {
await tester.pumpWidget(const MyApp());
// Enter credentials
await tester.enterText(find.byKey(const ValueKey('email_field')), '[email protected]');
await tester.enterText(find.byKey(const ValueKey('password_field')), 'password123');
// Submit login
await tester.tap(find.byKey(const ValueKey('login_button')));
await tester.pumpAndSettle();
// Verify navigation to home
expect(find.byType(HomePage), findsOneWidget);
expect(find.byType(LoginPage), findsNothing);
});
コメント
Markdown 対応