
Gad Ntenta
Développeur Full Stack
Tests dans Flutter : Guide Complet
FlutterTestingUnit TestsIntegration Tests
Apprenez à écrire des tests unitaires, d'intégration et de widget dans vos applications Flutter.
Tests dans Flutter : Guide Complet
Introduction
Les tests sont essentiels pour garantir la qualité et la fiabilité de vos applications Flutter. Dans cet article, nous allons explorer les différents types de tests et les meilleures pratiques pour tester vos applications Flutter.
Types de Tests
1. Tests Unitaires
// calculator.dart
class Calculator {
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
int multiply(int a, int b) => a * b;
double divide(int a, int b) {
if (b == 0) throw DivisionByZeroException();
return a / b;
}
}
// calculator_test.dart
void main() {
group('Calculator', () {
late Calculator calculator;
setUp(() {
calculator = Calculator();
});
test('addition de deux nombres', () {
expect(calculator.add(2, 3), equals(5));
expect(calculator.add(-1, 1), equals(0));
expect(calculator.add(0, 0), equals(0));
});
test('soustraction de deux nombres', () {
expect(calculator.subtract(5, 3), equals(2));
expect(calculator.subtract(1, 1), equals(0));
expect(calculator.subtract(0, 5), equals(-5));
});
test('multiplication de deux nombres', () {
expect(calculator.multiply(2, 3), equals(6));
expect(calculator.multiply(-2, 3), equals(-6));
expect(calculator.multiply(0, 5), equals(0));
});
test('division de deux nombres', () {
expect(calculator.divide(6, 2), equals(3.0));
expect(calculator.divide(5, 2), equals(2.5));
expect(calculator.divide(0, 5), equals(0.0));
});
test('division par zéro', () {
expect(() => calculator.divide(5, 0), throwsA(isA<DivisionByZeroException>()));
});
});
}
2. Tests de Widget
// counter_widget.dart
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Compteur: $_counter',
style: Theme.of(context).textTheme.headline4,
),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Incrémenter'),
),
],
);
}
}
// counter_widget_test.dart
void main() {
testWidgets('CounterWidget test', (WidgetTester tester) async {
// Construire le widget
await tester.pumpWidget(MaterialApp(home: CounterWidget()));
// Vérifier le compteur initial
expect(find.text('Compteur: 0'), findsOneWidget);
// Appuyer sur le bouton
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
// Vérifier que le compteur a été incrémenté
expect(find.text('Compteur: 1'), findsOneWidget);
// Appuyer plusieurs fois
for (int i = 0; i < 5; i++) {
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
}
// Vérifier le compteur final
expect(find.text('Compteur: 6'), findsOneWidget);
});
}
3. Tests d'Intégration
// integration_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Test d'intégration', () {
testWidgets('Navigation complète', (tester) async {
app.main();
await tester.pumpAndSettle();
// Vérifier la page d'accueil
expect(find.text('Bienvenue'), findsOneWidget);
// Naviguer vers la page de connexion
await tester.tap(find.text('Se connecter'));
await tester.pumpAndSettle();
// Remplir le formulaire de connexion
await tester.enterText(find.byType(TextFormField).first, 'test@example.com');
await tester.enterText(find.byType(TextFormField).last, 'password123');
await tester.tap(find.text('Connexion'));
await tester.pumpAndSettle();
// Vérifier la navigation vers le tableau de bord
expect(find.text('Tableau de bord'), findsOneWidget);
});
});
}
Mocks et Stubs
1. Utilisation de Mockito
// user_repository.dart
abstract class UserRepository {
Future<User> getUser(String id);
Future<List<User>> getUsers();
}
// user_repository_test.dart
@GenerateMocks([UserRepository])
void main() {
late MockUserRepository mockRepository;
late UserService userService;
setUp(() {
mockRepository = MockUserRepository();
userService = UserService(mockRepository);
});
test('getUser retourne un utilisateur', () async {
// Arrange
final user = User(id: '1', name: 'Test User');
when(mockRepository.getUser('1')).thenAnswer((_) async => user);
// Act
final result = await userService.getUser('1');
// Assert
expect(result, equals(user));
verify(mockRepository.getUser('1')).called(1);
});
test('getUsers retourne une liste d'utilisateurs', () async {
// Arrange
final users = [
User(id: '1', name: 'User 1'),
User(id: '2', name: 'User 2'),
];
when(mockRepository.getUsers()).thenAnswer((_) async => users);
// Act
final result = await userService.getUsers();
// Assert
expect(result, equals(users));
expect(result.length, equals(2));
verify(mockRepository.getUsers()).called(1);
});
}
2. Utilisation de Fake Async
void main() {
test('test avec fake async', () {
// Arrange
final completer = Completer<String>();
final future = completer.future;
// Act
future.then((value) => print(value));
// Assert
expect(future, completion(equals('test')));
completer.complete('test');
});
test('test avec fake async et délai', () {
// Arrange
final completer = Completer<String>();
final future = completer.future;
// Act
future.then((value) => print(value));
// Assert
expect(future, completion(equals('test')));
Future.delayed(Duration(seconds: 1), () {
completer.complete('test');
});
});
}
Tests de Performance
1. Mesure des Performances
void main() {
test('test de performance', () {
// Arrange
final stopwatch = Stopwatch()..start();
final list = List.generate(1000, (index) => index);
// Act
final result = list.where((e) => e % 2 == 0).toList();
// Assert
stopwatch.stop();
expect(stopwatch.elapsedMilliseconds, lessThan(100));
expect(result.length, equals(500));
});
}
2. Tests de Mémoire
void main() {
test('test de mémoire', () {
// Arrange
final initialMemory = ProcessInfo.currentRss;
final list = List.generate(1000000, (index) => index);
// Act
final result = list.where((e) => e % 2 == 0).toList();
// Assert
final finalMemory = ProcessInfo.currentRss;
expect(finalMemory - initialMemory, lessThan(100 * 1024 * 1024)); // 100 MB
});
}
Meilleures Pratiques
1. Organisation des Tests
- Suivre la structure du code source
- Utiliser des groupes de tests
- Nommer les tests de manière descriptive
- Isoler les tests
2. Écriture des Tests
- Suivre le pattern AAA (Arrange, Act, Assert)
- Tester les cas limites
- Éviter les tests fragiles
- Maintenir les tests à jour
3. Automatisation
- Intégrer les tests dans le CI/CD
- Exécuter les tests régulièrement
- Générer des rapports de couverture
- Surveiller les performances
Conclusion
Les tests sont une partie essentielle du développement d'applications Flutter. En suivant ces bonnes pratiques et en utilisant les outils appropriés, vous pouvez garantir la qualité et la fiabilité de vos applications.
N'oubliez pas de :
- Écrire des tests pour chaque fonctionnalité
- Maintenir une bonne couverture de tests
- Automatiser l'exécution des tests
- Documenter vos tests
Avec ces connaissances, vous êtes prêt à écrire des tests robustes pour vos applications Flutter !