Qualité de développement

BUT1-R2.03 - année 2022/23

IUT Aix-Marseille logo

Qui suis-je ?

Sébastien HOUZÉ

Logo Twitter Logo Github Logo LinkedIn

20 ans d'XP, de développeur à CTO en passant par responsable qualité logicielle.

Logo Rue du commerce Logo VeryLastRoom Logo Onatera Logo Gogaille

Qui êtes-vous ?

⏳ 5 min chit-chat

Vous vous reconnaissez...

dans l'une de ces situations ?

🤯 À chaque fois que vous écrivez du code - pour le tester - vous passez votre temps à le sauvegarder et à l'exécuter manuellement. C'est fastidieux.
🤕 Au bout d'un moment vous ne vous souvenez plus de la liste des points à vérifier pour valider votre code.
🤭 Vous testez souvent quelques cas particuliers, mais vous n'êtes pas sûr de couvrir tous les cas.
😖 Vous vous retrouvrez avec des nouveaux bugs quand vous pensez avoir corrigé tous les bugs.
🤔 Rien ne documente comment votre code fonctionne (à part au mieux des specs non branchées sur le code).

À la fin de ce module :

  1. Le concept de test unitaire
  2. Sa mise en pratique
  3. La couverture de code
  4. La gestion de contexte
  5. Les mocks
  6. Et les problèmes liés aux tests

N'aurons plus de secrets pour vous !

À la fin de ce module :

Vous maîtriserez les tests unitaires dans plusieurs langages de programmation ainsi que leur automatisation...

Et vous n'écrirez plus jamais d'applications

sans tests unitaires !

Tests Unitaires

Définitions

Définition Simple

Un test unitaire ou T.U. (U.T. en anglais) est un test qui vérifie le bon fonctionnement d'une unité de code.

Définition par cas de test

Un test unitaire doit couvrir un cas de test d'une portion de code.

Un cas de test

est composé de :

  1. Un état (ou contexte) de départ ou pré-condition.
  2. Un état (ou contexte) d'arrivée ou post-condition.
  3. Un oracle : outil permettant de prédire l'état d'arrivée en fonction de l'état de départ, et de comparer le résultat théorique et le résultat pratique (l'exécution du cas de test).

Modèle mental

Given

When

Then

Given

Étant donné que...

L'état de départ ou pré-condition.

On verra que l'on peut assi réaliser des prophéties dans des cas plus avancés, mais pour l'instant, on va se contenter de ça.

When

Quand

L'action testée, la fameuse portion de code sous test (qu'on appelle SUT ou System Under Test).

Exemple : on souhaite tester une fonction d'addition de 2 entiers add(1, 2)

Then

L'état d'arrivée ou post-condition.

On dit qu'on réalise des assertions.

Pour Aller plus loin

Vocabulaire que l'on définira + tard

Effet de bord / Side effect.

Cas limite / Edge case.

Isolation.

Boîte Blanche, Grise, Noire.

Tests Unitaires

Utilité

Tests Unitaires

Utilité

  1. Trouver des erreurs rapidement
  2. Sécuriser la maintenance
  3. Documenter le code

Trouver des erreurs rapidement

Ou même mieux... guider la conception par les tests (TDD ou Test Driven Development).

🤫 disclaimer: on verra pendant le TP qu'il est plus difficile de mettre en place des tests sur du code écrit que d'écrire les tests puis de coder.

Sécuriser la maintenance

Lorsqu'on modifie un programme, les tests permettent de détecter les éventuelles régressions.

Documenter le code

Il est très utile de lire les tests pour comprendre comment fonctionne le code.

Des tests bien écrits couvrent et illustrent les cas d'usage.

Utilité

Ce qu'il faut retenir

Les T.U. dès le départ c'est la victoire 🎉

TDD c'est plus que les tests, c'est un outil de conception 👨‍🎨.

Tests Unitaires

Mise en pratique

TP1

Mise en pratique

  • Javascript (NodeJS)
  • Typescript (Deno)
  • Php

I need you

Merci de me communiquer votre nom complet handle github pour que je vous invite sur le dépôt du TP.

TP1

Ce qu'il faut retenir

TP1 - Ce qu'il faut retenir : exercices 1 à 3

Se familiariser concrètement avec les outils, sur un test bidon.

  1. Je sais lire la doc : mettre en place (ou corriger la mise en place) des TU dans 3 langages.
  2. Je sais lancer des tests : faire tourner et échouer des TU.
  3. Je développe mon esprit critique : apprécier les différences d'implémentation selon les langages/technos.

TP1 - Ce qu'il faut retenir : exercice 4

Découverte du TDD sur des cas de tests déjà écrits.

  1. Je sais concevoir/implémenter en me servant des TU comme guide.
  2. Je découvre la liberté et la sécurité de remanier (refactoring) mon implémentation grâce aux tests.
  3. J'ai un regard critique sur les TU proposés : dernier cas de test inutile car pas de participants, pas de podium!

TP1 - Ce qu'il faut retenir : exercice 5

Je me sers des TU pour corriger un bug.

Sur un algorithme totalement inconnu, y compris une fois que j'ai corrigé ce bug.

Il a suffit de :

  1. Identifier en lançant les tests lequel des 2 algorithmes avait un bug (ROT13).
  2. Chercher une implémentation sur internet et la substituer à celle qui était écrite pour faire passer la suite de tests.

TP1 - Ce qu'il faut retenir : exercice 6

Je sais faire évoluer une fonctionnalité.

Elle était converte par les tests, mais il fallait désormais que l'application aie le comportement contraire de celui implémenté et testé.

Tests Unitaires

Couverture de code

Couverture de code

Les outils de test unitaire peuvent générer des rapports.

Ceux qui expliquent quelles portions de code sont couvertes ou non par les tests s'appellent la couverture de code ou code coverage.

Couverture de code

Exemple concret en Java

TP2 - Exercice 1 : en Java 11 Junit 5 avec le plugin JaCoCo

Couverture de code

100% de couverture n'est pas un graal, parce que :

  • Vous pouvez avoir des tests qui ne testent rien ou des choses inutiles.
  • Avec l'expérience, vous apprendrez et maîtriserez la pyramide des tests : les types de test doivent se compléter. Il faut parfois laisser les tests d'intégration couvrir une partie non couverte en unitaire, avec discernement.

Tests Unitaires

Gestion du contexte

Gestion du contexte

Parfois vous avez du code répétitif dans vos suites de test

Les outils de test unitaire fournissent des "hooks" permettant de s'accrocher au cycle de vie d'une suite de test ET d'un cas de test.

Gestion du contexte

Hooks

Hook Exécution Utilisation
BeforeAll Une seule fois, avant le premier test Initialisation du contexte
BeforeEach Au début de chaque test Initialisation du contexte
AfterEach A la fin de chaque test Nettoyage du contexte
AfterAll Une seule fois, après le dernier test Nettoyage du contexte

Gestion du contexte

Exemple concret en Java

TP2 - Exercice 2 : en Java 11 Junit 5

Gestion du contexte

Ce qu'il faut retenir

Il existe avec junit tout un tas d'autres annotations autour du contexte (@Nested, @Disabled, ...).

👉 Les seuls éléments de contexte véritablement universels entre les moteurs de tests unitaires de tous les langages sont ceux que nous avons vu. Les autres sont spécifiques et relèvent de votre montée en expertise sur un moteur donné.

Tests Unitaires

Les mocks

Les mocks

Vous avons vu comment tester des classes simples.

Maintenant, imaginez devoir tester une classe A qui a besoin pour exister d'être instanciée avec une classe B.

Les mocks

instanceOfA = new A(B)

👉 Vous avez déjà testé la classe B à part.

Si vous utilisez une implémentation concrète de B dans A, lorsque vous changerez B les tests de A casseront très souvent pour rien 🤯.

Les mocks

instanceOfA = new A(B)

💡 Vous pouvez "mocker" B dans A.

Afin de tester A, vous allez créer une classe MockB qui implémente (ce dont vous avez besoin uniquement) de l'interface de B.

Les mocks

Exemple concret en Java

TP2 - Exercice 3 : en Java 11 Junit 5

Les mocks

Ce qu'il faut retenir

Un mock permet de simuler le comportement d'une dépendance :

  • Afin de garantir l'isolation des pièces de code testées (on ne teste pas en double)
  • En poussant à la mise en place de bonnes pratiques (ex: définition d'un contrat via une interface)

Tests Unitaires

Les problèmes des tests

Les problèmes des tests

  1. Il est facile d'écrire un test "en bois".
  2. Un test reste du code et donc peut lui aussi avoir des bugs.
  3. Couvrir les 100% des cas noie le sens de ce que vous testez.
  4. Parfois avec des dépendances on a besoin de savoir si et comment elles sont appelées.
  5. Des entrées statiques sur des cas de test ne sont pas toujours satisfaisantes pour couvrir le cas.
  6. Mal nommer nos cas/méthodes de test rend nos tests incompréhensibles.

Les problèmes des tests

Solutions

  1. Pour éviter le test "en bois", penser given/when/then, ça doit raconter une histoie et être très lisible.
  2. Pour éviter les bugs dans le test, effectuer du pair programming et de la revue de code avec un pair.
  3. Couvrir uniquement les cas qui ont un sens, non au code défensif et aux cas de tests hors du périmètre fonctionel.
  4. Les tests en boîte blanche permettent de prophétiser notamment sur les dépendances (si appelées, combien de fois, ...).
  5. Il existe des libs fakers dans à peu près tous les langages afin de mettre de l'aléatoire dans vos fixtures (jeux de données).
  6. TDD favorisera toujours de bien nommer ses cas de test vu que c'est la transcription de l'énnoncé.

Les problèmes des tests

Ce qu'il faut retenir

Les tests unitaires et plus particulièrement le TDD nous guident dans la conception d'un code propre qui correspond au juste besoin en nous poussant très tôt à valider ce besoin auprès des interlocuteurs métier.

Les problèmes des tests

Ce qu'il faut retenir

Tester c'est donner du sens lorsqu'on conçoit.

Les problèmes des tests

Ce qu'il faut retenir

Tester c'est avoir confiance lorsqu'on déploie.

Les problèmes des tests

Ce qu'il faut retenir

Tester c'est se donner la liberté de remanier notre code avec des garanties.