• 10 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 23/02/2024

Adoptez la méthodologie des tests unitaires

Comme je vous le disais dans la partie précédente, le test unitaire est souvent la première brique de test que vous allez mettre en place.

Découvrez les tests unitaires

Qu’est-ce qu’un test unitaire ?

Le test unitaire

Le test unitaire permet de tester une partie spécifique de votre programme. Vous vous souvenez peut-être de notre fonction  sayHello  dans la partie précédente. Le test que nous avions réalisé à ce moment était un test unitaire !

Un test unitaire doit être simple à écrire, à lire, et rapide à exécuter. Il doit aussi ne pas avoir d’effets de bord. Pour un paramètre donné, le résultat retourné doit toujours être le même.

Euhhhh, c’est quoi un effet de bord ?

Bonne question !

En informatique, on dit qu’une fonction est à effet de bord si elle modifie un état autre que sa valeur de retour (le fameux  return  ), ou si le résultat qu’elle retourne change en fonction de l’état de l’application.

Par exemple :

// J'appelle une première fois la fonction
randomFunction(2) // retourne la valeur 15
// Je l'appelle une deuxième fois avec le même paramètre
// mais le résultat est différent. Il y a un effet de bord
randomFunction(2) // retourne la valeur 25

 En anglais, on appelle ça un “Side Effect” (pour effet secondaire), et je trouve ça beaucoup plus parlant.

À cause de cet effet secondaire ou de bord, une fonction peut vous retourner un résultat différent. Comme par exemple, retourner la valeur 25, au lieu de 15, dans notre exemple précédent.

OK. Et existe-t-il des bonnes pratiques pour les tests ?

Oui !

Il existe une règle d’or quand on écrit un test unitaire : si vous ne pouvez pas réaliser votre test facilement, c’est que votre code est trop compliqué et doit être simplifié.

Il arrive régulièrement des cas où les développeurs préfèrent surcharger une fonction existante, quitte à la rendre difficilement lisible et maintenable. Cela peut être dû à des deadlines un peu serrées, une mauvaise compréhension du code, ou simplement à un manque d’attention. Dans le code ci-dessous, j’ai écrit une fonction qui pourrait justement être un peu longue à tester. :)

const findLargestInArray = data => {
// On regarde si le tableau est composé uniquement de chiffres
if (!data.some(isNaN)) {
    // Si oui, alors on récupère le nombre le plus grand
    let largestNumber = 0;
    for (let i = 0; i < data.length; i++) {
        if (largestNumber < data[i]) {
        largestNumber = data[i]
        }
    }

// On retourne le nombre le plus grand
return largestNumber
} else {
    // Sinon, ça veut dire qu'on a un tableau de mots
    // On cherche le plus grand

let largestWord = ""
    for (let i = 0; i < data.length; i++) {
    if (largestWord.length < data[i].length) {
    largestWord = data[i]
    }
}

// On retourne le mot le plus grand
    return largestWord
    }
}

Voici un exemple de mauvaise fonction :

  • On traite trop de cas de figure différents.

  • On est obligé d’ajouter des commentaires pour préciser ce que l’on fait.

  • C’est typiquement une fonction qui fait trop de choses, et qui va être compliquée à tester. Elle devrait donc être simplifiée. :)

Voici ci-dessous, un exemple de code simplifié.

const isArrayOfNumbers = data => data.some(isNaN)

const findLargestNumberInArray = data => {
    let largestNumber = 0;
    for (let i = 0; i < data.length; i++) {
        if (largestNumber < data[i]) {
        largestNumber = data[i]
        }
    }

 return largestNumber
}

const findLargestWordInArray = data => {
    let largestWord = ""
    for (let i = 0; i < data.length; i++) {
        if (largestWord.length < data[i].length) {
        largestWord = data[i]

        }
    }

return largestWord
}

const findLargestInArray = data => {
    if (isArrayOfNumbers(data)) {
        return findLargestNumberInArray(data)
    }


return findLargestWordInArray(data)
}

Vous pouvez voir qu’écrire des tests a souvent deux avantages :

  • Augmenter la qualité de votre programme ;

  • Décomplexifier des parties de votre code. 

D’ailleurs, plus vous allez avoir de fonctions “simples”, plus votre code coverage sera pertinent !

Mettez en place une méthodologie pour tester vos applications

Vous savez à présent ce qu’est un test unitaire, et à quel moment l'ajouter ; il est maintenant temps de nous intéresser à la méthodologie. En effet, la question la plus fréquente qui revient quand je présente les tests à des étudiants est : OK, j’ai compris à quoi ça sert, maintenant je commence par quoi ?

Décomplexifiez votre code grâce aux tests

Avant de vous lancer la tête la première dans l’écriture de votre code, et afin d'éviter de partir dans la mauvaise direction, vous devez prendre du recul sur votre base de code. Il est important que vous preniez le temps de le comprendre.  D’autant plus si le code n’a pas été écrit par vous.

Une fois que c’est fait, vous pouvez commencer par écrire les tests aux endroits les plus critiques et les plus complexes de votre application. Cette méthode a deux avantages :

  • D’une part, plus le code est complexe ou la fonctionnalité critique, plus vous allez être en mesure d’éviter que cette dernière ne casse en ajoutant des tests dessus ;

  • D’autre part, ajouter des tests sur un code complexe va souvent vous permettre de le simplifier. En effet, vous serez forcé de décomposer votre code pour pouvoir le tester plus facilement.
    C’est comme une boucle vertueuse. :)

Mais ce n’est pas mieux de commencer par les endroits les plus simples ? Au moins, j’aurais des tests plus rapidement, non ?

Oui, mais non ! :)

C’est souvent une mauvaise pratique de commencer par les parties les moins critiques et les plus simples, pour aller vers les plus compliquées et les plus critiques. C’est un peu la solution de facilité, mais c’est aussi celle qui aura le moins d’impact sur la qualité de votre produit.

Par définition, une fonction ou un script facile à tester est un élément de votre code qui ne devrait pas avoir beaucoup de bugs. Cela ne veut pas dire que cet élément ne doit pas être testé, mais plutôt que ce n’est pas la priorité. :)

Rappelez-vous du cycle de vie de votre produit :

Les différentes phases du cycle de vie d'un produit avec leurs tests associés. POC, pas de tests. Croissance, tests unitaires et d'intégration. Maturité, tests complexes
Les différentes phases du cycle de vie d'un produit avec leurs tests associés.

Une fois que vous avez écrit votre test, lancez Jest et regardez si tout se passe comme prévu. Si le test passe, bravo ! Vous pouvez maintenant en ajouter un nouveau.

Définissez et lancez les tests à réaliser

OK, mais du coup, qu’est-ce que je dois écrire comme tests ?

J’attendais cette question avec impatience : c’est celle qui revient le plus parmi mes élèves, et elle est totalement justifiée. En fait, cela va le plus souvent dépendre de l’élément que vous souhaitez tester. Pour vous aider, on va prendre un exemple : le système de pagination.

Le code de la pagination est situé dans le fichier js/pages/common/pagination/index.js  . Dans ce dossier, vous avez deux méthodes : une méthode getNumberOfPages  et une autre,  render  . On s’intéressera à la méthode render  dans le cadre des tests d’intégration.

  getNumberOfPages: numberOfSensors => Math.ceil(numberOfSensors / ITEMS_PER_PAGE)

Cette fonction prend un paramètre :  numberOfSensors  . Cela correspond au nombre total de capteurs récupérés dans le fichier homepage-data.json  . Pour cette fonction, je peux écrire quatre tests :

  • Un cas de test où je lui passe 12 en paramètre. Ici, je ne vais pas tester le résultat de la fonction, mais simplement vérifier si la fonction me retourne bien quelque chose.

  • Un cas de test où je lui passe 0 en paramètre, soit aucun capteur. Et je teste si le nombre de pages retourné est 0.

  • Un cas de test où je lui passe 7. Le résultat retourné par la fonction devrait être de 1, car j’affiche 8 capteurs par page. Autrement dit, il n’y aura qu’une seule page.

  • Enfin, un cas où je lui passe 34, et où je teste si j’ai bien 5 pages.

Euhhh… On peut voir le code des tests ?

Oui !

Vous n’avez pas encore découvert Jest, mais vous allez voir dans le code ci-dessous que c’est quand même assez parlant (et que ça fera encore plus sens dès le prochain chapitre).

/**
* @function Pagination.getNumberOfPages
*/

describe('Pagination Unit Test Suites', () => {
    it('should return something', () => (
        expect(Pagination.getNumberOfPages(12)).toBeDefined()
    ))


    it('should return 0', () => (
        expect(Pagination.getNumberOfPages(0)).toEqual(0)
    ))

 
    it('should return 1', () => (
        expect(Pagination.getNumberOfPages(7)).toEqual(1)
    ))

 
    it('should return 5', () => (
        expect(Pagination.getNumberOfPages(34)).toEqual(5)
    ))

})

Vous pouvez trouver ce code dans le fichier js/pages/common/pagination/index.unit.test.js  . Si vous avez installé l’extension Jest pour VsCode, vous verrez un petit check vert à côté de chaque test. Cela signifie que ces derniers passent !

Le check vert situé à gauche de la ligne 7 vous indique que le test passe
Le check vert situé à gauche de la ligne 7 vous indique que le test passe

Faites attention à la spirale des tests

Vous allez vite voir que réaliser des tests est quelque chose de très chronophage, mais ça a quand même un côté assez fun : c’est souvent une petite victoire de voir que les tests passent bien quand on a écrit une fonctionnalité ! Seulement voilà, il faut faire attention à ne pas tomber dans la spirale des tests et partir trop loin : le fameux “Et si”.

Le fameux “Et si” ?

Reprenons l’exemple de la fonction précédente, getNumberOfPages  . On pourrait vouloir tester autre chose d'un nombre.

Par exemple : on pourrait tester si le paramètre est une chaîne de caractères. Ou alors un tableau. Ou un nombre négatif.

Voilà le fameux “Et si” !

Dans le cadre de cette fonction, le but n’est pas de savoir si l’élément peut être autre chose qu’un nombre. On admet, par définition, que le paramètre numberOfSensors  sera un nombre. Si d’aventure, vous commenciez à avoir des erreurs dessus, alors, vous mettriez à jour ce code pour qu’il prenne les nouvelles règles en compte.

Vous pouvez donner des informations aux autres développeurs concernant le type de données attendu, en utilisant la JSDoc comme ci-dessous :

/**
*
* @param {number} numberOfSensors
* @returns {number}
*/
getNumberOfPages: numberOfSensors => Math.ceil(numberOfSensors / ITEMS_PER_PAGE),

Ici, je précise que getNumberOfPages  prend pour paramètre un nombre, et retourne un nombre. Si vous survolez cette fonction avec VSCode, ce dernier va vous donner des informations sur ces éléments.

VSCode vous informe du type du paramètre attendu et du type de valeur de retour
VSCode vous informe du type du paramètre attendu et du type de valeur de retour

Plutôt cool, hein ?

Découvrez des tests unitaires pour notre application

Chose promise, chose due, vous voilà de retour sur notre application de façades !

Ouvrez VSCode et changez de branche, vous pouvez vous mettre sur la branchepartie-2/chapitre-1  .

Inspectez le code du projet et plus particulièrement le dossier js/utils  . Ce dernier contient toutes les fonctions "utilitaires" de ce projet. Par exemple, des formateurs de date, des vérifications, etc.

Inspectez le code, et trouvez des axes d’amélioration sur les fonctions. Essayez de trouver les aspects les plus critiques de l’application et les fonctions les plus complexes, et posez la question suivante : comment la testeriez-vous ?

Dans la vidéo ci-dessous, je vais vous présenter ma solution.

Nous allons nous occuper de coder les tests pour ces éléments dès le prochain chapitre.

En résumé

  • Le test unitaire permet de tester une partie spécifique de votre application (généralement une fonction). Il permet d’éviter les effets de bord : pour un paramètre donné, le résultat sera toujours le même.

  • Les tests permettent à la fois de fiabiliser les applications et de décomplexifier la base de code. Une fonction difficilement testable est souvent une fonction qui doit être refactorisée pour pouvoir être mieux testée.

  • Essayez toujours de commencer par réaliser des tests sur les parties les plus complexes et les plus critiques de votre application. Ce sont ces dernières qui auront souvent le plus de chance de casser.

  • Attention au “Et si” lors de la phase de rédaction de votre test. Il est important que vous sachiez à quoi vous intéresser lors de la rédaction de votre test.

Maintenant que vous avez compris ce qu’étaient les tests unitaires et où les intégrer, vous allez découvrir Jest et écrire vos premiers tests unitaires !

Exemple de certificat de réussite
Exemple de certificat de réussite