Как протестировать приложение Flutter

1. Введение

Flutter — это набор инструментов пользовательского интерфейса Google для создания красивых, скомпилированных в собственном коде приложений для мобильных устройств, Интернета и настольных компьютеров из единой базы кода.

В этой лаборатории кода вы создадите и протестируете простое приложение Flutter. Приложение будет использовать пакет Provider для управления состоянием.

Что вы узнаете

  • Как создавать тесты виджетов с помощью платформы тестирования виджетов
  • Как создать интеграционный тест для проверки пользовательского интерфейса и производительности приложения с помощью библиотеки integration_test
  • Как тестировать классы данных (провайдеры) с помощью модульных тестов

Что ты построишь

В этой лаборатории кода вы начнете с создания простого приложения со списком элементов. Мы предоставляем вам исходный код, чтобы вы могли сразу приступить к тестированию. Приложение поддерживает следующие операции:

  • Добавление товаров в избранное
  • Просмотр списка избранного
  • Удаление элементов из списка избранного

Как только приложение будет завершено, вы напишете следующие тесты:

  • Модульные тесты для проверки операций добавления и удаления.
  • Тесты виджетов для главной страницы и страницы избранного
  • Тесты пользовательского интерфейса и производительности для всего приложения с использованием интеграционных тестов.

GIF-изображение приложения, работающего на Android

Что бы вы хотели узнать из этой кодовой лаборатории?

Я новичок в этой теме, и мне нужен хороший обзор. Я кое-что знаю по этой теме, но хочу освежить знания. Я ищу пример кода для использования в моем проекте. Я ищу объяснение чего-то конкретного.

2. Настройте среду разработки Flutter.

Для выполнения этой лабораторной работы вам понадобятся два программного обеспечения — Flutter SDK и редактор .

Вы можете запустить кодовую лабораторию, используя любое из этих устройств:

  • Физическое устройство Android или iOS , подключенное к вашему компьютеру и переведенное в режим разработчика.
  • Симулятор iOS (требуется установка инструментов Xcode).
  • Эмулятор Android (требуется установка в Android Studio).
  • Браузер (для отладки необходим Chrome).
  • В качестве настольного приложения для Windows , Linux или macOS . Вы должны разрабатывать на платформе, на которой планируете развернуть. Итак, если вы хотите разработать классическое приложение для Windows, вам необходимо разработать его в Windows, чтобы получить доступ к соответствующей цепочке сборки. Существуют требования, специфичные для операционной системы, которые подробно описаны на docs.flutter.dev/desktop .

3. Начало работы

Создайте новое приложение Flutter и обновите зависимости.

Эта лаборатория посвящена тестированию мобильного приложения Flutter. Вы быстро создадите приложение для тестирования, используя исходные файлы, которые вы скопируете и вставите. Остальная часть лаборатории кода фокусируется на изучении различных видов тестирования.

a3c16fc17be25f6c.png Создайте простое приложение Flutter по шаблону, используя инструкции в разделе «Начало работы с первым приложением Flutter» или в командной строке, как показано ниже.

$ flutter create --empty testing_app
Creating project testing_app...
Resolving dependencies in `testing_app`... 
Downloading packages... 
Got dependencies in `testing_app`.
Wrote 128 files.

All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

In order to run your empty application, type:

  $ cd testing_app
  $ flutter run

Your empty application code is in testing_app/lib/main.dart.

a3c16fc17be25f6c.png Добавьте зависимости паба в командной строке.

  • provider для простого управления состоянием,
  • integration_test для самостоятельного тестирования кода Flutter на устройствах и эмуляторах,
  • flutter_driver для расширенного API для тестирования приложений Flutter, которые работают на реальных устройствах и эмуляторах,
  • test общего испытательного инструментария,
  • go_router для управления навигацией по приложению.
$ cd testing_app
$ flutter pub add provider go_router dev:test 'dev:flutter_driver:{"sdk":"flutter"}' 'dev:integration_test:{"sdk":"flutter"}'
Resolving dependencies... 
Downloading packages... 
+ _fe_analyzer_shared 67.0.0 (68.0.0 available)
+ analyzer 6.4.1 (6.5.0 available)
+ args 2.5.0
+ convert 3.1.1
+ coverage 1.7.2
+ crypto 3.0.3
+ file 7.0.0
+ flutter_driver 0.0.0 from sdk flutter
+ flutter_web_plugins 0.0.0 from sdk flutter
+ frontend_server_client 4.0.0
+ fuchsia_remote_debug_protocol 0.0.0 from sdk flutter
+ glob 2.1.2
+ go_router 14.0.2
+ http_multi_server 3.2.1
+ http_parser 4.0.2
+ integration_test 0.0.0 from sdk flutter
+ io 1.0.4
+ js 0.7.1
  leak_tracker 10.0.4 (10.0.5 available)
  leak_tracker_flutter_testing 3.0.3 (3.0.5 available)
+ logging 1.2.0
  material_color_utilities 0.8.0 (0.11.1 available)
  meta 1.12.0 (1.14.0 available)
+ mime 1.0.5
+ nested 1.0.0
+ node_preamble 2.0.2
+ package_config 2.1.0
+ platform 3.1.4
+ pool 1.5.1
+ process 5.0.2
+ provider 6.1.2
+ pub_semver 2.1.4
+ shelf 1.4.1
+ shelf_packages_handler 3.0.2
+ shelf_static 1.1.2
+ shelf_web_socket 1.0.4
+ source_map_stack_trace 2.1.1
+ source_maps 0.10.12
+ sync_http 0.3.1
+ test 1.25.2 (1.25.4 available)
  test_api 0.7.0 (0.7.1 available)
+ test_core 0.6.0 (0.6.2 available)
+ typed_data 1.3.2
+ watcher 1.1.0
+ web 0.5.1
+ web_socket_channel 2.4.5
+ webdriver 3.0.3
+ webkit_inspection_protocol 1.2.1
+ yaml 3.1.2
Changed 44 dependencies!
9 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

Следующие зависимости должны быть добавлены в ваш pubspec.yaml :

pubspec.yaml

name: testing_app
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0

environment:
  sdk: '>=3.4.0-0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  go_router: ^14.0.2
  provider: ^6.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.0
  test: ^1.25.2
  flutter_driver:
    sdk: flutter
  integration_test:
    sdk: flutter

flutter:
  uses-material-design: true

a3c16fc17be25f6c.png Откройте проект в выбранном вами редакторе кода и запустите приложение. Альтернативно, запустите его из командной строки следующим образом.

$ flutter run

4. Создайте приложение

Далее вы создадите приложение, чтобы его можно было протестировать. Приложение содержит следующие файлы:

  • lib/models/favorites.dart — создает класс модели для списка избранного.
  • lib/screens/favorites.dart — создает макет списка избранного.
  • lib/screens/home.dart — создает список элементов
  • lib/main.dart — основной файл, из которого запускается приложение.

Сначала создайте модель Favorites в lib/models/favorites.dart

a3c16fc17be25f6c.png Создайте новый каталог с именем models в каталоге lib , а затем создайте новый файл с именем favorites.dart . В этот файл добавьте следующий код:

lib/модели/фавориты.dart

import 'package:flutter/material.dart';

/// The [Favorites] class holds a list of favorite items saved by the user.
class Favorites extends ChangeNotifier {
  final List<int> _favoriteItems = [];

  List<int> get items => _favoriteItems;

  void add(int itemNo) {
    _favoriteItems.add(itemNo);
    notifyListeners();
  }

  void remove(int itemNo) {
    _favoriteItems.remove(itemNo);
    notifyListeners();
  }
}

Добавьте страницу «Избранное» в lib/screens/favorites.dart

a3c16fc17be25f6c.png Создайте новый каталог с именем screens в каталоге lib и в этом каталоге создайте новый файл с именем favorites.dart . В этот файл добавьте следующий код:

lib/экраны/favorites.dart

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

import '../models/favorites.dart';

class FavoritesPage extends StatelessWidget {
  const FavoritesPage({super.key});

  static String routeName = 'favorites_page';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Favorites'),
      ),
      body: Consumer<Favorites>(
        builder: (context, value, child) => ListView.builder(
          itemCount: value.items.length,
          padding: const EdgeInsets.symmetric(vertical: 16),
          itemBuilder: (context, index) => FavoriteItemTile(value.items[index]),
        ),
      ),
    );
  }
}

class FavoriteItemTile extends StatelessWidget {
  const FavoriteItemTile(this.itemNo, {super.key});

  final int itemNo;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
        ),
        title: Text(
          'Item $itemNo',
          key: Key('favorites_text_$itemNo'),
        ),
        trailing: IconButton(
          key: Key('remove_icon_$itemNo'),
          icon: const Icon(Icons.close),
          onPressed: () {
            Provider.of<Favorites>(context, listen: false).remove(itemNo);
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('Removed from favorites.'),
                duration: Duration(seconds: 1),
              ),
            );
          },
        ),
      ),
    );
  }
}

Добавьте домашнюю страницу в lib/screens/home.dart

a3c16fc17be25f6c.png В каталоге lib/screens создайте еще один новый файл с именем home.dart . В lib/screens/home.dart добавьте следующий код:

библиотека/экраны/home.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../models/favorites.dart';
import 'favorites.dart';

class HomePage extends StatelessWidget {
  static String routeName = '/';

  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Testing Sample'),
        actions: <Widget>[
          TextButton.icon(
            onPressed: () {
              context.go('/${FavoritesPage.routeName}');
            },
            icon: const Icon(Icons.favorite_border),
            label: const Text('Favorites'),
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: 100,
        cacheExtent: 20.0,
        padding: const EdgeInsets.symmetric(vertical: 16),
        itemBuilder: (context, index) => ItemTile(index),
      ),
    );
  }
}

class ItemTile extends StatelessWidget {
  final int itemNo;

  const ItemTile(this.itemNo, {super.key});

  @override
  Widget build(BuildContext context) {
    var favoritesList = Provider.of<Favorites>(context);

    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: Colors.primaries[itemNo % Colors.primaries.length],
        ),
        title: Text(
          'Item $itemNo',
          key: Key('text_$itemNo'),
        ),
        trailing: IconButton(
          key: Key('icon_$itemNo'),
          icon: favoritesList.items.contains(itemNo)
              ? const Icon(Icons.favorite)
              : const Icon(Icons.favorite_border),
          onPressed: () {
            !favoritesList.items.contains(itemNo)
                ? favoritesList.add(itemNo)
                : favoritesList.remove(itemNo);
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text(favoritesList.items.contains(itemNo)
                    ? 'Added to favorites.'
                    : 'Removed from favorites.'),
                duration: const Duration(seconds: 1),
              ),
            );
          },
        ),
      ),
    );
  }
}

Замените содержимое lib/main.dart

a3c16fc17be25f6c.png Замените содержимое lib/main.dart следующим кодом:

библиотека/main.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'models/favorites.dart';
import 'screens/favorites.dart';
import 'screens/home.dart';

void main() {
  runApp(const TestingApp());
}

final _router = GoRouter(
  routes: [
    GoRoute(
      path: HomePage.routeName,
      builder: (context, state) {
        return const HomePage();
      },
      routes: [
        GoRoute(
          path: FavoritesPage.routeName,
          builder: (context, state) {
            return const FavoritesPage();
          },
        ),
      ],
    ),
  ],
);

class TestingApp extends StatelessWidget {
  const TestingApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Favorites>(
      create: (context) => Favorites(),
      child: MaterialApp.router(
        title: 'Testing Sample',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(
            seedColor: Colors.deepPurple,
          ),
          useMaterial3: true,
        ),
        routerConfig: _router,
      ),
    );
  }
}

Приложение готово, но не протестировано.

a3c16fc17be25f6c.png Запустите приложение. Это должно выглядеть как на следующем снимке экрана:

b74f843e42a28b0f.png

Приложение показывает список предметов. Коснитесь значка в форме сердца в любой строке, чтобы заполнить сердце и добавить элемент в список избранного. Кнопка «Избранное» на AppBar переводит вас на второй экран, содержащий список избранного.

Теперь приложение готово к тестированию. Вы начнете тестировать приложение на следующем этапе.

5. Юнит-тестирование провайдера

Вы начнете с модульного тестирования модели favorites . Что такое модульный тест? Юнит-тест проверяет, что каждая отдельная единица программного обеспечения, будь то функция, объект или виджет, правильно выполняет поставленную задачу.

Все тестовые файлы в приложении Flutter, за исключением интеграционных тестов, размещаются в test каталоге.

Удалить test/widget_test.dart

a3c16fc17be25f6c.png Прежде чем начать тестирование, удалите файл widget_test.dart . Вы добавите свои собственные тестовые файлы.

Создайте новый тестовый файл

Сначала вы протестируете метод add() в модели Favorites , чтобы убедиться, что в список добавляется новый элемент и что список отражает это изменение. По соглашению, структура каталогов в test каталоге имитирует структуру каталога lib и файлов Dart, имеющих одно и то же имя с добавлением _test .

a3c16fc17be25f6c.png Создайте каталог models в test каталоге. В этом новом каталоге создайте файл favorites_test.dart со следующим содержимым:

test/models/favorites_test.dart

import 'package:test/test.dart';
import 'package:testing_app/models/favorites.dart';

void main() {
  group('Testing App Provider', () {
    var favorites = Favorites();

    test('A new item should be added', () {
      var number = 35;
      favorites.add(number);
      expect(favorites.items.contains(number), true);
    });    
  });
}

Платформа тестирования Flutter позволяет связывать похожие тесты, связанные друг с другом, в группу. В одном тестовом файле может быть несколько групп, предназначенных для тестирования различных частей соответствующего файла в каталоге lib .

Метод test() принимает два позиционных параметра: description теста и callback , в котором вы фактически пишете тест.

a3c16fc17be25f6c.png Проверьте удаление элемента из списка. Вставьте следующий тест в ту же группу Testing App Provider :

test/models/favorites_test.dart

test('An item should be removed', () {
  var number = 45;
  favorites.add(number);
  expect(favorites.items.contains(number), true);
  favorites.remove(number);
  expect(favorites.items.contains(number), false);
});

Запустить тест

a3c16fc17be25f6c.png В командной строке перейдите в корневой каталог проекта и введите следующую команду:

$ flutter test test/models/favorites_test.dart 

Если все работает, вы должны увидеть сообщение, подобное следующему:

00:06 +2: All tests passed!                                                    

Полный тестовый файл: test/models/favorites_test.dart .

Дополнительные сведения о модульном тестировании см. на странице Введение в модульное тестирование .

6. Тестирование виджета

На этом этапе вы добавите код для тестирования виджетов. Тестирование виджетов уникально для Flutter: вы можете тестировать каждый виджет изолированно. На этом этапе экраны HomePage и FavoritesPage тестируются по отдельности.

При тестировании виджетов используется функция testWidget() вместо функции test() . Как и функция test() , функция testWidget() принимает два параметра: description, и callback , однако обратный вызов принимает WidgetTester в качестве аргумента.

Тесты виджетов используют TestFlutterWidgetsBinding , класс, который предоставляет вашим виджетам те же ресурсы, что и в работающем приложении, например, информацию о размере экрана, возможность планировать анимацию, но без запуска внутри приложения. Вместо этого для создания экземпляра виджета используется виртуальная среда, а затем тестируются результаты. Здесь pumpWidget запускает процесс, сообщая платформе смонтировать и измерить конкретный виджет так же, как это было бы в приложении.

Платформа тестирования виджетов предоставляет средства поиска для поиска виджетов, например text() , byType() и byIcon(). Платформа также предоставляет средства сопоставления для проверки результатов.

Начните с тестирования виджета HomePage .

Создайте новый тестовый файл

Первый тест проверяет, правильно ли работает прокрутка HomePage .

a3c16fc17be25f6c.png Создайте новый файл в test каталоге и назовите его home_test.dart . Во вновь созданный файл добавьте следующий код:

тест/home_test.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/home.dart';

Widget createHomeScreen() => ChangeNotifierProvider<Favorites>(
      create: (context) => Favorites(),
      child: const MaterialApp(
        home: HomePage(),
      ),
    );

void main() {
  group('Home Page Widget Tests', () {
    testWidgets('Testing Scrolling', (tester) async {
      await tester.pumpWidget(createHomeScreen());
      expect(find.text('Item 0'), findsOneWidget);
      await tester.fling(
        find.byType(ListView),
        const Offset(0, -200),
        3000,
      );
      await tester.pumpAndSettle();
      expect(find.text('Item 0'), findsNothing);
    });
  });
}

Функция createHomeScreen() используется для создания приложения, которое загружает виджет для тестирования в MaterialApp, завернутый в ChangeNotifierProvider. Виджету HomePage необходимо, чтобы оба этих виджета присутствовали над ним в дереве виджетов, чтобы он мог наследовать от них и получать доступ к предлагаемым ими данным. Эта функция передается в качестве параметра функции pumpWidget() .

Затем проверьте, может ли платформа найти ListView отображаемый на экране.

a3c16fc17be25f6c.png Добавьте следующий фрагмент кода в home_test.dart :

тест/home_test.dart

group('Home Page Widget Tests', () {

  // BEGINNING OF NEW CONTENT
  testWidgets('Testing if ListView shows up', (tester) async {  
    await tester.pumpWidget(createHomeScreen());
    expect(find.byType(ListView), findsOneWidget);
  });                                                
  // END OF NEW CONTENT

    testWidgets('Testing Scrolling', (tester) async {
      await tester.pumpWidget(createHomeScreen());
      expect(find.text('Item 0'), findsOneWidget);
      await tester.fling(
        find.byType(ListView),
        const Offset(0, -200),
        3000,
      );
      await tester.pumpAndSettle();
      expect(find.text('Item 0'), findsNothing);
    });
});

Запустить тест

Сначала запустите тест так же, как и модульный тест, с помощью команды:

$ flutter test test/home_test.dart 

Тест должен выполниться быстро, и вы должны увидеть сообщение, подобное этому:

00:02 +2: All tests passed!                                                    

Вы также можете запускать тесты виджетов с помощью устройства или эмулятора, что позволяет вам наблюдать за выполнением теста. Это также дает вам возможность использовать горячий перезапуск.

a3c16fc17be25f6c.png Подключите устройство или запустите эмулятор. Вы также можете запустить тест как настольное приложение.

a3c16fc17be25f6c.png В командной строке перейдите в корневой каталог проекта и введите следующую команду:

$ flutter run test/home_test.dart 

Возможно, вам придется выбрать устройство для запуска теста. В этом случае следуйте инструкциям и выберите устройство:

Multiple devices found:
Linux (desktop) • linux  • linux-x64      • Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web)    • chrome • web-javascript • Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"): 

Если все работает, вы должны увидеть вывод, аналогичный следующему:

Launching test/home_test.dart on Linux in debug mode...
Building Linux application...                                           
flutter: 00:00 +0: Home Page Widget Tests Testing if ListView shows up
Syncing files to device Linux...                                    62ms

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

💪 Running with sound null safety 💪

An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:00 +1: Home Page Widget Tests Testing Scrolling
The Flutter DevTools debugger and profiler on Linux is available at:
http://127.0.0.1:9100?uri=http://127.0.0.1:35583/GCpdLBqf2UI=/
flutter: 00:02 +2: All tests passed!

Затем вы внесете изменения в тестовый файл и нажмите Shift + R , чтобы перезапустить приложение и повторно запустить все тесты. Не останавливайте приложение.

a3c16fc17be25f6c.png Добавьте больше тестов в группу, которая тестирует виджеты HomePage. Скопируйте следующий тест в свой файл:

тест/home_test.dart

testWidgets('Testing IconButtons', (tester) async {
  await tester.pumpWidget(createHomeScreen());
  expect(find.byIcon(Icons.favorite), findsNothing);
  await tester.tap(find.byIcon(Icons.favorite_border).first);
  await tester.pumpAndSettle(const Duration(seconds: 1));
  expect(find.text('Added to favorites.'), findsOneWidget);
  expect(find.byIcon(Icons.favorite), findsWidgets);
  await tester.tap(find.byIcon(Icons.favorite).first);
  await tester.pumpAndSettle(const Duration(seconds: 1));
  expect(find.text('Removed from favorites.'), findsOneWidget);
  expect(find.byIcon(Icons.favorite), findsNothing);
});

Этот тест проверяет, что нажатие IconButton меняется с Icons.favorite_border (открытое сердце) на Icons.favorite (заполненное сердце), а затем обратно на Icons.favorite_border при повторном нажатии.

a3c16fc17be25f6c.png Введите Shift + R . Этот горячий перезапускает приложение и повторно запускает все тесты.

Полный тестовый файл: test/home_test.dart .

a3c16fc17be25f6c.png Используйте тот же процесс для проверки FavoritesPage с помощью следующего кода. Выполните те же действия и запустите его.

тест/favorites_test.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:testing_app/models/favorites.dart';
import 'package:testing_app/screens/favorites.dart';

late Favorites favoritesList;

Widget createFavoritesScreen() => ChangeNotifierProvider<Favorites>(
      create: (context) {
        favoritesList = Favorites();
        return favoritesList;
      },
      child: const MaterialApp(
        home: FavoritesPage(),
      ),
    );

void addItems() {
  for (var i = 0; i < 10; i += 2) {
    favoritesList.add(i);
  }
}

void main() {
  group('Favorites Page Widget Tests', () {
    testWidgets('Test if ListView shows up', (tester) async {
      await tester.pumpWidget(createFavoritesScreen());
      addItems();
      await tester.pumpAndSettle();
      expect(find.byType(ListView), findsOneWidget);
    });

    testWidgets('Testing Remove Button', (tester) async {
      await tester.pumpWidget(createFavoritesScreen());
      addItems();
      await tester.pumpAndSettle();
      var totalItems = tester.widgetList(find.byIcon(Icons.close)).length;
      await tester.tap(find.byIcon(Icons.close).first);
      await tester.pumpAndSettle();
      expect(tester.widgetList(find.byIcon(Icons.close)).length,
          lessThan(totalItems));
      expect(find.text('Removed from favorites.'), findsOneWidget);
    });
  });
}

Этот тест проверяет, исчезает ли элемент при нажатии кнопки закрытия (удаления).

Для получения дополнительной информации о тестировании виджетов посетите:

7. Тестирование пользовательского интерфейса приложения с помощью интеграционных тестов.

Интеграционные тесты используются для проверки того, как отдельные части приложения работают вместе в целом. Библиотека integration_test используется для выполнения интеграционных тестов во Flutter. Это версия Selenium WebDriver, Protractor, Espresso или Earl Grey от Flutter. Пакет внутренне использует flutter_driver для проведения теста на устройстве.

Написание интеграционных тестов во Flutter похоже на написание тестов виджетов, за исключением того, что интеграционные тесты запускаются на мобильном устройстве, браузере или настольном приложении, называемом целевым устройством.

Написать тест

a3c16fc17be25f6c.png Создайте каталог с именем integration_test в корневом каталоге проекта и в этом каталоге создайте новый файл с именем app_test.dart .

Integration_test/app_test.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:testing_app/main.dart';

void main() {
  group('Testing App', () {
    testWidgets('Favorites operations test', (tester) async {
      await tester.pumpWidget(const TestingApp());

      final iconKeys = [
        'icon_0',
        'icon_1',
        'icon_2',
      ];

      for (var icon in iconKeys) {
        await tester.tap(find.byKey(ValueKey(icon)));
        await tester.pumpAndSettle(const Duration(seconds: 1));

        expect(find.text('Added to favorites.'), findsOneWidget);
      }

      await tester.tap(find.text('Favorites'));
      await tester.pumpAndSettle();

      final removeIconKeys = [
        'remove_icon_0',
        'remove_icon_1',
        'remove_icon_2',
      ];

      for (final iconKey in removeIconKeys) {
        await tester.tap(find.byKey(ValueKey(iconKey)));
        await tester.pumpAndSettle(const Duration(seconds: 1));

        expect(find.text('Removed from favorites.'), findsOneWidget);
      }
    });
  });
}

Запустить тест

a3c16fc17be25f6c.png Подключите устройство или запустите эмулятор. Вы также можете запустить тест как настольное приложение.

a3c16fc17be25f6c.png В командной строке перейдите в корневой каталог проекта и введите следующую команду:

$ flutter test integration_test/app_test.dart

Если все работает, вы должны увидеть вывод, аналогичный следующему:

Multiple devices found:
Linux (desktop)  linux   linux-x64       Ubuntu 22.04.1 LTS 5.15.0-58-generic
Chrome (web)     chrome  web-javascript  Google Chrome 109.0.5414.119
[1]: Linux (linux)
[2]: Chrome (chrome)
Please choose one (To quit, press "q/Q"): 1
00:00 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart                                                B00:08 +0: loading /home/miquel/tmp/testing_app/integration_test/app_test.dart                                                
00:26 +1: All tests passed!

8. Тестирование производительности приложения с помощью Flutter Driver

Напишите тест производительности

Создайте новый тестовый файл с именем perf_test.dart в папке Integration_test со следующим содержимым:

Integration_test/perf_test.dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:testing_app/main.dart';

void main() {
  group('Testing App Performance', () {
    final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;

    testWidgets('Scrolling test', (tester) async {
      await tester.pumpWidget(const TestingApp());

      final listFinder = find.byType(ListView);

      await binding.traceAction(() async {
        await tester.fling(listFinder, const Offset(0, -500), 10000);
        await tester.pumpAndSettle();

        await tester.fling(listFinder, const Offset(0, 500), 10000);
        await tester.pumpAndSettle();
      }, reportKey: 'scrolling_summary');
    });
  });
}

Функция ensureInitialized() проверяет, инициализирован ли драйвер интеграционного теста, и при необходимости инициализирует его повторно. Установка для framePolicy значения fullyLive удобна для тестирования анимированного кода.

Этот тест очень быстро прокручивает список элементов, а затем прокручивает его до конца вверх. Функция traceAction() записывает действия и генерирует сводку по временной шкале.

Фиксируйте результаты производительности

Чтобы получить результаты, создайте папку с именем test_driver с файлом с именем perf_driver.dart и добавьте следующий код:

test_driver/perf_driver.dart

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_summary'] as Map<String, dynamic>);

        final summary = driver.TimelineSummary.summarize(timeline);

        await summary.writeTimelineToFile(
          'scrolling_summary',
          pretty: true,
          includeSummary: true,
        );
      }
    },
  );
}

Запустить тест

a3c16fc17be25f6c.png Подключите устройство или запустите эмулятор.

a3c16fc17be25f6c.png В командной строке перейдите в корневой каталог проекта и введите следующую команду:

$ flutter drive \
  --driver=test_driver/perf_driver.dart \
  --target=integration_test/perf_test.dart \
  --profile \
  --no-dds

Если все работает, вы должны увидеть вывод, аналогичный следующему:

Running "flutter pub get" in testing_app...
Resolving dependencies... 
  archive 3.3.2 (3.3.6 available)
  collection 1.17.0 (1.17.1 available)
  js 0.6.5 (0.6.7 available)
  matcher 0.12.13 (0.12.14 available)
  meta 1.8.0 (1.9.0 available)
  path 1.8.2 (1.8.3 available)
  test 1.22.0 (1.23.0 available)
  test_api 0.4.16 (0.4.18 available)
  test_core 0.4.20 (0.4.23 available)
  vm_service 9.4.0 (11.0.1 available)
  webdriver 3.0.1 (3.0.2 available)
Got dependencies!
Running Gradle task 'assembleProfile'...                         1,379ms
✓  Built build/app/outputs/flutter-apk/app-profile.apk (14.9MB).
Installing build/app/outputs/flutter-apk/app-profile.apk...        222ms
I/flutter ( 6125): 00:04 +1: Testing App Performance (tearDownAll)
I/flutter ( 6125): 00:04 +2: All tests passed!
All tests passed.

После успешного завершения теста каталог сборки в корне проекта содержит два файла:

  1. scrolling_summary.timeline_summary.json содержит сводку. Откройте файл в любом текстовом редакторе, чтобы просмотреть содержащуюся в нем информацию.
  2. scrolling_summary.timeline.json содержит полные данные временной шкалы.

Для получения более подробной информации об интеграционном тестировании посетите:

9. Поздравляем!

Вы завершили работу над кодом и узнали различные способы тестирования приложения Flutter.

Что вы узнали

  • Как тестировать провайдеров с помощью модульных тестов
  • Как тестировать виджеты с помощью среды тестирования виджетов
  • Как протестировать пользовательский интерфейс приложения с помощью интеграционных тестов
  • Как проверить производительность приложения с помощью интеграционных тестов

Чтобы узнать больше о тестировании во Flutter, посетите