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

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

  • Создайте свой исходный код
  • Запуск модульных тестов
  • Загрузите покрытие кода
  • Запуск интегрированных тестов
  • Создайте и загрузите файл APK
  • Сделайте скриншоты интегрированных тестов и загрузите файлы

Создайте наше приложение-пример Flutter

Перед созданием нашего рабочего процесса нам нужно одно приложение, а пока достаточно шаблона по умолчанию.

Создайте новое приложение Flutter и запустите его в эмуляторе Android или на физическом устройстве.

Я назвал свой проект как flutter_workflow_example

Затем создайте новый репозиторий GitHub и загрузите в него весь код.

Действия Git-хаба

GitHub Action — это бесплатная платформа CI/CD, которая позволяет создавать рабочие процессы для сборки, тестирования, публикации и многих других возможностей, полностью интегрированных с GitHub.

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

Создать действие GitHub для вашего приложения Flutter просто и понятно, и это можно сделать за считанные секунды, поэтому давайте создадим наш рабочий процесс, а затем я объясню его шаг за шагом, как он работает:

Сначала создайте файл .github/workflows/main.yml и вставьте в него приведенный ниже код:

name: Flutter Workflow

on: [push, workflow_dispatch]
jobs:
  build:
    runs-on: macos-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Install Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '2.10.0'
          channel: 'stable'

      - name: Install dependencies
        run: flutter pub get

Отправьте это в свой репозиторий, и все готово, у вас уже есть рабочий процесс.

Ниже мы видим рабочий процесс, запущенный после отправки.

И это было завершено с успехом, отличная работа.

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

Упс, шучу, еще многое предстоит сделать, тогда давайте шаг за шагом проверим, что мы только что сделали.

name: Flutter Workflow

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

on: [push, workflow_dispatch]

Свойство on определяет, когда запускать рабочий процесс, есть несколько вариантов, которые вы можете выбрать, а параметр push заставит рабочий процесс запускаться каждый раз при выполнении нового нажатия, параметры workflow_dispatch позволяют запускать новый рабочий процесс непосредственно из интерфейса GitHub.

jobs:
  build:

Раздел jobs определяет задания, которые будут выполняться в рабочем процессе, в данном случае имя первого задания — build.

runs-on: macos-latest

В следующем разделе будет определен тип машины, которая будет использоваться в рабочем процессе, в нашем случае я использую машину с установленной последней версией macOS.

steps:

Здесь мы определим все этапы рабочего процесса, обычно мы предоставляем опции -name и uses или run.

- name: Checkout code
  uses: actions/checkout@v3

На этом первом шаге мы выполняем действие actions/checkout, которое отвечает за проверку кода репозитория рабочего процесса.

Текст, определенный в опции -name, будет отображаться при выполнении рабочего процесса.

- name: Install Flutter
  uses: subosito/flutter-action@v2
  with:
    flutter-version: '2.10.0'
    channel: 'stable'

- name: Install dependencies
  run: flutter pub get

Следующее действие — subosito/flutter-action, которое отвечает за настройку среды Flutter в нашем рабочем процессе, обратите внимание, что мы можем настроить версию Flutter и выполнять команды Flutter.

Мы будем использовать другие действия в следующих разделах, не стесняйтесь посещать их репозитории и проверять все доступные варианты.

Запуск модульных тестов

Шаблон проекта Flutter по умолчанию содержит файл test/test_widget.dart с очень простым модульным тестом, но его достаточно для запуска в нашем рабочем процессе.

Добавьте эту строку в свой main.yml и отправьте ее в репозиторий:

- name: Run unit tests
  run: flutter test --coverage

Командаflutter test запустит модульные тесты, опция --coverage необходима для создания файла .lcov, который мы будем использовать позже в отчете о покрытии кода.

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

Создайте отчет о покрытии кода

Есть много способов создать отчет о покрытии кода, я буду использовать Codecov, который является очень простым и полным инструментом для обработки информации о покрытии кода.

Войдите в Кодеков, используя свои учетные данные GitHub, и разрешите доступ для поиска ваших репозиториев.

Нажмите на опцию Not yet setup, чтобы увидеть свой недавно созданный проект, и нажмите на опцию setup repo.

На втором этапе мы найдем токен Codecov, как показано на рисунке ниже:

На странице репозитория GitHub нажмите Settings -> Secrets -> Actions и нажмите кнопку New repository secret

Затем добавьте codecov/action в ваш файл main.yml:

- name: Upload to code coverage
  uses: codecov/[email protected]
  with:
    token: ${{secrets.CODECOV_TOKEN}}
    file: coverage/lcov.info

После нажатия конвейер снова запустится, но на этот раз после запуска ваших модульных тестов он загрузит информацию о покрытии в Codecov и сгенерирует новый отчет о покрытии.

Обновите портал Codecov, и вы сможете увидеть новый отчет и статистику о покрытии кода вашего проекта.

В разделе настроек вашего проекта Codecov вы найдете значки покрытия, которые можно добавить в файл readme.md вашего проекта.

Запуск интеграционных тестов

Интеграционные тесты, в отличие от модульных тестов, не будут тестировать простой фрагмент кода, вместо этого они будут выполнять все приложение и тестировать его так, как если бы это был пользователь, в нашем случае интеграционный тест должен открыть приложение, нажать кнопку «плюс» и проверить если число увеличилось на экране.

Итак, давайте создадим наш интегрированный тест:

В pubspec.yaml добавьте зависимость integration_test

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 '../lib/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  group('end-to-end test', () {
    testWidgets('tap on the floating action button, verify counter', (tester) async {
      app.main();
      await tester.pumpAndSettle();
      
      // Verify the counter starts at 0.
      expect(find.text('0'), findsOneWidget);
      
      // Finds the floating action button to tap on.
      final Finder fab = find.byTooltip('Increment');

      // Emulate a tap on the floating action button.
      await tester.tap(fab);

      // Trigger a frame.
      await tester.pumpAndSettle();

      // Verify the counter increments by 1.
      expect(find.text('1'), findsOneWidget);
    });
  });
}

Flutter предоставляет очень простой и легкий способ запуска интеграционных тестов с помощью ChromeDriver, и мы можем использовать его после Введение в интеграционный тест Flutter cookbook (где я получил тест выше), но в нашем случае мы будем использовать что-то другое, вместо использования поддельного устройства мы загрузим эмулятор Android внутри нашего рабочего процесса, а затем выполним наши тесты.

Для этого мы будем использовать действие reactivecircus/android-emulator-runner, предоставив API-уровень нашего проекта и скрипт интеграционного теста, который будет выполнен, когда эмулятор станет доступен.

- name: Run integration tests
  uses: reactivecircus/android-emulator-runner@v1
  with:
    api-level: 29
    script: flutter test integration_test

После ее нажатия мы видим в логах загрузку эмулятора и выполнение тестов:

Сборка и загрузка APK

Для каждой сборки будет создан APK-файл Flutter. Вы можете воспользоваться рабочим процессом и загрузить этот APK в сборку, если хотите загрузить и установить его на любое устройство Android или эмулятор.

Для этого нам сначала нужно запустить команду flutter build apk к нашему уже определенному флаттерному действию suposito.

- name: Install Flutter
  uses: subosito/flutter-action@v2
  with:
    flutter-version: '2.10.0'
    channel: 'stable'
- run: flutter build apk

Затем добавьте действие upload-artifact и укажите физический путь к APK.

- name: Upload APK
  uses: actions/upload-artifact@v3
  with:
    name: release-apk
    path: build/app/outputs/apk/release/app-release.apk

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

И мы можем найти APK-файл в разделе артефактов выполнения задания:

Делаем тестовые скриншоты

Чтобы делать скриншоты во время наших интеграционных тестов, нам понадобится тестовый драйвер, поэтому добавьте файл test_driver/main_test.dart в свой проект и вставьте следующий код:

import 'dart:io';

import 'package:integration_test/integration_test_driver_extended.dart';

Future<void> main() async {
  try {
    await integrationDriver(
      onScreenshot: (String screenshotName, List<int> screenshotBytes) async {
        final File image = await File('screenshots/$screenshotName.png').create(recursive: true);

        image.writeAsBytesSync(screenshotBytes);
        return true;
      },
    );
  } catch (e) {
    print('onScreenshot - error - $e');
  }
}

Теперь давайте адаптируем интеграционный тест, чтобы он делал скриншот при выполнении тестов:

Сначала добавьте этот оператор в первую строку метода main.

final binding = IntegrationTestWidgetsFlutterBinding();

Добавьте следующий метод takeScreenshot в тестовый файл.

takeScreenshot(tester, binding, name) async {
  if (Platform.isAndroid) {
    try {
      await binding.convertFlutterSurfaceToImage();
    } catch (e) {
      print("TakeScreenshot exception $e");
    }
    await tester.pumpAndSettle();
  }

  await binding.takeScreenshot(name);
}

Вам также потребуется импортировать пакет dart.io

import 'dart:io';

Теперь мы можем вызывать метод takeScreenshot каждый раз, когда захотим, я дважды добавляю его в существующий тест ниже:

testWidgets('tap on the floating action button, verify counter', (tester) async {
  app.main();
  await tester.pumpAndSettle();

  // Verify the counter starts at 0.
  expect(find.text('0'), findsOneWidget);
  await takeScreenshot(tester, binding, 'shot-1');

  // Finds the floating action button to tap on.
  final Finder fab = find.byTooltip('Increment');

  // Emulate a tap on the floating action button.
  await tester.tap(fab);

  // Trigger a frame.
  await tester.pumpAndSettle();
  await takeScreenshot(tester, binding, 'shot-2');

  // Verify the counter increments by 1.
  expect(find.text('1'), findsOneWidget);
});

Затем в параметре сценария действия измените его, чтобы выполнить следующий новый сценарий:

- name: Run integration tests
  uses: reactivecircus/android-emulator-runner@v1
  with:
    api-level: 29
    script: flutter drive --driver=test_driver/main_test.dart --target integration_test/app_test.dart

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

И последнее, но не менее важное: добавьте код для загрузки скриншотов в раздел артефактов.

- name: Upload Screenshoots
  uses: actions/upload-artifact@v3
  with:
    name: Test result screenshots
    path: screenshots/*.png

После отправки его в репозиторий мы можем проверить раздел артефактов и увидеть zip-файл со скриншотами со всеми скриншотами, созданными во время интеграционных тестов.

Скачайте файл, разархивируйте и проверьте результат:

В итоге наш полный файл main.yml должен выглядеть так:

Теперь ... это все

Если вы хотите проверить созданный мной проект, смело скопируйте его на мою страницу GitHub.

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

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