Tests unitaires dans le développement mobile : ce qu'ils sont, méthodes et frameworks

Auteur : IT Sectr Publié le : 2026-04-06 Temps de lecture : 9 min

Les tests unitaires sont une méthode de vérification logicielle où l'on teste la correction de modules individuels ou de fonctions du code en isolation du reste du système. Selon Martin Fowler, 2026, les tests unitaires sont le fondement de l'IC/CD et du refactoring, fournissant un retour rapide sur le fonctionnement du code. Les tests modulaires aident à détecter les erreurs dès les premières étapes du développement, réduisant considérablement le coût de leur correction.

Points clés

  • Test unitaire — vérification d'un seul module (fonction, méthode, classe) isolé des dépendances externes
  • Principes FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — la base des tests de qualité
  • Mocks et stubs — substituts des dépendances externes (BD, API, système de fichiers) assurant l'isolation du test
  • TDD (Test-Driven Development) — méthodologie de développement piloté par les tests : rouge-vert-refactorisation
  • Pyramide des tests — les tests unitaires occupent 70% de la pyramide, fournissant un retour rapide à chaque commit

Qu'est-ce qu'un test unitaire ?

Un test unitaire est le processus de vérification d'unités individuelles du code source — fonctions, méthodes, classes — en isolation du reste du programme. Chaque test exécute un scénario d'utilisation spécifique du module et vérifie que le résultat correspond à celui attendu. Les tests unitaires sont écrits dans le même langage de programmation que le code principal et s'exécutent automatiquement dans l'environnement de développement ou dans un pipeline CI/CD. Contrairement aux tests d'intégration, les tests unitaires n'interagissent pas avec des bases de données réelles, des systèmes de fichiers ou des services réseau.

Pourquoi les tests unitaires sont-ils nécessaires ?

L'objectif principal est un retour rapide sur la correction du code après des modifications. Si un développeur refactorise une méthode, la suite de tests unitaires confirme que le comportement n'a pas été altéré. Selon Google Testing Blog (2025), les projets avec une couverture de tests unitaires supérieure à 60% ont 2,5 fois moins d'incidents en production. Avantages supplémentaires : documentation du code (les tests montrent comment utiliser l'API), simplification du refactoring (l'implémentation peut être modifiée tout en conservant le comportement) et diagnostic rapide des régressions.

Qu'est-ce qui est considéré comme un test unitaire ?

Ce n'est pas tout test automatisé qui est un test unitaire. Critères : un seul module (classe ou fonction) est testé, les dépendances externes sont remplacées par des mocks ou des stubs, le test s'exécute en millisecondes et ne nécessite pas de démarrer un serveur ou une base de données. Un test qui accède à une base de données réelle est un test d'intégration. Un test qui ouvre un navigateur est un test E2E. Comprendre les limites entre les types de tests est important pour répartir correctement les efforts dans la pyramide des tests.

Principes FIRST et structure AAA

Les tests unitaires de qualité suivent les principes FIRST formulés par Robert C. Martin. Chaque test doit être Fast (rapide — millisecondes), Isolated (isolé — ne dépend pas d'autres tests), Repeatable (reproductible — même résultat sur n'importe quelle machine), Self-validating (auto-validant — résultat « passed » ou « failed », sans vérification manuelle) et Timely (opportun — écrit avant ou simultanément au code). Violer un principe réduit la valeur du test.

Structure AAA (Arrange-Act-Assert)

Un modèle standard pour écrire des tests unitaires. Arrange — préparation des données et dépendances : création d'objets, configuration des mocks, définition des paramètres d'entrée. Act — exécution de l'action testée : appel d'une méthode ou fonction. Assert — vérification du résultat : comparaison de la valeur réelle avec la valeur attendue. La division en trois blocs rend le test lisible et compréhensible. Si le bloc Assert nécessite une logique complexe, le test vérifie probablement trop de choses à la fois.

kotlin
// Exemple de test unitaire avec le pattern AAA en Kotlin avec JUnit 5
class CalculatorTest {

    private lateinit var calculator: Calculator

    @BeforeEach
    fun setUp() {
        // ARRANGE — création de l'objet de test
        calculator = Calculator()
    }

    @Test
    fun addition_shouldReturnCorrectSum() {
        // ACT — exécution de l'action
        val result = calculator.add(2, 3)

        // ASSERT — vérification du résultat
        Assertions.assertEquals(5, result)
    }
}

Nommage des tests

Le nom du test doit décrire ce qui est vérifié et quel résultat est attendu. Format : [methodName]_[scenario]_[expectedResult]. Exemple : calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Un bon nom de test remplace un commentaire et, en cas d'échec, indique immédiatement quelle fonctionnalité est cassée. Évitez les noms comme test1, checkSomething ou verify — ils n'apportent pas d'information et compliquent le diagnostic.

Mocks, stubs et fakes : quoi et quand utiliser

Pour isoler le module testé des dépendances externes, on utilise des doubles de test (test doubles). Principaux types : mocks — vérifient qu'une méthode spécifique a été appelée avec les bons paramètres ; stubs — retournent des valeurs prédéfinies lors de l'appel d'une méthode ; fakes — implémentations simplifiées de composants réels (par exemple, InMemoryUserRepository au lieu de UserRepository qui travaille avec une BD). Le choix dépend de ce qui doit être vérifié : l'état (stub) ou l'interaction (mock).

DoubleCe qu'il vérifieExemple
MockAppel de méthode avec les bons paramètresuserRepository.save(user) a été appelé exactement 1 fois
StubValeur de retourrepository.findById(1) retourne User(id=1, name="Test")
FakeLogique via implémentation simplifiéeInMemoryMapUserRepository avec HashMap au lieu de BD
SpyMocking partiel d'un objet réelspy(repo).when(findById).thenReturn(user)

Mockito : exemple de mocking en Java/Kotlin

Mockito est le framework de mocking le plus populaire pour Java et Kotlin. Il permet de créer des mocks via mock(), de configurer des valeurs de retour via when().thenReturn() et de vérifier les appels via verify(). Les versions modernes de Mockito (5.x) prennent en charge les mocks statiques (mockStatic) et une syntaxe simplifiée via BDDMockito (given-willReturn). Une règle importante : ne mockez pas ce qui ne vous appartient pas — ne créez pas de mocks pour les objets valeurs et les bibliothèques standard.

kotlin
// Exemple de test unitaire avec Mockito en Kotlin
class OrderServiceTest {

    @Mock
    private lateinit var paymentGateway: PaymentGateway

    @Mock
    private lateinit var userRepository: UserRepository

    private lateinit var orderService: OrderService

    @BeforeEach
    fun init() {
        MockitoAnnotations.openMocks(this)
        orderService = OrderService(paymentGateway, userRepository)
    }

    @Test
    fun processOrder_whenPaymentFails_shouldThrowException() {
        // given
        val user = User(id = 1, balance = 100.0)
        val order = Order(amount = 200.0)
        Mockito.`when`(paymentGateway.charge(any())).thenReturn(false)
        Mockito.`when`(userRepository.findById(1)).thenReturn(user)

        // when & then
        assert Throws<PaymentException> {
            orderService.processOrder(user.id, order)
        }

        // verify
        Mockito.verify(paymentGateway).charge(any())
    }
}

TDD : développement piloté par les tests

TDD (Test-Driven Development) est une méthodologie où le test est écrit avant l'implémentation du code. Le cycle « Rouge-Vert-Refactor » : écrire un test qui échoue (Rouge), écrire le code minimal pour passer le test (Vert), améliorer le code sans changer le comportement (Refactor). TDD garantit que tout le code est couvert par des tests (couverture = 100% pour les fonctionnalités implémentées) et que le code est testable — si le code est difficile à tester, l'architecture doit être améliorée.

Avantages du TDD

Selon une étude d'IBM (2006-2026, étude longitudinale), les équipes utilisant TDD ont 40 à 80% moins de défauts en production par rapport aux équipes qui écrivent les tests après le code. TDD améliore également l'architecture : le développeur est obligé de penser à la conception de l'API avant l'impléparation, ce qui conduit à un couplage faible (loose coupling) et une forte cohésion (high cohesion). Un effet supplémentaire est la documentation vivante : les tests servent de spécification du comportement du module, toujours à jour.

Quand TDD n'est-il pas adapté ?

TDD n'est pas toujours optimal. Les composants d'interface utilisateur sont difficiles à tester isolément — les tests snapshot ou les tests de régression visuelle (Percy, Chromatic) sont plus efficaces. Le prototypage et la recherche (spike solutions) ne nécessitent pas de tests. Le code legacy sans tests est difficile à couvrir via TDD — ici, des tests de caractérisation (tests qui capturent le comportement actuel avant le refactoring) sont d'abord nécessaires. Dans ces cas, TDD n'est pas complètement abandonné mais adapté — des tests sont écrits pour la fonctionnalité modifiée, pas pour l'ensemble du code legacy.

Tests unitaires dans les applications mobiles

Le développement mobile a ses spécificités : la logique métier est souvent mélangée au code d'interface utilisateur (Activity, ViewController, ViewModel), ce qui complique les tests unitaires. La meilleure pratique est des vues fines, des ViewModels épais : extrayez toute la logique des composants d'interface utilisateur dans des classes séparées (UseCase, Repository, ViewModel) faciles à tester sans émulateur. Android et iOS ont des frameworks de tests unitaires natifs qui fonctionnent sur JVM/Native sans lancer d'appareil.

Tests unitaires sur Android (JUnit + Mockito/Robolectric)

Les tests unitaires Android s'exécutent sur une JVM locale sans émulateur, offrant une vitesse d'exécution — un test typique prend moins de 100 ms. JUnit 5 est le runner principal. Pour les tests ViewModel, utilisez kotlinx-coroutines-test pour tester les coroutines et Turbine pour tester StateFlow. Robolectric permet de tester les composants dépendant d'Android (Context, Resources) sans émulateur en chargeant des shadow-classes. Pour les tests Compose, utilisez Compose UI Test — mais ce sont des tests d'interface utilisateur, pas des tests unitaires.

Tests unitaires sur iOS (XCTest + Quick/Nimble)

Les tests unitaires iOS sont écrits en Swift avec XCTest (intégré dans Xcode). Quick + Nimble sont des frameworks BDD pour des tests plus lisibles (describe/context/it). Pour le mocking, utilisez Cuckoo (génération de mocks) ou SwiftyMocky. Swift prend en charge les protocoles et l'injection de dépendances, facilitant le remplacement des dépendances. Point clé : les tests unitaires iOS s'exécutent sur le simulateur macOS, pas sur un appareil réel. Les tests nécessitant des fonctions matérielles (appareil photo, Bluetooth) sont des tests d'intégration.

Tests unitaires sur Flutter (flutter_test + Mockito)

Les tests unitaires Flutter utilisent le package flutter_test et s'exécutent sur Dart VM sans émulateur. Pour le mocking, utilisez le package mockito avec génération de code (build_runner). Les tests de widgets (dans le même package) testent des widgets individuels mais nécessitent du rendu et sont plus lents — utilisez-les uniquement pour la vérification de la logique d'interface utilisateur. La logique Dart pure (modèles, référentiels, blocs) est testée comme des tests Dart normaux sans importer flutter_test.

dart
// Exemple de test unitaire sur Flutter avec mockito
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';

@GenerateMocks([ApiClient])
import 'user_repository_test.mocks.dart';

void main() {
    late MockApiClient mockApi;
    late UserRepository repository;

    setUp(() {
        mockApi = MockApiClient();
        repository = UserRepository(mockApi);
    });

    test('fetchUser returns user when API succeeds', () async {
        // Arrange
        final expectedUser = User(id: 1, name: 'Test');
        when(mockApi.getUser(1))
            .thenAnswer((_) async => expectedUser);

        // Act
        final result = await repository.fetchUser(1);

        // Assert
        expect(result, expectedUser);
        verify(mockApi.getUser(1)).called(1);
    });
}

Bonnes pratiques et erreurs courantes

Les tests unitaires efficaces exigent de la discipline. La règle principale : testez le comportement, pas l'implémentation. Le test ne doit pas savoir comment le module est implémenté en interne (quelles méthodes privées sont appelées, dans quel ordre). Si un test est lié à l'implémentation, il se brise à chaque refactorisation et perd sa valeur. Le test vérifie le contrat : pour une entrée X, la sortie doit être Y. L'exception concerne les tests d'algorithmes aux performances critiques, où l'ordre des appels est important.

  • Une vérification par test — un assert ou un groupe d'asserts liés pour une vérification logique
  • Évitez la duplication — utilisez @BeforeEach / setUp pour l'initialisation commune, des tests paramétrés pour différentes données d'entrée
  • Ne testez pas les méthodes privées — testez via l'API publique. Si une méthode privée n'est pas couverte, sa logique n'est pas visible de l'extérieur
  • Couvrez les cas limites — collections vides, null/undefined, nombres négatifs, valeurs maximales
  • N'utilisez pas Thread.sleep dans les tests — cela rend les tests lents et instables. Utilisez des timeouts de test et des coroutines

Quelle couverture est considérée comme suffisante ?

Une couverture de 100% est un objectif inaccessible et inutile. Selon Google Testing Blog (2025), le niveau de couverture optimal pour les tests unitaires est de 70 à 80% des lignes de code. Une couverture de 100% est souvent atteinte en testant les getters, setters et constructeurs, ce qui n'apporte aucune valeur. Concentrez-vous sur la logique métier critique : calculs complexes, validation, gestion des erreurs, cas limites. Utilisez JaCoCo (Java), Coverage.py (Python), Istanbul (JS) pour la mesure et définissez un seuil dans le CI — échec de build en dessous de 60% de couverture.

CI/CD et tests unitaires

Les tests unitaires sont la première étape de tout pipeline CI/CD. Ils s'exécutent à chaque push dans le dépôt, avant la construction et le déploiement. Le temps d'exécution moyen de la suite de tests unitaires ne doit pas dépasser 5 minutes — au-delà, les tests cessent d'être « rapides » et les développeurs cessent de les exécuter localement. Séparez les tests en rapides (unitaires) et lents (intégration) et exécutez-les dans différentes étapes du pipeline. Utilisez l'exécution parallèle et le fail-fast pour accélérer.

Questions fréquentes

Quelle est la différence entre un test unitaire et un test d'intégration ?

Un test unitaire vérifie un seul module isolément, en remplaçant les dépendances externes par des mocks. Un test d'intégration vérifie l'interaction entre plusieurs composants réels (BD, API, système de fichiers). Les tests unitaires s'exécutent en millisecondes, les tests d'intégration en secondes. Dans la pyramide des tests, les tests unitaires occupent 70%.

Quel framework choisir pour les tests unitaires ?

Le choix dépend de la plateforme : JUnit 5 pour Java/Kotlin, XCTest pour iOS/Swift, pytest pour Python, Jest/Vitest pour JavaScript/TypeScript, flutter_test pour Flutter. Pour le mocking, utilisez Mockito (Java), Cuckoo (iOS), unittest.mock (Python) ou vitest.mock (JS). Tous les frameworks modernes prennent en charge les tests paramétrés, les assertions intégrées et l'exécution parallèle.

Quels sont les principes F.I.R.S.T. de test ?

Fast — le test s'exécute en millisecondes. Isolated — il ne dépend pas d'autres tests ou systèmes externes. Repeatable — donne le même résultat sur n'importe quelle machine. Self-validating — vérifie automatiquement le résultat. Timely — écrit avant ou en même temps que le code. La violation d'un seul principe réduit l'efficacité des tests.

Faut-il écrire des tests unitaires pour ViewModel sur Android/iOS ?

Oui, absolument. Le ViewModel contient la logique métier — gestion des événements, transformation des données, gestion d'état. Sur Android, utilisez kotlinx-coroutines-test pour les coroutines et Turbine pour tester StateFlow. Sur iOS, testez les Combine Publishers ou async/await dans le ViewModel. Les tests ViewModel sont de purs tests unitaires qui s'exécutent sur JVM/macOS sans émulateur.

Comment tester du code avec des requêtes réseau ?

Les requêtes réseau dans les tests unitaires ne sont pas exécutées — elles sont remplacées par des mocks du client HTTP. Sur Android, utilisez MockWebServer (OkHttp) — il lance un serveur HTTP local, préférable aux mocks car il reproduit l'interaction réseau réelle. MockWebServer offre l'isolation sans perdre le réalisme. Pour iOS — OHHTTPStubs ou URLProtocol pour intercepter et remplacer les réponses.

Résumé

  • Tests unitaires — vérification de modules individuels isolés des dépendances externes avec retour rapide
  • Structure AAA — Arrange (préparation), Act (action), Assert (vérification) — modèle de test standard
  • Mocks et stubs — doubles de test pour isolation : les mocks vérifient les appels, les stubs retournent des valeurs
  • TDD — développement piloté par les tests (Rouge-Vert-Refactor) réduit les défauts de 40 à 80%
  • Principes FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — base des tests de qualité
  • Outils par plateforme — JUnit 5 (Android), XCTest (iOS), flutter_test (Flutter), Jest (React Native)
  • Couverture 70-80% — niveau optimal pour la logique métier critique, les getters et setters ne nécessitent pas de tests

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi