🧪 Интеграционное тестирование 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 вашего приложения, которая без устали нажимает, прокручивает, заполняет формы и получает данные как настоящий пользователь. От процесса входа до режима оффлайн и бесконечных лент, эти тесты ловят скрытые баги до того, как их заметят ваши пользователи.
Не ждите отчета о сбое, чтобы узнать, что сломалось.
✅ Начните с малого.
🔁 Автоматизируйте процессы.
🚀 Отправляйте с уверенностью.
Если вам понравился этот гайд, поставьте ему лайк (или три 👏👏👏) и оставьте комментарий ниже — мы хотим узнать, какой интеграционный тест спас ваш релиз!
Давайте вместе создавать надежные приложения. 💙