Во Flutter три уровня тестов. Чем выше уровень, тем ближе к пользователю и тем медленнее и дороже тест. Стандартный совет: много unit-тестов, достаточно widget-тестов и немного integration-тестов на ключевые сценарии.
| Уровень | Что проверяет | Где выполняется | Скорость |
|---|---|---|---|
| Unit | функцию, класс, блок | Dart VM | мгновенно |
| Widget | один виджет или экран | тестовое окружение без устройства | быстро |
| Integration | приложение целиком | эмулятор или устройство | медленно |
Unit-тесты
Пакет test (во Flutter-проекте flutter_test, который его включает):
void main() {
group('Cart', () {
late Cart cart;
setUp(() => cart = Cart());
test('сумма считается с учётом количества', () {
cart.add(const Item(price: 100), qty: 2);
expect(cart.total, 200);
});
});
}
Зависимости (API, база) подменяют: моками через mocktail или mockito (последний требует генерации кода) или простыми фейковыми классами. Для BLoC есть blocTest: задаёшь начальное состояние, действие и ожидаемую последовательность состояний.
Widget-тесты
testWidgets поднимает виджет без устройства. Время в тесте виртуальное: кадры и таймеры двигаются только когда вы их «прокачиваете».
testWidgets('счётчик растёт по нажатию', (tester) async {
await tester.pumpWidget(const MaterialApp(home: CounterPage()));
expect(find.text('0'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add));
await tester.pump(); // один кадр после setState
expect(find.text('1'), findsOneWidget);
});
pumpWidgetстроит виджет. Почти всегда его оборачивают вMaterialAppи провайдеры, иначе нет темы,Directionality, навигатора.pump()отрисовывает один кадр,pump(Duration)продвигает время.pumpAndSettle()крутит кадры, пока анимации не закончатся. С бесконечной анимацией (CircularProgressIndicator,repeat()) он упадёт по тайм-ауту: там нуженpumpс длительностью.- Поиск:
find.text,find.byType,find.byKey,find.byIcon. Проверки:findsOneWidget,findsNothing,findsNWidgets(n). - Действия:
tap,enterText,drag,longPress.
Ключи (Key('submit')) стабильнее текста: текст меняется при локализации.
Golden-тесты
Сравнивают картинку виджета с эталонным PNG: await expectLater(find.byType(Card), matchesGoldenFile('card.png'));. Эталоны обновляют флагом flutter test --update-goldens. Минус: шрифты и сглаживание отличаются между ОС, поэтому golden-тесты гоняют в одном окружении, например в CI на Linux.
Integration-тесты
Пакет integration_test запускает настоящее приложение на эмуляторе или устройстве тем же API WidgetTester. Подходят для критичных сценариев: вход, оплата, онбординг. Сторонние инструменты вроде Patrol добавляют работу с нативными диалогами (разрешения, уведомления), которые сам integration_test не видит.
Что делает код тестируемым
- Зависимости передаются снаружи (конструктор, провайдер,
get_it), а не создаются внутри виджета. - Логика в блоке или нотифаере, а не в
build. - Время и случайность абстрагированы:
clock, свой генератор.
Что спросят на собесе
- Виды тестов во Flutter, чем отличаются, пропорции.
pumpпротивpumpAndSettle, почему тест зависает на индикаторе загрузки.- Как замокать зависимости,
mocktailпротивmockito. - Что такое golden-тесты и их подводные камни.
- Как сделать экран тестируемым.