flutter-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chineseflutter-automated-testing
flutter-automated-testing
Goal
目标
Generates, configures, and debugs automated tests for Flutter applications, encompassing unit, widget, integration, and plugin testing. Analyzes architectural components (such as MVVM layers) to produce isolated, mock-driven tests and end-to-end device tests. Assumes a standard Flutter project structure, existing business logic, and familiarity with Dart testing paradigms.
为Flutter应用生成、配置和调试自动化测试,涵盖单元测试、Widget测试、集成测试和插件测试。分析架构组件(例如MVVM层)以生成隔离的、基于mock的测试以及端到端设备测试。假定你使用标准的Flutter项目结构、已有的业务逻辑,并且熟悉Dart测试范式。
Instructions
使用说明
1. Determine Test Type (Decision Logic)
1. 确定测试类型(决策逻辑)
Evaluate the user's target code to determine the appropriate testing strategy using the following decision tree:
- If verifying a single function, method, ViewModel, or Repository: Implement a Unit Test (Proceed to Step 2).
- If verifying a single widget's UI, layout, or interaction: Implement a Widget Test (Proceed to Step 3).
- If verifying complete app behavior, routing, or performance on a device: Implement an Integration Test (Proceed to Step 4).
- If verifying platform-specific native code (MethodChannels): Implement a Plugin Test (Proceed to Step 5).
STOP AND ASK THE USER: "Which specific class, widget, or flow are we testing today? Please provide the relevant source code if you haven't already."
使用以下决策树评估用户的目标代码,以确定合适的测试策略:
- 如果要验证单个函数、方法、ViewModel或Repository: 实现单元测试(进入步骤2)。
- 如果要验证单个widget的UI、布局或交互: 实现Widget测试(进入步骤3)。
- 如果要验证完整的应用行为、路由或在设备上的性能: 实现集成测试(进入步骤4)。
- 如果要验证平台特定的原生代码(MethodChannels): 实现插件测试(进入步骤5)。
请停下来询问用户: "我们今天要测试哪个具体的类、widget或流程?如果你还没有提供相关源代码,请提供一下。"
2. Implement Unit Tests (Logic & Architecture)
2. 实现单元测试(逻辑与架构)
Unit tests verify logic without rendering UI. They must reside in the directory and end with .
test/_test.dart- For ViewModels (UI Layer Logic): Fake the repository dependencies. Do not rely on Flutter UI libraries.
dart
import 'package:test/test.dart';
// Import your ViewModel and Fakes here
void main() {
group('HomeViewModel tests', () {
test('Load bookings successfully', () {
final viewModel = HomeViewModel(
bookingRepository: FakeBookingRepository()..createBooking(kBooking),
userRepository: FakeUserRepository(),
);
expect(viewModel.bookings.isNotEmpty, true);
});
});
}- For Repositories (Data Layer Logic): Fake the API clients or local database services.
dart
import 'package:test/test.dart';
// Import your Repository and Fakes here
void main() {
group('BookingRepositoryRemote tests', () {
late BookingRepository bookingRepository;
late FakeApiClient fakeApiClient;
setUp(() {
fakeApiClient = FakeApiClient();
bookingRepository = BookingRepositoryRemote(apiClient: fakeApiClient);
});
test('should get booking', () async {
final result = await bookingRepository.getBooking(0);
final booking = result.asOk.value;
expect(booking, kBooking);
});
});
}单元测试在不渲染UI的情况下验证逻辑。它们必须存放在目录下,且文件名以结尾。
test/_test.dart- 针对ViewModel(UI层逻辑): 伪造repository依赖,不要依赖Flutter UI库。
dart
import 'package:test/test.dart';
// Import your ViewModel and Fakes here
void main() {
group('HomeViewModel tests', () {
test('Load bookings successfully', () {
final viewModel = HomeViewModel(
bookingRepository: FakeBookingRepository()..createBooking(kBooking),
userRepository: FakeUserRepository(),
);
expect(viewModel.bookings.isNotEmpty, true);
});
});
}- 针对Repository(数据层逻辑): 伪造API客户端或本地数据库服务。
dart
import 'package:test/test.dart';
// Import your Repository and Fakes here
void main() {
group('BookingRepositoryRemote tests', () {
late BookingRepository bookingRepository;
late FakeApiClient fakeApiClient;
setUp(() {
fakeApiClient = FakeApiClient();
bookingRepository = BookingRepositoryRemote(apiClient: fakeApiClient);
});
test('should get booking', () async {
final result = await bookingRepository.getBooking(0);
final booking = result.asOk.value;
expect(booking, kBooking);
});
});
}3. Implement Widget Tests (UI Components)
3. 实现Widget测试(UI组件)
Widget tests verify UI rendering and interaction. They must reside in the directory and use the package.
test/flutter_test- Use to build the widget.
WidgetTester - Use to locate elements (
Finder,find.text(),find.byKey()).find.byWidget() - Use to verify existence (
Matcher,findsOneWidget,findsNothing).findsNWidgets
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('HomeScreen displays title and handles tap', (WidgetTester tester) async {
// 1. Setup Fakes and ViewModel
final bookingRepository = FakeBookingRepository()..createBooking(kBooking);
final viewModel = HomeViewModel(
bookingRepository: bookingRepository,
userRepository: FakeUserRepository(),
);
// 2. Build the Widget tree
await tester.pumpWidget(
MaterialApp(
home: HomeScreen(viewModel: viewModel),
),
);
// 3. Finders
final titleFinder = find.text('Home');
final buttonFinder = find.byKey(const Key('increment_button'));
// 4. Assertions
expect(titleFinder, findsOneWidget);
// 5. Interactions
await tester.tap(buttonFinder);
await tester.pumpAndSettle(); // Wait for animations/state updates to finish
expect(find.text('1'), findsOneWidget);
});
}Widget测试验证UI渲染和交互。它们必须存放在目录下,且使用包。
test/flutter_test- 使用构建widget。
WidgetTester - 使用定位元素(
Finder、find.text()、find.byKey())。find.byWidget() - 使用验证存在性(
Matcher、findsOneWidget、findsNothing)。findsNWidgets
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('HomeScreen displays title and handles tap', (WidgetTester tester) async {
// 1. Setup Fakes and ViewModel
final bookingRepository = FakeBookingRepository()..createBooking(kBooking);
final viewModel = HomeViewModel(
bookingRepository: bookingRepository,
userRepository: FakeUserRepository(),
);
// 2. Build the Widget tree
await tester.pumpWidget(
MaterialApp(
home: HomeScreen(viewModel: viewModel),
),
);
// 3. Finders
final titleFinder = find.text('Home');
final buttonFinder = find.byKey(const Key('increment_button'));
// 4. Assertions
expect(titleFinder, findsOneWidget);
// 5. Interactions
await tester.tap(buttonFinder);
await tester.pumpAndSettle(); // Wait for animations/state updates to finish
expect(find.text('1'), findsOneWidget);
});
}4. Implement Integration Tests (End-to-End)
4. 实现集成测试(端到端)
Integration tests run on real devices or emulators. They must reside in the directory.
integration_test/- Ensure is in
integration_testindev_dependencies.pubspec.yaml - Initialize .
IntegrationTestWidgetsFlutterBinding
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' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('End-to-End App Test', () {
testWidgets('Full flow: tap FAB and verify counter', (WidgetTester tester) async {
// Load the full app
app.main();
await tester.pumpAndSettle();
// Verify initial state
expect(find.text('0'), findsOneWidget);
// Find and tap the FAB
final fab = find.byKey(const ValueKey('increment'));
await tester.tap(fab);
// Trigger a frame
await tester.pumpAndSettle();
// Verify state change
expect(find.text('1'), findsOneWidget);
});
});
}集成测试在真实设备或模拟器上运行。它们必须存放在目录下。
integration_test/- 确保的
pubspec.yaml中包含dev_dependencies。integration_test - 初始化。
IntegrationTestWidgetsFlutterBinding
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' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('End-to-End App Test', () {
testWidgets('Full flow: tap FAB and verify counter', (WidgetTester tester) async {
// Load the full app
app.main();
await tester.pumpAndSettle();
// Verify initial state
expect(find.text('0'), findsOneWidget);
// Find and tap the FAB
final fab = find.byKey(const ValueKey('increment'));
await tester.tap(fab);
// Trigger a frame
await tester.pumpAndSettle();
// Verify state change
expect(find.text('1'), findsOneWidget);
});
});
}5. Implement Plugin Tests (Native & Dart)
5. 实现插件测试(原生与Dart)
If testing a plugin, tests must cover both Dart and Native communication.
- Dart Side: Mock the platform channel and call the plugin's public API.
- Native Side: Instruct the user to write native tests in the respective directories:
- Android: (JUnit)
android/src/test/ - iOS/macOS: (XCTest)
example/ios/RunnerTests/ - Linux/Windows: (GoogleTest)
linux/test/
- Android:
如果要测试插件,测试必须覆盖Dart和原生通信两部分。
- Dart侧: mock平台通道,调用插件的公开API。
- 原生侧: 指导用户在对应目录下编写原生测试:
- Android: (JUnit)
android/src/test/ - iOS/macOS: (XCTest)
example/ios/RunnerTests/ - Linux/Windows: (GoogleTest)
linux/test/
- Android:
6. Validate and Fix (Feedback Loop)
6. 验证与修复(反馈循环)
Provide the user with the exact command to run the generated test:
- Unit/Widget:
flutter test test/your_test_file.dart - Integration:
flutter test integration_test/your_test_file.dart
STOP AND ASK THE USER: "Please run the test using the command above and paste the output. If the test fails, provide the stack trace so I can analyze the failure and generate a fix."
为用户提供运行生成的测试的准确命令:
- 单元/Widget测试:
flutter test test/your_test_file.dart - 集成测试:
flutter test integration_test/your_test_file.dart
请停下来询问用户: "请使用上述命令运行测试并粘贴输出。如果测试失败,请提供堆栈跟踪,以便我分析失败原因并生成修复方案。"
Constraints
约束条件
- Single Source of Truth: Do not duplicate state in tests. Always use fakes or mocks for external dependencies (Repositories, Services) to isolate the unit under test.
- No Logic in Widgets: When writing widget tests, assume the widget is "dumb". All business logic should be tested via the ViewModel/Controller unit tests.
- File Naming: All test files MUST end with .
_test.dart - Pump vs PumpAndSettle: Use for single frame advances. Use
tester.pump()strictly when waiting for animations or asynchronous UI updates to complete.tester.pumpAndSettle() - Immutability: Treat test data models as immutable. Create new instances for state changes rather than mutating existing mock data.
- Do not use : Flutter does not support reflection. Rely on code generation (e.g.,
dart:mirrors,build_runner,mockito) for mocking.mocktail
- 单一数据源: 不要在测试中重复状态。始终为外部依赖(Repositories、Services)使用伪造或mock对象,以隔离被测单元。
- Widget中不要包含逻辑: 编写Widget测试时,假定Widget是「哑组件」。所有业务逻辑都应该通过ViewModel/Controller的单元测试进行验证。
- 文件命名: 所有测试文件必须以结尾。
_test.dart - Pump与PumpAndSettle的区别: 单帧推进时使用。仅当需要等待动画或异步UI更新完成时,才使用
tester.pump()。tester.pumpAndSettle() - 不可变性: 将测试数据模型视为不可变的。为状态变化创建新实例,而不是修改现有的mock数据。
- 不要使用: Flutter不支持反射。依赖代码生成工具(例如
dart:mirrors、build_runner、mockito)来实现mock。mocktail