Le Test-Driven Development (TDD) est une méthodologie de développement dans laquelle les tests sont écrits avant l'implémentation du code. Le développeur formule d'abord le comportement attendu sous la forme d'un test échouant, puis écrit le code minimum pour le faire passer et enfin refactorise le résultat. Selon Martin Fowler (2023), TDD n'est pas une technique de test — c'est une technique de conception qui discipline l'architecture et réduit le nombre de défauts au stade de l'écriture du code.
Points clés
Le Test-Driven Development est une pratique de développement logiciel où les tests automatisés déterminent l'écriture du code de production. Contrairement à l'approche traditionnelle où le code est écrit puis testé, le TDD inverse la séquence : d'abord le test est écrit, ensuite le code qui le fait passer.
Le fondateur du TDD est Kent Beck, qui a formulé cette pratique à la fin des années 1990 dans le cadre de la méthodologie Extreme Programming (XP). Dans le livre « Test-Driven Development : By Example » (2002), Beck a décrit cinq règles du TDD devenues canoniques : écris le test avant le code de production, écris exactement le code nécessaire pour faire passer le test et refactorise après chaque cycle.
Le premier principe — le test définit l'interface. Le développeur est obligé de penser à la manière dont le composant sera utilisé avant de penser à la manière dont il est implémenté. Cela forme une API propre dès le départ.
Le deuxième principe — l'implémentation minimale. Une fois le test écrit, le développeur écrit exactement le code de production nécessaire pour le faire passer — pas une ligne de plus. Cela évite l'abstraction prématurée et la complexité excessive, que Martin Fowler appelle Speculative Generality.
La différence clé entre le TDD et les tests « a posteriori » — la discipline de séquence. Dans le TDD, le test ne se contente pas de vérifier le code — il guide sa structure. Selon une étude de Microsoft Research (Nagappan et al., 2008), les équipes appliquant le TDD démontrent une réduction de 40 à 90% de la densité de défauts par rapport aux équipes utilisant l'approche traditionnelle.
Le cycle Red-Green-Refactor est une séquence en trois étapes répétée pour chaque nouveau test. Red : écrire un test qui ne passe pas. Green : écrire le code minimum pour faire passer le test. Refactor : améliorer le code sans changer son comportement.
Le développeur écrit un test qui vérifie une fonctionnalité pas encore implémentée. À ce stade, le test doit échouer — cela confirme que le test vérifie réellement quelque chose. Dans l'environnement de développement Android, le framework JUnit 5 affiche un indicateur rouge pour les tests échoués, ce qui a donné son nom à la phase.
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
À ce stade, le code de production minimal suffisant pour faire passer le test est écrit. Aucune redondance — seulement ce qui est nécessaire pour l'indicateur vert. Si l'implémentation peut être une constante, qu'elle soit une constante. La refactorisation aura lieu à l'étape suivante lorsque de nouveaux tests apparaîtront.
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
Le test vert est une assurance pour la refactorisation. Le développeur peut réécrire l'implémentation, optimiser les performances ou améliorer la lisibilité, confiant que le test détectera immédiatement tout écart par rapport au comportement attendu. Dans le développement mobile Android, cette phase est particulièrement importante pour extraire des interfaces communes et réduire la duplication de code.
L'application du TDD dans les projets mobiles apporte des avantages mesurables, confirmés à la fois par la recherche académique et par la pratique des principaux studios de développement.
Une étude d'IBM (Bhat & Nagappan, 2006) sur quatre projets industriels a montré que les équipes utilisant le TDD produisent 40% de défauts en moins par rapport à des équipes similaires travaillant avec l'approche traditionnelle. Pour le développement mobile, où le coût de correction d'un bug après la publication sur Google Play est significativement plus élevé qu'au stade de l'écriture du code, cette métrique est critique.
Les tests écrits avec le TDD servent de documentation vivante de l'API. Un développeur rejoignant le projet peut lire les tests et comprendre comment chaque composant doit être utilisé. C'est particulièrement précieux dans des conditions de rotation élevée de l'équipe — un problème typique des studios mobiles.
Une couverture de code dépassant 90% permet aux développeurs de refactoriser sans crainte de casser quelque chose. Google, dans son livre « Software Engineering at Google » (2020), qualifie la couverture de tests de facteur clé permettant de maintenir la base de code propre sur des projets avec des millions de lignes de code.
L'écosystème TDD dans le développement mobile comprend des outils pour les tests unitaires, le mocking et la vérification des composants d'interface utilisateur — à la fois pour Android et iOS.
| Outil | Plateforme | Objectif |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | Framework de base pour les tests unitaires |
| Mockito | Android | Création d'objets mock et vérification d'appels |
| MockK | Android (Kotlin) | Mocking avec syntaxe Kotlin-first et support des coroutines |
| Turbine | Android | Test de Kotlin Flow et de flux réactifs |
| XCTest | iOS (Swift) | Framework de test standard |
Pour les projets Android en Kotlin, la stack standard inclut JUnit 5 + MockK. MockK est préférable à Mockito car il prend en charge les fonctionnalités de première classe de Kotlin — classes sealed, coroutines et fonctions suspend — sans configuration supplémentaire.
Dans le développement iOS, le TDD est implémenté via XCTest — le framework intégré d'Apple qui fournit des assertions, des classes de test et une intégration CI/CD via Xcode Server ou GitHub Actions. Pour le mocking sur iOS, les bibliothèques Cuckoo et OHHTTPStubs sont utilisées.
Considérons un scénario réel de TDD en Kotlin pour Android — le test d'un repository d'utilisateurs. D'abord nous écrivons le test, puis l'implémentation qui le fait passer.
class UserRepositoryTest {
private val api = mockk<UserApi>()
private val dao = mockk<UserDao>()
private val repo = UserRepository(api, dao)
fun `when api returns user then cache and emit`() = runTest {
val user = User(1, "Alice")
coEvery { api.getUser(1) } returns user
every { dao.insert(user) } returns Unit
val result = repo.getUser(1)
assertEquals(user, result)
verify { dao.insert(user) }
}
}
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
suspend fun getUser(id: Int): User {
val user = api.getUser(id)
dao.insert(user)
return user
}
}
Après avoir passé le premier test, nous en ajoutons un second — vérifiant le comportement lors d'une erreur réseau. Maintenant, le test détermine que lorsque l'API échoue, le repository doit renvoyer les données depuis le cache.
fun `when api fails then return cached user`() = runTest {
val cached = User(1, "Cached Alice")
coEvery { api.getUser(1) } throws IOException()
every { dao.getById(1) } returns cached
val result = repo.getUser(1)
assertEquals(cached, result)
}
La transition vers le TDD est accompagnée d'erreurs typiques qui peuvent annuler tous les avantages de la méthodologie. Comprendre ces pièges aide les équipes à adopter la pratique plus efficacement.
Le premier et le plus courant anti-patron — tester un trop grand volume de fonctionnalités dans un seul test. Un test doit vérifier exactement une assertion. Si un test échoue, le développeur doit savoir exactement ce qui s'est cassé sans débogage supplémentaire.
La deuxième erreur — écrire un test qui passe dès le départ. Si le test n'a jamais été rouge au moins une fois, il n'y a pas de certitude qu'il vérifie réellement quelque chose. Règle : ne fais jamais confiance à un test que tu n'as pas vu échouer.
La troisième erreur courante — s'arrêter à la phase verte. La refactorisation n'est pas facultative mais une étape obligatoire du cycle. Sans elle, la base de code se dégrade, les tests deviennent fragiles et les avantages du TDD se perdent.
Questions fréquentes
TDD est avant tout une technique de conception, pas une technique de test. Les tests dans le TDD jouent le rôle de spécification : ils définissent l'API du composant avant son implémentation. Kent Beck lui-même appelle le TDD « une discipline de conception, pas de test ».
Selon les études de Microsoft Research, les équipes ont besoin de 3 à 6 mois de pratique continue pour que le TDD devienne une habitude. Les 2 à 3 premières semaines, la productivité chute de 15 à 30%, mais après adaptation, elle revient au niveau initial ou le dépasse grâce à la réduction du temps de débogage.
Oui, mais avec des limites. Pour la logique d'interface (ViewModel, State), le TDD est directement applicable. Pour les composants visuels (Compose UI, SwiftUI Views), les tests de capture d'écran (snapshot testing) complètent le TDD mais ne le remplacent pas. Il est recommandé de séparer la logique métier de l'affichage.
Pour le code legacy, la stratégie recommandée est celle des « tests de caractérisation » — où les tests sont écrits sur le comportement existant, puis le code est refactorisé. Cette approche est décrite dans le livre de Michael Feathers « Working Effectively with Legacy Code » (2004) et permet d'introduire le TDD progressivement.
TDD et Clean Architecture se renforcent mutuellement. L'architecture propre exige des limites claires entre les couches, et le TDD oblige le développeur à concevoir ces limites à travers les tests. La couche domaine est testée isolément avec des dépendances mock, la couche données — via des tests d'intégration.
Résumé
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.
Lisez aussi