Flutter集成测试预览插件integration_test_preview的使用

Flutter集成测试预览插件integration_test_preview的使用

概述

Integration Test Preview 是一个基于 integration_test_helperdevice_preview 的 Flutter 包,用于快速部署端到端(e2e)测试。它允许用户针对不同的设备进行测试,并生成特定设备尺寸的截图,以供应用商店审核使用。此外,它还支持通过组合多个设备的屏幕截图生成一个交互式的截图画廊。

Integration Test Preview


特性

Integration Test Preview 示例

通过继承 IntegrationTestPreview 子类,可以轻松测试多个屏幕类型的端到端功能。

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test_preview/integration_test_binding.dart';
import 'package:device_frame/src/devices/devices.dart';
import 'package:device_frame/src/info/info.dart';

import 'package:example/main.dart' as app;
import 'app_feature_groups.dart';

void main() async {
    const minutesPerDevice = 3;
    final List<DeviceInfo> testDevices = [
        Devices.ios.iPhone12, 
        Devices.android.samsungGalaxyNote20, 
        Devices.ios.iPadPro11Inches,
    ];
    final totalExpectedDuration = Duration(minutes: testDevices.length * minutesPerDevice);
    final binding = IntegrationTestPreviewBinding.ensureInitialized();

    testWidgets('Testing end to end multi-screen integration', (WidgetTester tester) async {
        final main = app.setupMainWidget();
        final integrationTestGroups = ScreenIntegrationTestGroups(binding);
        await integrationTestGroups.initializeDevices(testDevices, state: ScreenshotState.RESPONSIVE);
        await integrationTestGroups.initializeTests(tester, main);
    }, timeout: Timeout(totalExpectedDuration));
}

查看截图结果

生成的截图默认保存在 screenshots 目录下。可以通过自定义 integrationDriver 来生成一个交互式的 HTML 页面。

import 'dart:io';
import 'package:integration_test_preview/integration_test_driver.dart';

Future<void> main() => integrationDriver(
    clearScreenshots: true, 
    onScreenshot: (String screenshotPath, List<int> screenshotBytes) async {
        final File image = File(screenshotPath);
        print(image);

        final dir = image.parent;
        if (!await dir.exists()) await dir.create(recursive: true);

        image.writeAsBytesSync(screenshotBytes);
        return true;
    }
);

运行后会在控制台中显示 screenshots.html 文件的路径:

Device Previews: /Users/your/project/path/screenshots.html

截图画廊示例:


使用步骤

安装依赖

通过命令行或 pubspec.yaml 安装必要的依赖包。

通过命令行安装:

flutter pub add provider
flutter pub add drawer_manager
flutter pub add integration_test_preview

通过 pubspec.yaml 安装:

dependencies:
  flutter:
    sdk: flutter

  provider: ^6.0.2
  drawer_manager: ^0.0.4

dev_dependencies:
  flutter_test:
    sdk: flutter
  integration_test:
    sdk: flutter
  integration_test_preview: <最新版本>

添加测试驱动文件

创建一个测试驱动文件 test_driver/app_features_test.dart

import 'dart:io';
import 'package:integration_test_preview/integration_test_driver.dart';

Future<void> main() => integrationDriver(
    clearScreenshots: true, 
    onScreenshot: (String screenshotPath, List<int> screenshotBytes) async {
        final File image = File(screenshotPath);
        print(image);

        final dir = image.parent;
        if (!await dir.exists()) await dir.create(recursive: true);

        image.writeAsBytesSync(screenshotBytes);
        return true;
    }
);

示例代码

以下是一个完整的示例,展示如何使用 integration_test_preview 进行集成测试。

创建平台文件

lib/platforms.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class PlatformApp extends PlatformWidget {
  PlatformApp({
    Key? key,
    required MaterialApp androidApp,
    required CupertinoApp iosApp,
    required TargetPlatform defaultPlatform,
  }) : super(key: key,
      androidBuilder: (BuildContext context) => androidApp,
      iosBuilder:  (BuildContext context) => iosApp
    ) {
      PlatformWidget.setPlatform(defaultPlatform);
  }
}

class PlatformWidget extends StatefulWidget {
  static TargetPlatform? _currentPlatform;

  static get platform {
      if (_currentPlatform == null) {
        return TargetPlatform.android;
      }
      return _currentPlatform;
  }

  static get isAndroid {
      return _currentPlatform == TargetPlatform.android;
  }

  static get isIOS {
      return _currentPlatform == TargetPlatform.iOS;
  }

  static void setPlatform(TargetPlatform platform) {
      _currentPlatform = platform;
  }

  static void reassembleApplication() {
      WidgetsBinding.instance!.reassembleApplication();
  }

  const PlatformWidget({
    Key? key,
    required this.androidBuilder,
    required this.iosBuilder,
  }) : super(key: key);

  final WidgetBuilder androidBuilder;
  final WidgetBuilder iosBuilder;

  @override
  State<PlatformWidget> createState() => _PlatformWidgetState();
}

class _PlatformWidgetState extends State<PlatformWidget> {
  @override
  Widget build(context) {
    switch (PlatformWidget._currentPlatform) {
      case TargetPlatform.android:
        return widget.androidBuilder(context);
      case TargetPlatform.iOS:      
        return widget.iosBuilder(context);        
      default:
        assert(false, 'Unexpected platform ${PlatformWidget._currentPlatform}');
        return Container();
    }
  }
}

创建主文件

lib/main.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:drawer_manager/drawer_manager.dart';

import 'platforms.dart';
import 'hello.dart';

void main() {
  runApp(setupMainWidget());
}

Widget setupMainWidget() {
  WidgetsFlutterBinding.ensureInitialized();
  return const MyApp();
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<DrawerManagerProvider>(
        create: (_) => DrawerManagerProvider(),
        child: PlatformApp(
            defaultPlatform: PlatformWidget.platform,
            androidApp: const MaterialApp(home: MyHomePage()),
            iosApp: const CupertinoApp(
                theme: CupertinoThemeData(brightness: Brightness.light),
                home: MyHomePage(),
            )
        )
      );
  }
}

class MyHomePage extends StatelessWidget {

  const MyHomePage({Key? key}) : super(key: key);

  String _getTitle(int index) {
      switch (index) {
        case 0: return 'Hello 1';
        case 1: return 'Hello 2';
        default: return '';
      }
  }

  Widget _getTitleWidget() {
    return Consumer<DrawerManagerProvider>(builder: (context, dmObj, _) {
      return Text(
        _getTitle(dmObj.selection),
        key: const Key('app-bar-text')
      );
    });
  }

  Widget _buildAndroidHomePage(BuildContext context) {

    final drawerSelections = [
      const HelloPage(position: 1),
      const HelloPage(position: 2),
    ];
    
    final manager = Provider.of<DrawerManagerProvider>(context, listen: false);

    return Scaffold(
        appBar: AppBar(title: _getTitleWidget()),
        body: manager.body,
        drawer: DrawerManager(
          context,
          drawerElements: [
            const DrawerHeader(
              decoration: BoxDecoration(color: Colors.blue),
              child: Padding(
                padding: EdgeInsets.only(bottom: 20),
                child: Icon(
                  Icons.account_circle,
                  color: Colors.blueGrey,
                  size: 96,
                ),
              ),
            ),
            DrawerTile(
              key: const Key('drawer-hello-1'),
              context: context,
              leading: const Icon(Icons.hail_rounded),
              title: Text(_getTitle(0)),
              onTap: () async {
                // RUN A BACKEND Hello, Flutter OPERATION
              },
            ),
            DrawerTile(
              key: const Key('drawer-hello-2'),
              context: context,
              leading: const Icon(Icons.hail_rounded),
              title: Text(_getTitle(1)),
              onTap: () async {
                // RUN A BACKEND Hello, Flutter OPERATION
              },
            )
          ],
          tileSelections: drawerSelections,
        ));
    }

  Widget _buildIosHomePage(BuildContext context) {

    return CupertinoTabScaffold(
        tabBar: CupertinoTabBar(
            items: [
                BottomNavigationBarItem(
                    label: _getTitle(0),
                    icon: const Icon(Icons.hail_rounded),
                ),
                BottomNavigationBarItem(
                    label: _getTitle(1),
                    icon: const Icon(Icons.hail_rounded),
                ),
            ],
        ),
        // ignore: avoid_types_on_closure_parameters
        tabBuilder: (BuildContext context, int index) {
            switch (index) {
            case 0: return CupertinoTabView(
                    builder: (context) => const HelloPage(position: 1),
                );
            case 1: return CupertinoTabView(
                    builder: (context) => const HelloPage(position: 2),
                );
            default:
                assert(false, 'Unexpected tab');
                return Container();
            }
        },
    );
  }

  @override
  Widget build(context) {
    return PlatformWidget(
      androidBuilder: _buildAndroidHomePage,
      iosBuilder: _buildIosHomePage,
    );
  }

}

运行测试

通过以下命令运行测试驱动程序:

flutter drive -t integration_test/app_features.dart

更多关于Flutter集成测试预览插件integration_test_preview的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter集成测试预览插件integration_test_preview的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


integration_test_preview 是一个用于 Flutter 集成测试的插件,它允许你在 Flutter 应用中编写和运行集成测试。集成测试是用于测试整个应用或应用中的多个部分,以确保它们能够正确地协同工作。integration_test_preview 插件提供了一些工具和功能,使得编写和运行集成测试变得更加容易。

安装 integration_test_preview 插件

首先,你需要在 pubspec.yaml 文件中添加 integration_test_preview 插件的依赖:

dev_dependencies:
  integration_test_preview: ^1.0.0

然后运行 flutter pub get 来安装依赖。

编写集成测试

接下来,你可以编写集成测试。通常,集成测试文件会放在 test_driverintegration_test 目录下。以下是一个简单的集成测试示例:

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test_preview/integration_test_preview.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // 启动应用
    await tester.pumpWidget(MyApp());

    // 验证初始文本
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // 点击按钮
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    // 验证文本是否更新
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}

运行集成测试

要运行集成测试,你可以使用以下命令:

flutter test integration_test/my_test.dart

或者,如果你想在设备上运行测试,可以使用以下命令:

flutter drive --driver=test_driver/integration_test.dart --target=integration_test/my_test.dart

使用 integration_test_preview 插件的高级功能

integration_test_preview 插件提供了一些高级功能,例如:

  • Screenshot Testing: 你可以使用 takeScreenshot 方法来捕获屏幕截图,并将其与基准图像进行比较,以确保 UI 没有发生变化。

    await IntegrationTestWidgetsFlutterBinding.instance.takeScreenshot('screenshot_name');
    
  • Performance Testing: 你可以使用 traceAction 方法来测量特定操作的性能。

    await IntegrationTestWidgetsFlutterBinding.instance.traceAction(() async {
      // 执行需要测量的操作
    });
回到顶部