Tests dans Flutter : Guide Complet
Gad Ntenta

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 !