← Назад к вопросам
Как тестировать Flutter-приложения? Какие виды тестов существуют?
2.0 Middle🔥 252 комментариев
#Тестирование
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Тестирование во Flutter
Три типа тестов
Фреймворк Flutter поддерживает три основных вида тестирования:
┌─────────────────────────────────────────────┐
│ Unit Tests (Юнит-тесты) │
│ Тестирование отдельных функций, классов │
│ Быстро | Не нужен эмулятор | Просто │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Widget Tests (Виджет-тесты) │
│ Тестирование UI компонентов и виджетов │
│ Средняя скорость | Изолированные тесты │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Integration Tests (E2E тесты) │
│ Полное приложение от начала до конца │
│ Медленно | Нужен эмулятор | Реальные BL │
└─────────────────────────────────────────────┘
1. Unit Tests (Юнит-тесты)
Тестируем функции, классы, логику БЕЗ UI:
import "package:flutter_test/flutter_test.dart";
void main() {
group("Calculator Tests", () {
// Простая функция для тестирования
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
test("add returns correct sum", () {
expect(add(2, 3), equals(5));
expect(add(-1, 1), equals(0));
expect(add(0, 0), equals(0));
});
test("subtract returns correct difference", () {
expect(subtract(5, 3), equals(2));
expect(subtract(-1, -1), equals(0));
});
test("divide by zero throws exception", () {
expect(
() => 10 ~/ 0,
throwsException,
);
});
});
}
2. Widget Tests (Виджет-тесты)
Тестируем отдельные виджеты в изоляции:
import "package:flutter/material.dart";
import "package:flutter_test/flutter_test.dart";
void main() {
group("Counter Button Tests", () {
testWidgets("Button increments counter", (WidgetTester tester) async {
// Собрать виджет
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Counter(),
),
),
);
// Проверить начальное состояние
expect(find.text("0"), findsOneWidget);
expect(find.byIcon(Icons.add), findsOneWidget);
// Нажать на кнопку
await tester.tap(find.byIcon(Icons.add));
await tester.pump(); // Пересчитать виджет
// Проверить новое состояние
expect(find.text("1"), findsOneWidget);
});
testWidgets("Multiple taps work correctly", (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(body: Counter()),
),
);
// Тапнуть 5 раз
for (int i = 0; i < 5; i++) {
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
}
expect(find.text("5"), findsOneWidget);
});
testWidgets("Text field accepts input", (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: TextField(),
),
),
);
await tester.enterText(find.byType(TextField), "Hello");
await tester.pump();
expect(find.text("Hello"), findsOneWidget);
});
});
}
class Counter extends StatefulWidget {
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("$count"),
IconButton(
icon: Icon(Icons.add),
onPressed: () => setState(() => count++),
),
],
);
}
}
3. Integration Tests (E2E тесты)
Тестируем полное приложение:
// integration_test/app_test.dart
import "package:flutter/material.dart";
import "package:flutter_test/flutter_test.dart";
import "package:integration_test/integration_test.dart";
import "package:my_app/main.dart";
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group("App Integration Tests", () {
testWidgets("Full user flow", (WidgetTester tester) async {
// Запустить приложение
app();
await tester.pumpAndSettle();
// Нажать на кнопку "Sign Up"
await tester.tap(find.text("Sign Up"));
await tester.pumpAndSettle();
// Ввести email
await tester.enterText(
find.byType(TextField).first,
"test@example.com",
);
// Ввести пароль
await tester.enterText(
find.byType(TextField).last,
"password123",
);
// Нажать "Create Account"
await tester.tap(find.text("Create Account"));
await tester.pumpAndSettle(Duration(seconds: 2));
// Проверить, что мы на главной странице
expect(find.text("Welcome"), findsOneWidget);
});
});
}
Тестирование с Mockito
import "package:mockito/mockito.dart";
// Создать Mock класс
class MockHttpClient extends Mock implements http.Client {}
void main() {
group("User Repository Tests", () {
late MockHttpClient mockHttpClient;
late UserRepository repository;
setUp(() {
mockHttpClient = MockHttpClient();
repository = UserRepository(mockHttpClient);
});
test("fetchUser returns user when API call succeeds", () async {
// Настроить Mock
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response(
jsonEncode({"id": 1, "name": "John"}),
200,
),
);
// Выполнить
final user = await repository.fetchUser(1);
// Проверить
expect(user.name, "John");
verify(mockHttpClient.get(any)).called(1);
});
test("fetchUser throws exception on API error", () async {
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response("Not Found", 404),
);
expect(
() => repository.fetchUser(999),
throwsException,
);
});
});
}
Тестирование BLoC
import "package:bloc_test/bloc_test.dart";
void main() {
group("CounterBloc Tests", () {
late CounterBloc counterBloc;
setUp(() {
counterBloc = CounterBloc();
});
tearDown(() {
counterBloc.close();
});
test("initial state is zero", () {
expect(counterBloc.state, 0);
});
blocTest<CounterBloc, int>(
"emits [1, 2, 3] when IncrementEvent is added three times",
build: () => counterBloc,
act: (bloc) {
bloc.add(IncrementEvent());
bloc.add(IncrementEvent());
bloc.add(IncrementEvent());
},
expect: () => [1, 2, 3],
);
});
}
Покрытие кода (Code Coverage)
# Запустить тесты с покрытием
flutter test --coverage
# Сгенерировать отчёт
genhtml coverage/lcov.info -o coverage/html
# Открыть отчёт в браузере
open coverage/html/index.html
Команды для запуска тестов
# Unit тесты
flutter test test/unit/
# Widget тесты
flutter test test/widget/
# Интеграционные тесты
flutter test integration_test/
# Все тесты
flutter test
# Watch mode (перезапуск при изменениях)
flutter test --watch
# Конкретный файл
flutter test test/counter_test.dart
# Фильтр по названию
flutter test --name "Counter"
Best Practices
- Юнит тесты: тестируйте логику, бизнес-слой, утилиты
- Виджет тесты: тестируйте отдельные компоненты UI
- Интеграционные тесты: критические пути пользователя
- Покрытие: стремитесь к 80%+ покрытию
- Читаемость: используйте описательные имена тестов
- Изоляция: каждый тест должен быть независимым
- Скорость: юнит тесты должны быть быстрыми
Итог
Пирамида тестирования:
- Много юнит-тестов (быстро, дёшево)
- Среднее количество виджет-тестов (баланс)
- Мало интеграционных тестов (медленные, дорогие)