’article d’introduction débute en listant certaines différences de visions que je peux avoir avec d'autres développeurs concernant l'architecture applicative ou encore la rédaction des tests. À travers elles, j’évoque les difficultés qu’ils peuvent rencontrer à identifier précisément quoi tester et comment.
Deux phrases extraites de l’article de Ian Cooper ont été mises en avant :
Ces deux axes sont à mon sens essentiels. Nous allons essayer de les décortiquer un par un, à travers des exemples, pour en comprendre leur essence et en quoi ils sont si importants pour faciliter le quotidien de l’équipe de développement.
Ici, nous débuterons par le second axe : “Je vous recommande d’utiliser ports/adaptateurs et d’écrire les tests en outside-in depuis le use case” en illustrant ce qu’il implique dans l’écriture des tests.
Dans cet article : Un test peut en cacher un autre - Tests unitaires - P1, nous nous attardons davantage sur le premier axe : “Le code issu d’un refactoring ne requiert pas de nouveaux tests”. Je vous recommande de lire ce dernier avant car nous allons nous appuyer dessus.
On va travailler avec un Kata qui sera la gestion d’un système de réservation de livres dans une librairie, dont les règles sont les suivantes :
NDR : le code est en TypeScript, qui propose un typage statique fort. Néanmoins, le code peut être transposé dans des langages comme JavaScript, Python ou PHP qui ont nativement un typage dynamique et faible pour certains.
Prenons pour exemple les fonctionnalités suivantes :
Ici, nous avons le mot clef “sauvegardé” qui met en avant un choix d’architecture : sauvegarder la donnée quelque part, base de données, fichier, etc. Hors dans ce type de tests, devrions-nous connaître ces choix et en être impacté ? À priori, non.
Ces fonctionnalités n’ont pas besoin de savoir comment la donnée est enregistrée, juste que c’est possible. Dans les TU, on doit uniquement se concentrer sur les règles métiers sans être affecté par des “détails d’implémentation” !
C’est là que le principe d’inversion des dépendances (DIP) vient nous aider pour répondre à cette problématique.
Modifions les tests pour prendre en compte cette notion de sauvegarde du panier :
https://gist.github.com/mickaelw/07fa70b57b8df2a7d232be7bd7bd9056
Les TU gardent les mêmes intitulés que précédemment, uniquement l'implémentation à changée.
Apparition de trois classes :
AddToBasket et GetBasketInformation :
Ces deux classes sont des Use Cases, l’une pour ajouter un livre dans le panier et l’autre pour récupérer les informations du panier.
Pour chacune de ces classes, nous devons injecter une implémentation d’un BasketRepository afin qu’elles puissent fonctionner correctement pour sauvegarder et récupérer les données nécessaires. Les dépendances sont biens inversées.
https://gist.github.com/mickaelw/f0b64379c811ba65b75901930e7e9c10
https://gist.github.com/mickaelw/e583d291b7467527d32193c2c244e638
Là, nous écrivons les tests en outside-in depuis le use case.
InMemoryBasketRepository :
https://gist.github.com/mickaelw/a6dd68497c19ab63fb58f920e8c7941a
Cette classe est une implémentation possible du contrat défini par l’interface BasketRepository :
https://gist.github.com/mickaelw/0e3ecc4e3f0ce5310fdf83388472e2fd
C’est la première notion évoquée plus haut : Je vous recommande d’utiliser ports/adaptateurs :
NDR : Dans les faits, InMemoryBasketRepository est une doublure de test manuel. Il est donc possible de le remplacer par une doublure générée par une librairie de tests.
Pour ne pas s’embrouiller entre les tâches, on évite d’en faire plusieurs en même temps, pour nos tests c’est pareil.
Les TU doivent nous permettre de se concentrer sur les règles métiers pour éviter au maximum de faire des erreurs (qui pourraient en plus coûter de l’argent à l’entreprise).
Nous utilisons le principe d’inversion des dépendances (DIP), ce qui nous aide à tester efficacement nos fonctionnalités sans se préoccuper d’un détail d’implémentation (ici, comment seront stockées les données).
Un TU a en point d’entrée un Use Case à qui on va masquer des problématiques qui vont être du ressort des Adaptateurs, afin de se concentrer sur ce qui est présent dans le Core****.
L'objectif est de tester que les règles métier s’orchestrent bien en isolation de tous choix d'implémentation technique.
N’utilisant pas l’implémentation réelle, les tests deviennent :
Résumé en une image :