flutter-testing-skill について
flutter-testing-skillは、Flutter開発者向けにユニット、ウィジェット、統合、ゴールデンテストを解説します。Dartの例やコマンドに加え、Finder、操作、依存関係のモックに関する指針を提供します。
主な機能
- 01ユニット・ウィジェット・統合テストのガイド
- 02ウィジェットのFinderと操作パターン
- 03ビジュアルリグレッション用ゴールデンテスト
- 04Mockitoによる依存関係のモック
- 05テスト実行と管理のコマンド
活用シーン
- 01Flutter UIの動作と操作のテスト
- 02画面とゴールデン画像の比較
- 03統合テストの実行やモックの生成
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ lambdatest/ agent-skills --skill flutter-testing-skillインストール後、Claude Code で/flutter-testing-skillを入力して読み込まれたか確認します。
同梱ファイルreference/advanced-patterns.mdreference/playbook.md
詳細
Flutter テストスキル
あなたはテストを専門とするシニア Flutter 開発者です。
ステップ 1 — テストの種類
├─ "unit test", "business logic", "model test"
│ └─ Unit test: test/ directory, flutter_test package
│
├─ "widget test", "component test", "UI test"
│ └─ Widget test: test/ directory, testWidgets()
│
├─ "integration test", "E2E", "full app test"
│ └─ Integration test: integration_test/ directory
│
├─ "golden test", "snapshot", "visual regression"
│ └─ Golden test: matchesGoldenFile()
│
└─ Ambiguous? → Widget test (most common)
基本パターン — Dart
ウィジェットテスト(最も一般的)
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/screens/login_screen.dart';
void main() {
testWidgets('Login screen shows email and password fields', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: LoginScreen()));
// Verify fields exist
expect(find.byType(TextField), findsNWidgets(2));
expect(find.text('Email'), findsOneWidget);
expect(find.text('Password'), findsOneWidget);
expect(find.byType(ElevatedButton), findsOneWidget);
});
testWidgets('Login with valid credentials navigates to dashboard', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: LoginScreen()));
// Enter credentials
await tester.enterText(find.byKey(const Key('emailField')), '[email protected]');
await tester.enterText(find.byKey(const Key('passwordField')), 'password123');
// Tap login button
await tester.tap(find.byKey(const Key('loginButton')));
await tester.pumpAndSettle(); // Wait for animations and navigation
// Verify navigation
expect(find.text('Dashboard'), findsOneWidget);
});
testWidgets('Shows error for invalid credentials', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: LoginScreen()));
await tester.enterText(find.byKey(const Key('emailField')), '[email protected]');
await tester.enterText(find.byKey(const Key('passwordField')), 'wrong');
await tester.tap(find.byKey(const Key('loginButton')));
await tester.pumpAndSettle();
expect(find.text('Invalid credentials'), findsOneWidget);
});
}
Finder の戦略
// By Key (best — explicit test identifiers)
find.byKey(const Key('loginButton'))
find.byKey(const ValueKey('email_input'))
// By Type
find.byType(ElevatedButton)
find.byType(TextField)
find.byType(LoginScreen)
// By Text
find.text('Login')
find.textContaining('Welcome')
// By Icon
find.byIcon(Icons.login)
// By Widget predicate
find.byWidgetPredicate((widget) => widget is Text && widget.data!.startsWith('Error'))
// Descendant/Ancestor
find.descendant(of: find.byType(AppBar), matching: find.text('Title'))
find.ancestor(of: find.text('Login'), matching: find.byType(Card))
操作
await tester.tap(finder); // Tap
await tester.longPress(finder); // Long press
await tester.enterText(finder, 'text'); // Type text
await tester.drag(finder, const Offset(0, -300)); // Drag/scroll
await tester.fling(finder, const Offset(0, -500), 1000); // Fling/swipe
// CRITICAL: Always pump after actions
await tester.pump(); // Single frame
await tester.pump(const Duration(seconds: 1)); // Advance time
await tester.pumpAndSettle(); // Wait for animations to finish
統合テスト
// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Full login flow', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
// Login
await tester.enterText(find.byKey(const Key('emailField')), '[email protected]');
await tester.enterText(find.byKey(const Key('passwordField')), 'password123');
await tester.tap(find.byKey(const Key('loginButton')));
await tester.pumpAndSettle();
// Verify dashboard
expect(find.text('Dashboard'), findsOneWidget);
// Navigate to settings
await tester.tap(find.byIcon(Icons.settings));
await tester.pumpAndSettle();
expect(find.text('Settings'), findsOneWidget);
});
}
ゴールデンテスト(ビジュアルリグレッション)
testWidgets('Login screen matches golden', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: LoginScreen()));
await tester.pumpAndSettle();
await expectLater(
find.byType(LoginScreen),
matchesGoldenFile('goldens/login_screen.png'),
);
});
# Generate golden files
flutter test --update-goldens
# Run golden comparison
flutter test
依存関係のモック
// Using Mockito
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([AuthService])
void main() {
late MockAuthService mockAuth;
setUp(() {
mockAuth = MockAuthService();
});
testWidgets('Login calls auth service', (tester) async {
when(mockAuth.login(any, any)).thenAnswer((_) async => true);
await tester.pumpWidget(MaterialApp(
home: LoginScreen(authService: mockAuth),
));
await tester.enterText(find.byKey(const Key('emailField')), '[email protected]');
await tester.enterText(find.byKey(const Key('passwordField')), 'pass123');
await tester.tap(find.byKey(const Key('loginButton')));
await tester.pumpAndSettle();
verify(mockAuth.login('[email protected]', 'pass123')).called(1);
});
}
アンチパターン
| 悪い例 | 良い例 | 理由 |
|---|---|---|
操作後に pumpAndSettle() を呼び出さない | 操作後は必ず pump する | アニメーションが完了しない |
動的なテキストに find.text() を使う | find.byKey() を使う | ロケールやテキストの変更でテストが壊れる |
| 実装の詳細をテストする | ユーザーが使う機能をテストする | 壊れやすい |
| ウィジェットテストでモックを使わない | サービスやリポジトリをモックする | テストが実際の API を呼び出す |
TestMu AI Cloud(統合テスト)
# Run integration tests on LambdaTest real devices
# 1. Build app for testing
flutter build apk --debug # Android
flutter build ios --simulator # iOS
# 2. Upload to LambdaTest
curl -u "$LT_USERNAME:$LT_ACCESS_KEY" \
-X POST "https://manual-api.lambdatest.com/app/upload/realDevice" \
-F "appFile=@build/app/outputs/flutter-apk/app-debug.apk"
# 3. Run via Appium (Flutter driver)
# Use appium-flutter-driver for element interaction
クイックリファレンス
| タスク | コマンド |
|---|---|
| すべてのテストを実行 | flutter test |
| 特定のファイルを実行 | flutter test test/login_test.dart |
| カバレッジ付きで実行 | flutter test --coverage |
| 統合テストを実行 | flutter test integration_test/ |
| ゴールデンを更新 | flutter test --update-goldens |
| モックを生成 | flutter pub run build_runner build |
| 特定のプラットフォームでテスト | flutter test --platform chrome |
pubspec.yaml
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
mockito: ^5.4.0
build_runner: ^2.4.0
詳細なパターン
高度なパターン、デバッグガイド、CI/CD との統合、ベストプラクティスについては、
reference/playbook.md を参照してください。
コメント
Markdown 対応