Les tests unitaires, la clé de voûte de la qualité d’un projet

Qui n’a jamais vécu le cauchemar d’un gros bug de production, la prise de tête de la recherche d’un bug qui semble simple ? Dans bien des cas, les tests unitaires vont vous sauver la mise. Il existe évidemment de nombreux types de tests que vous pouvez effectuer dans le cadre d’un projet pour garantir la qualité, la fiabilité et la sécurité de votre application : tests unitaires, tests d’intégration, tests fonctionnels, tests de performance, tests de sécurité ou de pénétration… 

Mais les plus importants sont les tests unitaires. Ces tests se concentrent sur la validation de petites unités de code, telles que des fonctions ou des méthodes. Et, pour nous, ils sont la pierre angulaire de la qualité d’un projet parce qu’ils garantissent que votre code fonctionne correctement et résiste aux modifications futures. 

Illustration tests unitaires

Pourquoi les tests unitaires garantissent la qualité d’un projet ? 

Ils seront exécutés automatiquement à chaque intégration de code. Cela garantit que les nouvelles modifications n’ont pas cassé les fonctionnalités existantes. Si un test échoue, l’outil d’intégration continue signale immédiatement le problème, ce qui permet de corriger le code défectueux avant qu’il ne soit fusionné avec le code principal.

Mais pas seulement, ils permettent aussi : 

  • D’apporter une preuve de l’exécution des tests et de quantifier son taux de couverture. Il est ainsi possible d’évaluer rationnellement l’évolution de cette qualité au fil du temps. 
  • De gagner du temps sur la résolution de problèmes récurrents en évitant une itération déploiement – test de qualification – retour de bug – correction – redéploiement (qui a un impact sur le coût et sur le respect du planning). 
  • Il autorise un niveau de confiance suffisante sur les parties à risques (tel qu’un système de facturation ou de paiement) pour permettre de réduire l’étendu des tests manuels à chaque mise en production. Pour un projet évoluant rapidement, le délai requis pour passer de la phase de test au passage en production est un facteur clé pour augmenter le nombre de cycles. 

Comment rédiger de bons tests unitaires ! 

Ecrire de bons tests unitaires est assez simple, voici ce qu’il faut garder en tête :

  • Chaque test unitaire doit se concentrer sur un seul aspect d’une fonction ou d’une méthode ; 
  • Il faut couvrir le plus de conditions possibles. Y compris les chemins d’exécution alternatifs (branches if/else, boucles, exceptions, etc.)
  • Il ne faut pas oublier les “tests aux limites”. C’est-à-dire fournir des entrées qui vont solliciter la robustesse de l’algorithme testé. Par exemple avec des données invalides pour les règles métiers (nous attendons la génération d’une erreur) ou calculer une TVA sur un panier d’achat avec des montants unitaires faibles (susceptibles de faire entrer en jeu les règles d’arrondis ligne à ligne ou sur le total de facture).
  • Tips : les outils de tests unitaires permettent d’utiliser des “data providers” qui vont alimenter le même test avec des jeux de données très différents. 
  • Il faut isoler les cas de tests pour limiter les interactions entre eux (et ainsi identifier plus rapidement la partie défaillante en cas d’échec). 
  • Il ne faut pas faire dépendre le cas de test d’un contexte susceptible de varier (comme un changement persisté en base de données ou l’appel à un service externe). Les mocks, les fixtures, les transactions sont autant de pratiques qui peuvent aider à isoler le test des conditions extérieures.  
  • Dans la mesure du possible, donnez des noms de test clairs et significatifs pour décrire ce qui est testé. Par exemple, ICanHashMyPassword plutôt que TestPassword.

Il faudra évidemment veiller à maintenir vos tests à jour au fur et à mesure que votre code évolue parce que les tests obsolètes peuvent devenir une source de confusion.

Tips : Utiliser un outil d’analyse de couverture de code vous permettra de savoir quelles parties de votre code sont testées et lesquelles ne le sont pas. Cela permet en prime de valoriser ce travail relativement invisible pour les non techniciens (chef de projet client, investisseurs, etc).  

Prenons un exemple sur un cas concret (en PHP)

Le client souhaitait qu’un rapport hebdomadaire sur ses ventes soit généré dans un document excel exploité pour verser des commissions aux commerciaux via son outils de comptabilité tiers.

Ce document doit se nommer sous la forme 2023-05.xlsx pour la 5ième semaine de l’année 2023 et être déposé sur un ftp. 

Nous allons nous intéresser pour notre cas de test au nommage du fichier :

// serviceDeNommageDesFichiers.php

function genererLeNomDuFichier(\DateTimeInterface $dateDuRapport) : string {
    return $dateDuRapport->format(‘Y-W’).’xlsx’;
}

Après avoir installé PHPUnit (si ce n’est pas le cas), vous créez un fichier de test (par exemple, MathTest.php) dans le même répertoire que votre fonction :

// serviceDeNommageDesFichiersTest.php

use PHPUnit\Framework\TestCase;

require serviceDeNommageDesFichiers.php';

class GenerationDesRapportsTest extends TestCase {
    public function provideDates() {
        return [
             [‘2020-12-27, ‘2020-52.xlsx’],
             [‘2020-12-28’, ‘2020-53.xlsx’],
             [‘2021-01-03’, ‘2020-53.xlsx’],
             [‘2021-01-04’, ‘2021-01.xlsx’],
             [‘2021-01-10’, ‘2021-01.xlsx’],
       ];
    } 
    /**
     * @dataProvider provideDates()
     */
    public function genererUneDateValideTest($dateDuRapport, $nomAttendu) {
        $dateDuRapport = \DateTimeImmutable::createFromFormat(‘y-m-d’, $dateDuRapport);
        $nomObtenu = genererLeNomDuFichier($dateDuRapport);
        $this->assertEquals($nomAttendu, $nomObtenu);
    }
}

Il ne vous reste plus qu’à exécuter le test avec la commande :

vendor/bin/phpunit serviceDeNommageDesFichiersTest.php

… et constater que ce test ne fonctionne pas ! Pourquoi ?

Dans la norme ISO_8601 (voir https://fr.wikipedia.org/wiki/Num%C3%A9rotation_ISO_des_semaines ), le numéro de la semaine numéro 1 de l’année doit comporter au moins 4 jours dans cette année. En 2021, le 3 janvier est un dimanche. La première semaine commence donc le lundi 4 janvier. 

Le 3 janvier 2021 correspond donc à la notation 2020-53.xlsx sauf que notre fonction de génération de noms va sortir 2021-53.xlsx car le Y stipule l’année correspondant à la date. Pour avoir la chaine 2020-53.xlsx, il faut réécrire avec $dateDuRapport->format(‘o-W’).’xlsx’; (o étant l’année de la semaine respectant la norme iso).

Cet exemple permets d’identifier plusieurs choses :

  • Cela demande un travail en atelier avec le product owner pour identifier les spécifications des normes à respecter. Le chef de projet, s’il est familier avec le monde des ERP/CRM ou la documentation du logiciel comptable peut aussi aider à mettre en lumière l’importance de ce cas limite. 
  • Reproduire cette situation avec un test manuel aurait été difficile : la date du rapport à générer ne peut probablement pas être forcé dans le logiciel. 
  • Un développeur pourrait à l’occasion d’une évolution ou d’une réécriture modifier “o-W” par “Y-W” (qui est plus naturel à lire). Il serait peu probable de retester ce cas là plusieurs années après le développement initial. 
  • Le bug est susceptible d’intervenir un à deux ans après la modification l’ayant provoqué ce qui compliquera l’analyse (d’autant qu’il faudra encore attendre quelques années pour le reproduire à la bonne date). 
  • L’impact en jour homme cumulé du comptable qui va identifier l’erreur, la remonté aux chefs de projets en charge, le développeur, le redéploiement et les correctifs comptable et financier induits par cette erreur est sans commune mesure avec le coût de l’implémentation de ces quelques lignes. 

Quelques outils pour ses tests unitaires : 

#1 – Des outils pour l’exécution des tests unitaires et la couverture du code : 

Principalement PHPUnit qui est un framework de tests unitaires PHP et il intègre un rapport de couverture de code. 

Xdebug est un outil de profilage pour PHP qui peut être utilisé en conjonction avec PHPUnit pour générer des rapports de couverture de code. Il est souvent utilisé dans les environnements de développement.

Note : PCov est plus performant (plus rapide) lorsqu’on intègre ces tests dans le CI/CD.

#2 – Des outils pour simplifier la rédaction de ces tests : 

PHPUnitGen qui peut générer des squelettes de tests pour les classes et méthodes de votre code. Cela simplifie la création de tests unitaires.

Le framework Laravel propose des fonctionnalités de scaffolding qui peuvent générer des tests unitaires pour les contrôleurs, les modèles et les migrations.

#3 – D’autres outils 

D’autres outils permettent de gérer les tests comme Codeception ou d’analyser la qualité du code (et donc des rapports de couverture du code) comme Scrutinizer.

Au fait, quel est le bon taux de couverture pour des tests unitaires ?

Le niveau de couverture de tests unitaires est un sujet de débat pour les développeurs ! 

Couvrir l’application à 100% est une tentation logique (notamment pour des applications à haute criticité comme le médical, le transport ou des données financières). Cependant, plus la couverture est élevée, plus l’effort pour la conserver et l’améliorer l’est aussi. 

Aussi, il faut convenir d’un bon compromis entre : 

  • Couvrir les parties du code qui sont critiques pour le bon fonctionnement de l’application (les fonctionnalités de sécurité, les calculs complexes, etc.) ; 
  • Les coûts associés à l’écriture, l’exécution et la maintenance de ces tests (plus il y aura de tests, plus les changements fonctionnels impliqueront un nombre important de tests à réécrire). 

Il n’y a donc pas de bon chiffres. Pour un projet standard, on parle d’une couverture minimale à 10-15%, correcte à partir de 30% de couverture et au-delà de 50%, on approche de l’exemplarité. Bien sûr ces taux sont pour un applicatif dans son ensemble (en excluant les librairies tierces et le framework, déjà testés distinctement) : cette moyenne implique que certaines fonctionnalités applicatives soient testées à un taux proche du 100% et d’autres peu ou pas. 

Quel impact sur les coûts du projet ?

Alors faut-il faire des tests unitaires à tout prix ? Pas nécessairement, pour certaines phases de démarrage de projet (MVP par exemple), l’écriture des tests est un surcoût et surtout leur réécriture permanente risque de coûter très cher. 

Ainsi, de nombreux projets voient le jour sans tests unitaires (et les bugs finissent toujours par être identifiés un jour). La sécurité et la preuve de test est un avantage principalement pour le product owner : il lui offre la maîtrise des risques de son projets (budget, planning, évolutivité, indicateur quantifié pour évaluer la qualité du produit développé dans le temps). 

Chez The Coding Machine, nous assurons le développement de tests automatisés pour des parties jugées indispensables par le lead technique et l’architecte en charge du projet. La mise en place d’atelier avec le client et l’écriture de tests spécifiques pour atteindre un objectif de couverture sur des fonctionnalités critiques implique donc un surcoût présenté en option. 

Conclusion

Il n’est jamais facile d’engager des dépenses sur un aspect non fonctionnel du produit. Même pour l’équipe chargée des développements, c’est une tâche peu gratifiante. Pour autant, les gains sur la maîtrise du budget et du planning, la réduction des temps de déploiement lors de l’évolution continue et l’assurance qualité que cela offre permettent de limiter les risques liés au projets. 

De plus, de même que pour tout autre produit, la qualité de fabrication est un élément important dans le ressenti utilisateur. Il n’y a rien de plus désagréable quand des régressions surviennent alors qu’elles ont été maintes fois remontées à l’équipe de développement. Au-delà du product owner qui peut s’agacer, les utilisateurs quotidien du produit peuvent rapidement perdre confiance et rejeter un outil dont la fiabilité est trop souvent mise en défaut. 

C’est un élément qui est souvent clé dans les actifs de la société qui le produit. Une bonne couverture des tests permet d’en prendre soin pour l’amortir dans la durée et donne des éléments quantifiables sur l’atteinte des objectifs qualités. Les tests unitaires permettent ainsi de mieux maîtriser votre dette technique.

Et, si vous avez une méthode magique, souhaitez échanger avec nous sur votre pratique, n’hésitez pas à nous contacter !

Faites des tests unitaires, c'est bon pour vos projets - Mistral Oz

par Mistral Oz

Articles similaires TAG