Nicolas Le Borgne

Développeur

TDD : Inside Out, Outside In, démêlons le sujet

Le 11 février 2021

Il y a deux ans, à force d'entendre parler de TDD, j'ai lu livre "Test Driven Development" de Kent Beck, puis j'ai adopté l'approche proposée par le livre, à savoir "Inside Out".

En continuant de faire de la veille, j'ai vu qu'il y avait un débat assez vif sur les deux approches et que certaines personnes étaient très dogmatiques sur le sujet. On trouve différents termes pour expliquer ces notions, avec parfois des associations, des oppositions ... J'ai vraiment eu des difficultés à comprendre cette seconde approche "Outside In".

Maintenant que c'est normalement réussi, je tente de le resituer ici.

Test Driven Development

La boucle du TDD

Schéma 1 : rappel du cycle TDD

Le cycle classique du TDD, que l'on retrouve à peu près partout :

  1. On écrit un premier test, non passant bien sûr.
  2. On écrit le strict minimum de code permettant de faire passer le test.
  3. On refacto.

Make it work. Make it right. Make it fast.

  • Kent Beck

On boucle, on boucle, c'est la triangulation, et on a couvert notre feature ! Les deux courants se basent bien entendu sur ce cycle.

La grande confusion

En veillant le sujet, j'ai noté les synonymes suivants (lire verticalement) :

Synonymes
Inside OutOutside In
ClassicalMockist
Chicago schoolLondon school

Notons qu'on en déduit déjà une partie de l'association suivante :

Associations
Outside InMockistBDD

Pour finir une opposition :

Oppositions
State based verificationBehaviour verification

Et donc là c'est le drame :

  • En Inside Out, on ne moque pas.
  • En Outside In, on moque tout le temps.
  • L'Outside In, c'est du BDD.
  • Une vérification d'état, c'est pas un comportement.

Et si on pousse plus loin, qu'est-ce qu'un comportement ?

Requirements are behaviour, too — Dan North

Si vous aussi vous êtes perdu, j'espère pouvoir vous éclaircir. Afin de ne pas empirer les choses, dans la suite de l'article, je ne parle pas de test de vérification de comportement mais de vérification d'interaction (mock) et par comportement, j'entends comportement métier.

Inside Out

Le cycle

La boucle du TDD

Schéma 2 : le cycle inside out détaillé

  1. On écrit un premier test, non passant bien sûr
  2. On écrit le strict minimum de code permettant de faire passer le test
  3. On refactor le code si besoin. C'est là que la conception se fait, on va peut-être extraire un collaborateur etc ...

Cette approche se concentre sur des tests de vérification d'état. Néanmoins, ce n'est pas pour autant que la doublure de test est interdite ! Elle est juste réservée pour les cas aux limites, pour lesquels on ne maitrise pas directement le comportement : un message broker, un client Http, une base de données ... L'usage des doublures est si minime, qu'il est souvent privilégié d'écrire la doublure soi-même, plutôt que de passer par une librairie, mais c'est aussi une question de goût 🙂.

Pourquoi vouloir une nouvelle approche ?

Lors de l'apprentissage du TDD, il est encouragé de pratiquer via des Katas. On retrouve les grands classiques comme le FizzBuzz ou la suite de Fibonacci. Certains développeurs n'arrivent pas à se projeter, à transposer l'exercice dans leur quotidien. La grande majorité d'entre nous travaille sur des "logiciels d'entreprises" très éloignés de l'implémentation d'un FizzBuzz. Un autre élément important est le point de départ. En fonction de votre niveau de pratique et du travail réalisé en amont du code, il peut être difficile de savoir par où commencer.

Mais ce n'est pas tout, la première approche possède quelques défauts :

  • La conception survient principalement lors de la phase de refactoring. Potentiellement un collaborateur peut donc être testé de manière indirecte. Et si on veut qu'il soit testé de manière directe, il va falloir multiplier les cas de tests.
  • Le fait de partir de l'intérieur peut pousser le développeur à s'emballer un peu et rajouter des morceaux qui ne sont pas forcément nécessaires pour réaliser le comportement demandé. On peut aller à l'encontre du "YAGNI".
  • Les tests de vérification d'état peuvent inciter à rajouter des assesseurs, brisant ainsi le principe "Tell, don't ask".
  • Les collaborateurs n'étant pas moqués, la portion de code testée n'est pas isolée du reste. Ainsi, un test qui échoue risque d'engendrer une cascade, rendant difficile le débogage.

À l'inverse, son immense atout est de ne pas coupler les tests aux implémentations.

En bref

AvantagesInconvénients
Peu de couplage aux implémentationsLe point de départ peut ne pas être évident à trouver
Semble peu adapté aux logiciels d'entreprises
Peut aller à l'encontre du "YAGNI"
Peut aller à l'encontre du "Tell don't ask"
Lorsqu'un test échoue, il peut entrainer ses copains en cascade

J'ai beau avoir une préférence sur l'approche classique, principalement lié à l'usage mesuré des mocks, réduisant le couplage avec les tests, je ne peux pas nier qu'on puisse lui trouver beaucoup d'inconvénients !

Outside In

Le cycle

La boucle du TDD

Schéma 3 : le cycle outside in détaillé

  1. On écrit un premier test, situé aux frontières du système. Peu importe le niveau de travail en amont de la réalisation, normalement les frontières du système (UI, contrat d'api ...) sont définies. Pour pouvoir écrire ce test, nous devons identifier les différents collaborateurs, et tester leurs interactions. La phase de conception se situe désormais en amont.
  2. On écrit le strict minimum de code permettant de faire passer le test.
  3. On refactor le code si besoin.

On se concentre sur la validation des interactions entre les collaborateurs, puis les collaborateurs en eux-mêmes. On considère que si le collaborateur est bien testé de son côté, alors nous n'avons plus qu'à tester qu'il est bien appelé pour valider notre système. Cela nous permet d'éviter les inconvénients de collaborateurs testés indirectement ou de multiplication des cas de tests. On évite également d'exposer des données pour vérifier un état, on reste dans le "Tell, don't ask". Pour finir, puisque l'on part de l'extérieur vers l'intérieur, pas de place à la dispersion, on évite de briser le "YAGNI".

La double boucle

Jusqu'à présent, notre premier test valide que notre point d'entrée interagit bien avec ses copains, pas que le comportement demandé par le métier c'est bien produit. On va alors commencer en tout premier lieu avec un test de vérification d'état aux frontières de notre système. C'est notre boucle externe. Ensuite on teste nos interactions etc ... (Voir Schéma 3), c'est notre boucle interne.

Mais c'est pas du BDD ?

Pas vraiment. En fait, même si Dan North nous décrit une approche similaire, la finalité reste la notion de "spécifications exécutables". Donc on tomberait dans le BDD si le test de notre boucle externe était écrit avec une syntaxe type "Gherkin", genre Behat.

En bref

AvantagesInconvénients
Point de départ identifié plus facilementFort couplage aux implémentations
Favorise le "Tell, don't ask"
Favorise le "YAGNI"
Lorsqu'un test échoue, il ne déclenche pas de cascade

Conclusion

Chaque solution vient toujours avec son lot d'avantages et d'inconvénients. Finalement, les deux approches sont intéressantes et ne répondent peut-être pas aux mêmes besoins. Jason Gorman souligne le fait que l'approche "Inside Out" semble plus adaptée aux algorithmes tandis que l'approche "Outside In" l'est probablement plus lorsque l'on a beaucoup d'interaction. Il faut également être vigilant car chacune des deux approches à un impact différent sur nos manières de penser, et le code produit sera très différent en fonction de celle choisie. Il est surement intéressant d'utiliser les deux approches au sein d'un même logiciel, en fonction des différents composants et des besoins qu'ils apportent.

Always remember that panaceas don't exist. Consider your own context — Vincent Driessen

Si je n'ai pas réussi à vous éclairer, je vous encourage à regarder le screencast de Sandro Mancuso dans les sources, il m'a beaucoup aidé 🙂.

Sources

© 2021 Nicolas Le Borgne