mcpField
F
flutter-add-integration-test

flutter-add-integration-test

github.com/dhruvanbhalara测试与质量3050 次安装9 周前更新
代码仓库

关于 flutter-add-integration-test

flutter-add-integration-test 是指导开发者为 Flutter 项目添加和运行集成测试的技能,涵盖测试配置与编写、设备和 Web 目标、性能时间线及 CI 工作流。

主要功能

  1. 01配置集成测试依赖
  2. 02编写组件交互与断言
  3. 03在设备和 Web 上运行测试
  4. 04捕获性能时间线
  5. 05配置 CI 集成测试

适用场景

  1. 01测试跨屏幕应用流程
  2. 02验证带键组件的交互
  3. 03在 Chrome 中运行集成测试
  4. 04分析测试操作性能

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dhruvanbhalara/skills --skill flutter-add-integration-test

安装后在 Claude Code 中输入/flutter-add-integration-test确认已加载。

详细介绍

目录

项目设置

  1. 将所需的开发依赖项添加到 pubspec.yaml:

    flutter pub add 'dev:integration_test:{"sdk":"flutter"}'
    flutter pub add 'dev:flutter_test:{"sdk":"flutter"}'
    
  2. 创建目录结构:

    project_root/
    ├── integration_test/
    │   └── app_test.dart          # 测试用例
    └── test_driver/
        └── integration_test.dart  # 宿主驱动脚本
    
  3. 在 test_driver/integration_test.dart 创建宿主驱动脚本:

    import 'package:integration_test/integration_test_driver.dart';
    
    Future<void> main() => integrationDriver();
    
  4. 在生产代码的关键组件上添加 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
  • 使用 reactivecircus/android-emulator-runner 运行 Android 模拟器。
  • 使用 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:创建 test_driver/integration_test.dart 并添加 integrationDriver()。
  • 步骤 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