В чем разница между widget-тестом и unit-тестом во Flutter?

Ответ

Unit-тесты проверяют изолированную бизнес-логику — функции, классы, алгоритмы — без зависимостей от Flutter фреймворка. Они быстрые и используют пакет test.

// Тестируемый класс (логика)
class Calculator {
  int add(int a, int b) => a + b;
  double divide(int a, int b) {
    if (b == 0) throw ArgumentError('Cannot divide by zero');
    return a / b;
  }
}

// Unit-тест
import 'package:test/test.dart';

void main() {
  test('Calculator.add returns sum of two numbers', () {
    final calc = Calculator();
    expect(calc.add(2, 3), equals(5));
  });

  test('Calculator.divide throws error when dividing by zero', () {
    final calc = Calculator();
    expect(() => calc.divide(5, 0), throwsArgumentError);
  });
}

Widget-тесты (компонентные тесты) проверяют виджеты Flutter в изолированной среде. Они используют flutter_test, могут имитировать пользовательские взаимодействия (тап, ввод текста) и проверять построенное дерево виджетов.

// Widget-тест
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/widgets/counter_button.dart';

void main() {
  testWidgets('CounterButton increments counter on tap', (WidgetTester tester) async {
    int counter = 0;

    // Строим виджет
    await tester.pumpWidget(
      MaterialApp(
        home: CounterButton(
          count: counter,
          onPressed: () => counter++,
        ),
      ),
    );

    // Проверяем начальное состояние
    expect(find.text('Count: 0'), findsOneWidget);

    // Имитируем тап по кнопке
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump(); // Перестраиваем виджет

    // Проверяем обновленное состояние (виджет перестроился с новым значением)
    expect(find.text('Count: 1'), findsOneWidget);
  });
}
Сравнение: Критерий Unit-тест Widget-тест
Скорость Быстрый (миллисекунды) Медленнее (запускает Flutter engine)
Зависимости Только Dart, без Flutter Требует Flutter фреймворк
Тестирует Логику, алгоритмы, модели UI, взаимодействие, построение виджетов
Инструмент Пакет test Пакет flutter_test, WidgetTester
Использование Тестирование ViewModel, репозиториев, утилит Тестирование виджетов, экранов, навигации

Ответ 18+ 🔞

А, ну так, слушай, тут про тесты в Flutter'е объясняют. В общем, смотри, есть два основных вида, и если их не различать — это пиздец, а не разработка. Сейчас разжую.

Unit-тесты — это, блядь, святое. Они проверяют голую бизнес-логику: твои функции, классы, алгоритмы. Всё это безо всякой привязки к самому Flutter'у, то есть фреймворк им нахуй не упёрся. Они быстрые, как угорелые, и используют пакет test. Просто, понятно, без лишней ебалы.

Вот смотри, как это выглядит. Берём какой-нибудь класс Calculator, который складывает и делит. Ну, обычная математика, ёпта.

// Тестируемый класс (логика)
class Calculator {
  int add(int a, int b) => a + b;
  double divide(int a, int b) {
    if (b == 0) throw ArgumentError('Cannot divide by zero');
    return a / b;
  }
}

// Unit-тест
import 'package:test/test.dart';

void main() {
  test('Calculator.add returns sum of two numbers', () {
    final calc = Calculator();
    expect(calc.add(2, 3), equals(5));
  });

  test('Calculator.divide throws error when dividing by zero', () {
    final calc = Calculator();
    expect(() => calc.divide(5, 0), throwsArgumentError);
  });
}

Видишь? Никаких виджетов, никакого WidgetTester'а. Чистая логика. Написал, запустил — и через полсекунды уже знаешь, работает твоя функция или она — пидарас шерстяной, который на ноль делит.

А теперь Widget-тесты (или компонентные). Вот тут уже начинается ёперный театр. Они проверяют сами виджеты, но в изолированной среде. Используют flutter_test, и могут имитировать, как пользователь тыкает, скроллит или текст вводит. По сути, ты смотришь, правильно ли твоя кнопка строится и реагирует ли она на тап.

// Widget-тест
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/widgets/counter_button.dart';

void main() {
  testWidgets('CounterButton increments counter on tap', (WidgetTester tester) async {
    int counter = 0;

    // Строим виджет
    await tester.pumpWidget(
      MaterialApp(
        home: CounterButton(
          count: counter,
          onPressed: () => counter++,
        ),
      ),
    );

    // Проверяем начальное состояние
    expect(find.text('Count: 0'), findsOneWidget);

    // Имитируем тап по кнопке
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump(); // Перестраиваем виджет

    // Проверяем обновленное состояние (виджет перестроился с новым значением)
    expect(find.text('Count: 1'), findsOneWidget);
  });
}

Смотри, что происходит: ты поднимаешь целый Flutter engine, чтобы проверить одну кнопку. Это уже не миллисекунды, а ощутимо дольше. Но зато ты уверен, что UI не разъёбывается при первом же клике.

Так в чём разница, спросишь ты? Да вот, смотри табличку, я для таких, как ты, собрал:

Критерий Unit-тест Widget-тест
Скорость Быстрый (миллисекунды) Медленнее (запускает Flutter engine)
Зависимости Только Dart, без Flutter Требует Flutter фреймворк
Тестирует Логику, алгоритмы, модели UI, взаимодействие, построение виджетов
Инструмент Пакет test Пакет flutter_test, WidgetTester
Использование Тестирование ViewModel, репозиториев, утилит Тестирование виджетов, экранов, навигации

Короче, суть в чём: unit-тесты — это для твоего бэкенда в приложении, для всяких AuthService или OrderCalculator. А widget-тесты — это когда тебе надо убедиться, что твой красивый экран с анимациями не превращается в манда с ушами после обновления состояния. Если будешь всё unit-тестами покрывать — доверия ебать ноль к UI останется. Если всё widget-тестами гонять — будешь ждать сборку до второго пришествия. Сочетай, ебать копать, с умом.