🧪 Интеграционное тестирование Flutter на реальных устройствах: примеры, кейсы и автоматизация CI

🧪 Интеграционное тестирование Flutter на реальных устройствах: примеры, кейсы и автоматизация CI

FlutterPulse

Эта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Flutter делает создание красивых приложений проще, чем когда-либо — но как насчет надежных приложений?

Вы отправили сборку. Казалось, все в порядке. Но затем…

  • Пользователи не могут войти 🙄
  • Оплата аварийно завершается 💸
  • ListView не прокручивается на некоторых устройствах Android 😩

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

🧠 Что такое интеграционное тестирование в Flutter?

Интеграционное (также End-to-End или E2E) тестирование в Flutter проверяет, как несколько виджетов и сервисов работают вместе — в реальном сценарии приложения.

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

В сравнении с другими тестами:

  • Unit Test:
    Сфокусировано на тестировании чистой логики или отдельных функций в изоляции.
    Пример: Тестирование функции, такой как validateEmail() чтобы убедиться, что она возвращает правильный результат для разных входных данных.
  • Widget Test:
    Нацелено на отдельные виджеты и их взаимодействия с интерфейсом. Полезно для проверки компоновки, жестов и изменений состояния в пределах одного виджета.
    Пример: Тестирование формы входа, чтобы убедиться, что текстовые поля и нажатия кнопок ведут себя как ожидается.
  • Integration Test:
    Покрывает весь поток приложения, включая интерфейс, навигацию и сервисы бэкенда. Идеально для симуляции реального поведения пользователя на нескольких экранах.
    Пример: Тестирование полного процесса входа → навигация на домашний экран → получение данных из API.

⚙️ Настройка интеграционного тестирования в Flutter

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

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

Запустите:

flutter pub get

Структура папок:

/integration_test/
└── auth_flow_test.dart
└── form_validation_test.dart
/test_driver/
└── integration_test.dart

🔐 1. Поток аутентификации (вход/регистрация)

✅ Тест: Пользователь успешно входит

testWidgets('Login with valid credentials', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());

await tester.enterText(find.byKey(Key('email_field')), 'user@example.com');
await tester.enterText(find.byKey(Key('password_field')), 'password123');
await tester.tap(find.byKey(Key('login_button')));
await tester.pumpAndSettle();

expect(find.text('Welcome Back!'), findsOneWidget);
});

🔒 Тест: Блокировка входа с некорректными данными

testWidgets('Invalid login shows error', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());

await tester.enterText(find.byKey(Key('email_field')), '');
await tester.enterText(find.byKey(Key('password_field')), '');
await tester.tap(find.byKey(Key('login_button')));
await tester.pumpAndSettle();

expect(find.text('Please enter email'), findsOneWidget);
});

🧾 2. Ввод и валидация формы

📝 Тест: Заполнение и отправка формы

testWidgets('Contact form submits successfully', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());

await tester.enterText(find.byKey(Key('name_field')), 'Avyaan');
await tester.enterText(find.byKey(Key('message_field')), 'Need help!');
await tester.tap(find.byKey(Key('submit_button')));
await tester.pumpAndSettle();

expect(find.text('Message Sent'), findsOneWidget);
});

❌ Тест: Предотвращение отправки без обязательных полей

testWidgets('Form validation error on empty submit', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());

await tester.tap(find.byKey(Key('submit_button')));
await tester.pump();

expect(find.text('Name is required'), findsOneWidget);
});

🔀 3. Тестирование навигации

📱 Тест: Навигация от Splash → Home → Профиль

testWidgets('Complete navigation flow', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());

await tester.pumpAndSettle(); // splash screen delay
expect(find.text('Welcome'), findsOneWidget);

await tester.tap(find.byKey(Key('go_to_home')));
await tester.pumpAndSettle();

expect(find.text('Home Screen'), findsOneWidget);

await tester.tap(find.byKey(Key('profile_nav')));
await tester.pumpAndSettle();

expect(find.text('User Profile'), findsOneWidget);
});

🌐 4. Ответ API и отображение данных

🛰️ Тест: Получение и отображение данных API

testWidgets('Fetch user list from server', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());

await tester.tap(find.byKey(Key('load_users')));
await tester.pumpAndSettle();

expect(find.textContaining('Name:'), findsWidgets);
});

📦 5. ListView, прокрутка и ленивая загрузка

🎡 Тест: Прокрутка длинного списка

testWidgets('Scroll through list items', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());

final list = find.byKey(Key('user_list'));
await tester.fling(list, Offset(0, -800), 1000); // fast upward scroll
await tester.pumpAndSettle();

expect(find.text('User #50'), findsOneWidget);
});

📴 6. Поведение в оффлайн-режиме

📵 Тест: Приложение корректно обрабатывает отсутствие интернета

testWidgets('Show offline banner if no internet', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());

// Assuming your app checks connectivity and shows a banner
expect(find.text('No Internet Connection'), findsOneWidget);
});

Используйте пакеты вроде connectivity_plus + внедрение зависимостей для имитации поведения в оффлайн-режиме.

🔁 7. Shared Preferences или локальное хранилище

📌 Тест: Сохранение состояния входа

testWidgets('Приложение запоминает состояние входа', (WidgetTester tester) async {
// Симулируем вход и перезапуск приложения

await tester.pumpWidget(MyApp());
await tester.enterText(find.byKey(Key('email_field')), 'user@example.com');
await tester.enterText(find.byKey(Key('password_field')), '123456');
await tester.tap(find.byKey(Key('login_button')));
await tester.pumpAndSettle();

// Перезапуск приложения
await tester.pumpWidget(MyApp());
await tester.pumpAndSettle();

expect(find.text('Dashboard'), findsOneWidget);
});

🧪 Запуск тестов на реальных устройствах

Подключите устройство (через USB), включите режим разработчика и убедитесь, что оно обнаружено:

flutter devices

Запустите тесты:

flutter test integration_test/

Или используйте flutter drive для полной симуляции приложения:

flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/auth_flow_test.dart

🚀 Автоматизация с помощью GitHub Actions

Как показано ранее, вот быстрая конфигурация CI:

name: Flutter E2E Test

on: [push, pull_request]

jobs:
integration_test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.19.0'

- run: flutter pub get
- run: flutter test integration_test/

Для полного тестирования устройств (эмуляторов или Firebase Test Lab) рассмотрите:

🧭 Итог: Где использовать интеграционное тестирование в Flutter

💡 Полезные советы для чистого тестирования

  • Используйте Key() для важных виджетов для легкого целеполагания
  • Добавьте утилитарные функции для loginUser(tester) для повторного использования потоков
  • Группируйте свои тесты по экранам (например, auth_test.dart, profile_test.dart)
  • Оберните повторяющиеся взаимодействия в вспомогательные методы или миксины

🏁 Всё — теперь ваша очередь!

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

Не ждите отчета о сбое, чтобы узнать, что сломалось.
✅ Начните с малого.
🔁 Автоматизируйте процессы.
🚀 Отправляйте с уверенностью.

Если вам понравился этот гайд, поставьте ему лайк (или три 👏👏👏) и оставьте комментарий ниже — мы хотим узнать, какой интеграционный тест спас ваш релиз!

Давайте вместе создавать надежные приложения. 💙

Report Page