• 10 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 9/20/24

Choisissez les bons tests automatisés avec la pyramide de tests

Tout le monde peut faire des erreurs, surtout lorsque l'on est contraint par les échéances de livraison. Prendre le temps de vérifier ce que l'on a produit, en particulier en développement, permet de garantir la livraison d'un produit de qualité. Et cela peut aussi nous éviter quelques moments gênants devant le client lors d'une démonstration !

À ce sujet, un des exemples les plus frappants est sans aucun doute le vol inaugural du lanceur européen Ariane 5. 10 ans de recherches et de développement se soldant par un échec cuisant au décollage... Le tout à cause d'un système de guidage hérité d'Ariane 4, et non testé dans des simulations adaptées aux scénarios d'Ariane 5. Ces simulations auraient pu éviter la perte de plusieurs centaines de millions de dollars.

Je comprends ce que tester veut dire de manière générale, mais qu'est-ce que cela signifie quand on travaille avec du code ?

En quoi consistent les tests ?

En développement, les tests visent à vérifier que le produit codé fonctionne comme prévu selon des scénarios prédéfinis et représentatifs. Cela permet de garantir la qualité de ce qui est codé, malgré les contraintes du projet, comme les délais, par exemple.

Tests manuels ou tests automatisés ?

La manière la plus directe de faire des tests, c'est d'effectuer des tests manuels par des humains. En informatique, de nombreuses équipes ont employé des testeurs, qui prennent la responsabilité de la qualité d'une application. Ils vérifient les logiciels durant une phase de projet nommée recette, ou même après, lorsque le logiciel est en activité, en production.

Ça m'a l'air pas mal. Ça ne suffit pas ?

Quand vous codez de plus en plus de fonctionnalités, tester manuellement l'exécution d'un produit et de toutes ses fonctionnalités est une activité répétitive et peu créative. De plus, coder une fonctionnalité peut avoir un impact à un autre endroit du logiciel. Donc, si l'on est rigoureux, il faudrait retester tout le produit à chaque développement !

À l'heure où de nombreuses équipes sortent de nouvelles versions de leur logiciel plusieurs fois par jours, l'alternative est de développer des programmes spécifiques qui vérifient le code de nombreuses façons différentes. C'est ce que l'on appelle des tests automatisés. Comme il s'agit de code, l'exécution de ces tests est rapide et peut être répétée à de multiples reprises pour un coût très faible. Vous avez le choix entre de nombreux types de tests automatisés, chacun avec ses avantages et ses inconvénients.

Découvrez les différents types de tests automatisés

Les 3 types de tests automatisés les plus courants sont les tests unitaires, d'intégration et fonctionnels. Mike Cohn, l'un des fondateurs du mouvement Agile, présente ces types de tests sous forme de pyramide des tests, en particulier dans le livre Succeeding with Agile (en anglais).

En utilisant cette métaphore, l'auteur préconise la répartition de ces types de tests dans un projet de développement agile selon la pyramide ci-dessous.

Image illustrant la pyramide des tests
La pyramide des tests

Cette illustration indique qu'il y a davantage de tests rapides et autonomes à la base de la pyramide, puis de moins en moins à mesure que l'on grimpe vers le sommet. Plus on approche du sommet, plus les tests sont longs et complexes à exécuter, tout en simulant des scénarios de plus en plus réalistes. Définissons chacun de ces tests !

Tests unitaires

La plus grosse section, à la base de la pyramide, est constituée des tests unitaires qui testent de "petites" unités de code. Plus précisément, ils testent que chaque fonctionnalité extraite de manière isolée se comporte comme attendu.

Ils sont très rapides et faciles à exécuter. Si vous avez cassé quelque chose, vous pouvez le découvrir vite et tôt. De bons tests unitaires sont stables, c'est-à-dire que le code de ces tests n'a pas besoin d'être modifié, même si le code de l'application change pour des raisons purement techniques. Ils deviennent donc rentables, car ils ont été écrits une seule fois, mais exécutés de nombreuses fois.

Cependant, seules des unités individuelles de code sont testées, vous avez donc besoin d'autres tests permettant de s'assurer que ces unités de code fonctionnent entre elles.

Tests d'intégration

Au milieu de la pyramide se trouvent les tests d'intégration. Ils vérifient si vos unités de code fonctionnent ensemble comme prévu – en présupposant que vos tests unitaires soient passés ! Comme les tests d'intégration vérifient les interactions entre les unités, vous avez plus de certitude concernant le bon fonctionnement de l'application finale.

Ils peuvent nécessiter l'exécution de composants extérieurs (base de données, services web externe, etc.). Le lancement de ces composants et l'interaction entre vos unités de code développées rendent ces types de test plus lents  et potentiellement moins stables. Mais vous simulez des scénarios plus proches de l'utilisation finale de l'application.

Tests fonctionnels

Enfin, au sommet de la pyramide, les tests fonctionnels (appelés end-to-end en anglais), visent à simuler le comportement d'un utilisateur final sur l'application, depuis l'interface utilisateur. L'ensemble du code développé est pris comme une boîte noire à tester, sans connaissance des briques et des unités de code qui la composent. Les simulations obtenues sont donc les plus proches de l'application utilisée dans des conditions réelles.

Ces tests nécessitent toute l'infrastructure nécessaire à l'application. Ces types de tests sont les plus lents à exécuter, et testent une partie beaucoup plus grande de votre code développé.

Cela crée une plus forte dépendance qui rend vos tests moins stables, donc moins rentables. Potentiellement, une modification simple de l'interface utilisateur (la couleur d'un bouton) pourrait nécessiter de recoder le test fonctionnel associé.

Pourquoi cette répartition des tests est-elle préconisée ? La simulation au plus près de la réalité n'est-elle pas plus importante que la rapidité et la rentabilité ?

Oui, bien sûr, la simulation de scénarios "réalistes" est très importante. Mais écrire et exécuter des tests est un investissement qui a un coût. L'automatisation des tests n'a de réelle valeur ajoutée que si l'on arrive à exécuter ces tests sans les modifier de nombreuses fois, et la pratique montre qu'il est plus facile de rentabiliser un test unitaire qu'un test fonctionnel.

De plus, plus un bug est détecté tôt, moins son coût de correction est élevé. Or, pour qu'une nouvelle unité de code développée puisse être couverte par un test fonctionnel, il faut développer le code de l'interface utilisateur associée ainsi que le code d'accès aux données nécessaires. Si l'unité de code présente des bugs, sa détection sera alors beaucoup plus tardive, et son coût beaucoup plus élevé.

Enfin, comme nous allons le voir dans les prochains chapitres, les bonnes pratiques visant à coder beaucoup de tests unitaires (en particulier le développement dirigé par les tests, ou TDD) poussent le développeur à coder des unités de code plus robustes et autonomes. Ces pratiques favorisent aussi une détection très tôt des bugs, ces derniers ont donc moins d'impact en termes de coût sur le projet.

Les mauvaises pratiques dans la réalité des projets

Malheureusement, ces préconisations agiles, datant de plus de dix ans, ne sont pas toujours suivies. En effet, de nombreuses équipes de développement, soumises à des contraintes fortes de délais et de coûts, ont développé de mauvaises pratiques de tests.

La plus connue est sûrement la mauvaise pratique (ou anti-pattern en anglais) du cône de glace de tests, illustré ci-dessous. Elle vient souvent du fait qu'un projet ait été développé sans tests unitaires au début. Puis, pour rattraper la situation, les développeurs codent des tests fonctionnels et d'intégration, qui couvrent le plus de code possible en peu de temps de développement.

Anti-pattern du cône de glace de tests
Anti-pattern du cône de glace de tests

Regardez bien, le cône de glace de tests, c'est la pyramide de tests inversée ! Avec une telle répartition, le produit développé est beaucoup moins maintenable (il est plus difficile à faire évoluer dans le temps par les développeurs), les tests deviennent obsolètes très vite et on retourne à des pratiques de tests manuelles coûteuses et peu efficaces (la crème glacée sur le schéma).

Découvrez d'autres finalités pour les tests

Dans votre produit numérique, savoir que des tests existent et en parsemer quelques-uns dans votre code ne suffit pas à avoir confiance en son code. Revenons à l'exemple de la fusée Ariane. Ils avaient exécuté des tests pendant des années ! Mais personne n'avait pensé à tester ce problème particulier.

Testez pour faire face à l'inattendu

Lorsque vous codez une fonctionnalité pour implémenter une user story ou résoudre un problème, il est facile d'imaginer un bon scénario : celui où le logiciel est utilisé exactement de la façon attendue, sans aucune difficulté. Par exemple, un utilisateur pourrait cliquer dans votre application dans le bon ordre, ne rencontrer aucun problème et tout se terminerait par le résultat attendu.

Cependant, la réalité correspond rarement à nos attentes. Il y aura toujours des scénarios alternatifs, des comportements utilisateurs inattendus, des problèmes réseau, un service web externe non disponible. Il y aura souvent plus de mauvais chemins que ce que vous pouvez envisager.

Mais comment m'assurer que mon produit fonctionnera, si je ne peux pas penser à tous les scénarios possibles ?

Voici plusieurs pistes de solution :

  • le risque zéro n'existe pas. Réduisez le risque en combinant les scénarios attendus, les cas limites courants, et les risques de non-disponibilité des services extérieurs dont vous dépendez ;

  • plus vos tests seront unitaires, moins il y aura de combinaisons de scénarios alternatifs. Ceci doit vous conforter dans le respect de la pyramide des tests vue précédemment ;

  • collaborez avec les membres de votre équipe, les responsables de la valeur de votre produit numérique (product owner), les autres développeurs et les testeurs.

Testez pour faciliter la maintenance

Plus vous codez de tests et plus vous vous assurez qu'ils restent à jour, plus la confiance en votre produit va grandir. Ces tests vont grandement faciliter la correction et la maintenance du code. En particulier, si un bug bloquant a lieu après la mise en production de votre produit, disposer de tests existants permet de vérifier très rapidement si les corrections apportées cassent quelque chose dans le code existant. Ils testent la non-régression de votre produit. La mise en production de la correction s'effectue en confiance et cette réactivité  donne aussi confiance au client.

Testez pour communiquer

Oui, les tests peuvent vous aider à communiquer ! Quand vous êtes en plein développement, vous avez une compréhension profonde du code, de ce qu'il est censé accomplir, et de comment vous le vérifierez. Un peu comme dans la lecture d'un livre – vous connaissez le contexte et l'importance de chaque interaction dans l'histoire.

Mais si vous preniez une page au hasard et que vous la donniez à quelqu'un d'autre, il n'aurait aucune idée de ce qu'il se passe. C'est la même chose avec le code. Imaginez que vous changiez subitement de projet, et qu'un autre développeur doive finir votre code. Comment saurait-il par où commencer ?

Les cas de tests sont là pour ça : ils décriront le comportement attendu du logiciel. La lecture de vos tests doit permettre de comprendre non seulement ce que votre code est censé faire, mais aussi comment il fonctionne et à quel point il est opérationnel. Si vous écrivez des tests, un autre développeur – ou vous-même, si vous devez reprendre un projet plusieurs mois après – sera capable de comprendre ce que vous essayiez d'accomplir, et aura toute confiance pour modifier des éléments du code que vous aviez écrit.

Le test va au-delà d'une simple manière de corriger le code, c'est un véritable état d'esprit agile

Dans le chapitre suivant, vous allez apprendre comment intégrer cet état d'esprit dans votre façon de coder.

En résumé

  • L'automatisation des tests réduit le besoin de vérifications manuelles et, quand elle est bien faite, peut vous donner la confiance nécessaire pour automatiser complètement la sortie de votre produit numérique.

  • La pyramide de tests vous donne un modèle pour vous aider à écrire des tests qui vous donnent confiance en votre code, à mesure que vous y apportez des changements.

  • Les tests unitaires vérifient les classes, ou autres unités de code, rapidement et de façon exhaustive, pour s'assurer qu'elles tiennent leurs promesses.

  • Les tests d'intégration vérifient que les classes et les parties de votre application qui doivent fonctionner ensemble le font en collaborant comme prévu.

  • Les tests fonctionnels vérifient du point de vue de l'utilisateur final qu'on sera capable de résoudre ses problèmes en utilisant votre application lorsqu'elle est active. Pour une application web, cela passe souvent par une simulation d'interactions sur le navigateur.

  • Au-delà de la vérification, les tests permettent, entre autres, de mieux faire face à l'inattendu, d'améliorer la réactivité de la maintenance, et de mieux communiquer entre développeurs.

Nous étudierons chacun de ces points plus en détail dans les chapitres suivants. Maintenant, vous êtes prêt à vous lancer avec votre premier test unitaire dans le prochain chapitre ! 

Example of certificate of achievement
Example of certificate of achievement