flutter-add-integration-test について
Flutter 開発者向けのインテグレーションテストガイドです。Dart/Flutter MCP server を使った UI と操作の探索を含め、設定、作成、実行方法を説明します。
主な機能
- 01依存関係とアプリのエントリーポイント設定
- 02MCP による Widget ツリーの確認
- 03タップ・入力・スクロールの再現
- 04エンドツーエンドテストの作成
- 05テスト実行とパフォーマンス計測
活用シーン
- 01テスト作成前のアプリ操作フロー探索
- 02Widget をまたぐユーザーフローの検証
- 03Chrome または Android でのテスト実行
- 04未マウント Widget やアニメーションのタイムアウト調査
MCP 対応
Dart/Flutter MCP server でアプリを起動し、Widget ツリーを確認して、タップ・テキスト入力・スクロールによる操作を試す方法を案内します。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ flutter/ agent-plugins --skill flutter-add-integration-testインストール後、Claude Code で/flutter-add-integration-testを入力して読み込まれたか確認します。
詳細
Flutter 統合テストの実装
目次
プロジェクトの設定と依存関係
統合テストと Flutter Driver 拡張機能をサポートするようにプロジェクトを設定します。
- 必要な開発依存関係を
pubspec.yamlに追加します。flutter pub add 'dev:integration_test:{"sdk":"flutter"}' flutter pub add 'dev:flutter_test:{"sdk":"flutter"}' - アプリケーションのエントリーポイント(通常は
lib/main.dartまたは専用のlib/main_test.dart)で Flutter Driver 拡張機能を有効にします。package:flutter_driver/driver_extension.dartをインポートします。runApp()の前にenableFlutterDriverExtension();を呼び出します。
- テスト中に確実に対象を特定できるよう、アプリケーションコード内の重要なウィジェットに
Keyパラメーター(例:ValueKey('login_button'))を追加します。
MCP による対話的な探索
静的テストを作成する前に、Dart/Flutter MCP サーバーのツールを使用してアプリケーションの状態を対話的に探索、操作します。
- 起動:
target: "lib/main_test.dart"を指定してlaunch_appを実行し、アプリケーションを起動して DTD URI を取得します。 - 検査:
get_widget_treeを実行し、利用可能なKey、Textノード、ウィジェットのTypeを確認します。 - 操作:
tap、enter_text、scrollを実行してユーザーフローをシミュレートします。 - 待機:画面遷移時やアニメーションの開始時には、必ず
waitForを実行するか、get_healthで状態を確認します。 - マウントされていないウィジェットのトラブルシューティング:ウィジェットがツリー内で見つからない場合、
SliverListまたはListViewで遅延読み込みされている可能性があります。操作する前にscrollまたはscrollIntoViewを実行してウィジェットをマウントします。
テスト作成のガイドライン
flutter_test API のパラダイムに従って統合テストを構成します。
- プロジェクトルートに専用の
integration_test/ディレクトリを作成します。 - すべてのテストファイルに
<name>_test.dartという命名規則を使用します。 main()の冒頭でIntegrationTestWidgetsFlutterBinding.ensureInitialized();を呼び出してバインディングを初期化します。await tester.pumpWidget(MyApp());を使用してアプリケーションの UI を読み込みます。tester.tap()などの操作の後にawait tester.pumpAndSettle();を使用してフレームを進め、アニメーションの完了を待ちます。expect(find.byKey(ValueKey('foo')), findsOneWidget);またはfindsNothingを使用してウィジェットの表示を検証します。await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder);を使用して画面外にある特定のウィジェットまでスクロールします。
レガシーな flutter_driver の条件付きロジック:
- レガシーな
flutter_driverテストを維持または移行する場合は、WidgetTesterAPI の代わりにdriver.waitFor()、driver.waitForAbsent()、driver.tap()、driver.scroll()を使用します。
実行とプロファイリング
flutter drive コマンドを使用してテストを実行します。integrationDriver() を呼び出すホストドライバースクリプトを test_driver/integration_test.dart に配置する必要があります。
実行対象の条件
- **Chrome でテストする場合:**別のターミナルで
chromedriver --port=4444を起動してから、次を実行します。flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome - ヘッドレス Web でテストする場合:
-d web-serverを指定して実行します。 - Android(ローカル)でテストする場合:
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dartを実行します。 - Firebase Test Lab(Android)でテストする場合:
- デバッグ APK をビルドします:
flutter build apk --debug - テスト APK をビルドします:
./gradlew app:assembleAndroidTest - 両方の APK を Firebase Test Lab コンソールにアップロードします。
- デバッグ APK をビルドします:
ワークフロー:エンドツーエンドの統合テスト
このチェックリストをコピーして、統合テストを実装・検証してください。
- タスクの進捗:セットアップ
-
pubspec.yamlにintegration_testとflutter_testを追加します。 - アプリのエントリーポイントに
enableFlutterDriverExtension()を組み込みます。 - 対象ウィジェットに
ValueKeys を割り当てます。
-
- タスクの進捗:調査
- MCP 経由で
launch_appを実行します。 -
get_widget_treeを使ってウィジェットツリーを把握します。 - MCP ツール(
tap、enter_text)を使って操作経路を検証します。
- MCP 経由で
- タスクの進捗:作成
-
integration_test/app_test.dartを作成します。 -
WidgetTesterAPI を使ってテストケースを記述します。 -
integrationDriver()を含むtest_driver/integration_test.dartを作成します。
-
- タスクの進捗:実行とフィードバックループ
-
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dartを実行します。 - フィードバックループ:テスト出力を確認します ->
PumpAndSettleTimedOutExceptionが発生した場合は、無限アニメーションがないか確認します -> ウィジェットが見つからない場合は、scrollUntilVisibleを追加します -> テストが成功するまで再実行します。
-
例
標準的な統合テスト(integration_test/app_test.dart)
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('tap on the floating action button, verify counter', (tester) async {
// Load app widget.
await tester.pumpWidget(const MyApp());
// Verify the counter starts at 0.
expect(find.text('0'), findsOneWidget);
// Find the floating action button to tap on.
final fab = find.byKey(const ValueKey('increment'));
// Emulate a tap on the floating action button.
await tester.tap(fab);
// Trigger a frame and wait for animations.
await tester.pumpAndSettle();
// Verify the counter increments by 1.
expect(find.text('1'), findsOneWidget);
});
});
}
ホストドライバースクリプト(test_driver/integration_test.dart)
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();
パフォーマンスプロファイリング用ドライバースクリプト(test_driver/perf_driver.dart)
テスト操作を binding.traceAction() でラップしてパフォーマンス指標を取得する場合は、このドライバースクリプトを使用します。
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,
);
}
},
);
}
コメント
Markdown 対応