Ответ
В моих Flutter-проектах для unit-тестирования я использую следующий стек:
Основные пакеты:
test/flutter_test: основной фреймворк для написания тестов и ассертов.mocktail: для создания моков зависимостей. Предпочитаю его из-за null-safety и отсутствия необходимости в кодогенерации.bloc_test: если в проекте используется BLoC, этот пакет значительно упрощает тестирование cubit'ов и bloc'ов.
Пример теста бизнес-логики (без UI):
import 'package:test/test.dart';
class Counter {
int value = 0;
void increment() => value++;
void decrement() => value--;
}
void main() {
group('Counter', () {
late Counter counter;
setUp(() {
// Инициализация перед каждым тестом
counter = Counter();
});
test('value should start at 0', () {
expect(counter.value, 0);
});
test('value should be 1 after increment', () {
counter.increment();
expect(counter.value, 1);
});
test('value should be -1 after decrement', () {
counter.decrement();
expect(counter.value, -1);
});
});
}
Пример теста с моком (mocktail):
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';
// Репозиторий (зависимость)
abstract class UserRepository {
Future<String> fetchUserName(int id);
}
// Сервис, который тестируем
class UserService {
final UserRepository repository;
UserService(this.repository);
Future<String> getUserName(int id) async {
return 'User: ${await repository.fetchUserName(id)}';
}
}
// Мок репозитория
class MockUserRepository extends Mock implements UserRepository {}
void main() {
late UserService userService;
late MockUserRepository mockRepository;
setUp(() {
mockRepository = MockUserRepository();
userService = UserService(mockRepository);
});
test('getUserName should format data from repository', () async {
// Настраиваем мок
when(() => mockRepository.fetchUserName(1))
.thenAnswer((_) async => 'John');
// Выполняем и проверяем
final result = await userService.getUserName(1);
expect(result, 'User: John');
// Проверяем, что метод был вызван с правильным аргументом
verify(() => mockRepository.fetchUserName(1)).called(1);
});
}
Ключевые принципы, которых я придерживаюсь:
- Изоляция: Каждый тест должен быть независимым и не полагаться на состояние, созданное другими тестами.
- Одна ответственность: Тест проверяет одну конкретную функциональность или сценарий.
- Читаемость: Использую
setUp/tearDownдля подготовки и очистки, аgroupдля логической группировки тестов. - Скорость: Unit-тесты должны выполняться быстро, без эмулятора/симулятора.