跳转到正文
YGJoon 技术博客

Flutter 集成测试详解

发布于:
·1.5k 字 · 约 4 分钟·作者:
integration_test 包跑真机全链路:从输入账号密码点登录,到接口返回后页面跳转,端到端验证真实流程
Flutter 集成测试详解文章封面
目录

简介

集成测试用于测试应用的完整功能流程。本文介绍如何在 Flutter 中编写集成测试。

基本配置

添加依赖

dev_dependencies:
  integration_test:
    sdk: flutter
  flutter_test:
    sdk: flutter

创建测试文件

// 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';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('Complete login flow test', (tester) async {
    await tester.pumpWidget(MyApp());
    await tester.pumpAndSettle();
    
    // 测试登录流程
  });
}

测试方法

页面交互

testWidgets('Login flow', (tester) async {
  await tester.pumpWidget(MyApp());
  await tester.pumpAndSettle();
  
  // 输入用户名
  await tester.enterText(
    find.byKey(Key('username_field')),
    'testuser',
  );
  
  // 输入密码
  await tester.enterText(
    find.byKey(Key('password_field')),
    'password123',
  );
  
  // 点击登录按钮
  await tester.tap(find.byKey(Key('login_button')));
  await tester.pumpAndSettle();
  
  // 验证登录成功
  expect(find.text('Welcome, testuser'), findsOneWidget);
});

网络请求

testWidgets('API integration test', (tester) async {
  await tester.pumpWidget(MyApp());
  
  // 触发数据加载
  await tester.tap(find.byKey(Key('load_data_button')));
  await tester.pumpAndSettle();
  
  // 验证加载状态
  expect(find.byType(CircularProgressIndicator), findsOneWidget);
  
  // 等待数据加载完成
  await tester.pumpAndSettle(Duration(seconds: 3));
  
  // 验证数据显示
  expect(find.byType(ListTile), findsNWidgets(10));
});

完整示例

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('End-to-end test', () {
    testWidgets('Complete user flow', (tester) async {
      // 启动应用
      await tester.pumpWidget(MyApp());
      await tester.pumpAndSettle();
      
      // 1. 登录流程
      await _loginFlow(tester);
      
      // 2. 创建新帖子
      await _createPostFlow(tester);
      
      // 3. 查看帖子列表
      await _viewPostsFlow(tester);
      
      // 4. 编辑个人资料
      await _editProfileFlow(tester);
      
      // 5. 退出登录
      await _logoutFlow(tester);
    });
  });
}

Future<void> _loginFlow(WidgetTester tester) async {
  // 输入登录信息
  await tester.enterText(
    find.byKey(Key('email_field')),
    '[email protected]',
  );
  await tester.enterText(
    find.byKey(Key('password_field')),
    'password123',
  );
  
  // 点击登录按钮
  await tester.tap(find.byKey(Key('login_button')));
  await tester.pumpAndSettle();
  
  // 验证登录成功
  expect(find.text('Welcome'), findsOneWidget);
}

Future<void> _createPostFlow(WidgetTester tester) async {
  // 点击创建按钮
  await tester.tap(find.byKey(Key('create_post_button')));
  await tester.pumpAndSettle();
  
  // 输入帖子内容
  await tester.enterText(
    find.byKey(Key('post_title_field')),
    'Test Post Title',
  );
  await tester.enterText(
    find.byKey(Key('post_content_field')),
    'Test post content',
  );
  
  // 提交帖子
  await tester.tap(find.byKey(Key('submit_post_button')));
  await tester.pumpAndSettle();
  
  // 验证帖子创建成功
  expect(find.text('Post created successfully'), findsOneWidget);
}

Future<void> _viewPostsFlow(WidgetTester tester) async {
  // 滚动列表
  await tester.fling(
    find.byType(ListView),
    Offset(0, -500),
    3000,
  );
  await tester.pumpAndSettle();
  
  // 点击帖子
  await tester.tap(find.text('Test Post Title'));
  await tester.pumpAndSettle();
  
  // 验证帖子详情
  expect(find.text('Test post content'), findsOneWidget);
}

Future<void> _editProfileFlow(WidgetTester tester) async {
  // 导航到个人资料页
  await tester.tap(find.byKey(Key('profile_button')));
  await tester.pumpAndSettle();
  
  // 点击编辑按钮
  await tester.tap(find.byKey(Key('edit_profile_button')));
  await tester.pumpAndSettle();
  
  // 更新个人信息
  await tester.enterText(
    find.byKey(Key('name_field')),
    'Updated Name',
  );
  
  // 保存更改
  await tester.tap(find.byKey(Key('save_profile_button')));
  await tester.pumpAndSettle();
  
  // 验证更新成功
  expect(find.text('Profile updated'), findsOneWidget);
}

Future<void> _logoutFlow(WidgetTester tester) async {
  // 点击退出按钮
  await tester.tap(find.byKey(Key('logout_button')));
  await tester.pumpAndSettle();
  
  // 确认退出
  await tester.tap(find.text('Yes'));
  await tester.pumpAndSettle();
  
  // 验证返回登录页
  expect(find.byKey(Key('login_button')), findsOneWidget);
}

性能测试

测量启动时间

testWidgets('Measure app startup time', (tester) async {
  final stopwatch = Stopwatch()..start();
  
  await tester.pumpWidget(MyApp());
  await tester.pumpAndSettle();
  
  stopwatch.stop();
  print('App startup time: ${stopwatch.elapsedMilliseconds}ms');
});

测量页面切换性能

testWidgets('Measure page transition performance', (tester) async {
  await tester.pumpWidget(MyApp());
  await tester.pumpAndSettle();
  
  final stopwatch = Stopwatch()..start();
  
  // 触发页面切换
  await tester.tap(find.byKey(Key('next_page_button')));
  await tester.pumpAndSettle();
  
  stopwatch.stop();
  print('Page transition time: ${stopwatch.elapsedMilliseconds}ms');
});

最佳实践

  1. 只覆盖核心流程:登录、下单、支付这 3 条(实战案例里说的)。集成测试跑一次几分钟,全量回归 20 条流程就是半小时——贪多等于没人愿意跑。
  2. 一个用例一条完整用户路径:_loginFlow→_createPostFlow→…拆成 helper 函数(正文完整示例就是这么组织的),主用例读起来是用户故事,挂了能直接定位到哪一步。
  3. 测试数据要自包含:用固定测试账号(如 [email protected]/password123),用例开头保证数据状态(没有就创建),结尾清理。依赖”数据库里已经有条帖子”的测试,换个环境就挂。
  4. 等待用 pumpAndSettle 而不是固定 sleep:正文网络请求示例里 pumpAndSettle(Duration(seconds: 3)) 是反面教材——网络快了白等 3 秒,慢了 3 秒不够。等目标元素出现(waitFor)才是正解。
  5. 失败时截图:integration_test 挂掉时把当前屏幕 dump 出来。CI 上”expect 失败”配一张截图,定位速度差一个数量级——不然你只能对着日志猜。

注意事项

  1. 跑在真机/模拟器上,不是 flutter test:integration_test 需要 flutter test integration_test 起设备(正文基本配置已写)。CI 要配 macOS runner 跑模拟器或接真机农场——这正是小团队没写集成测试的真实原因(实战案例)。
  2. 超时设置要宽容:真机启动+网络请求,一个用例给 1-2 分钟超时。CI 机器性能波动大,按本地最快速度设超时等于定期误报。
  3. 别在集成测试里测分支:20 种表单校验组合是单元测试的活(见单元测试篇)。集成测试只走主线 happy path + 一条错误路径(如登录失败),分支覆盖靠下层测试。
  4. 设备兼容性抽样就行:不需要 20 台手机全量跑——选一台低端机(千元机)+ 一台主流机。低端机负责暴露性能问题和布局溢出,主流机负责功能。
  5. 网络要可控:连真实后端测的是”端到端”,但 CI 应该打 staging 环境。直连生产等于用测试账号污染真实数据——删不掉的那种。

实战案例

集成测试(integration_test,真机上跑完整流程)我还没写过。最接近的是 电商奢侈品专区改版 的 widget 测试——但它跑在 flutter test 里,是伪造环境,不是真机。没写的原因很实际:集成测试要起模拟器/真机,CI 配置复杂,跑一次几分钟,小团队的迭代速度受不了。我的替代方案是”关键流程人工走查清单”:发版前按 checklist 点一遍(登录→下单→支付→退出),15 分钟,比写集成测试便宜。当然这是妥协——用户量上来后,核心流程(登录、支付)一定要补集成测试,人工走查迟早会漏。参考:Flutter 官方文档 integration_test 章节。

总结

集成测试是确保应用整体功能正确性的重要手段。通过编写全面的集成测试,可以提前发现问题,提高应用质量。


分享这篇文章:


相关文章

上一篇
Flutter 实现应用内更新
下一篇
Flutter ListView 组件详解