Mise en place d’une stratégie de test
Introduction
Tester une application web pour vérifier son bon fonctionnement peut s’effectuer de diverses manières. La méthode la plus simple, qui sera suffisante dans de nombreux cas, consiste à lancer un navigateur, aller sur la page à tester et vérifier son comportement en regardant ce qui est affiché. Cette méthode ne permet cependant pas de garantir à elle seule la qualité d’un logiciel à moyen terme. Il faudra donc rapidement trouver une alternative.
Un processus de développement est constitué d’itérations : on crée une fonctionnalité, on la modifie, puis on ajoute une deuxième fonctionnalité, on modifie à nouveau la première fonctionnalité, puis on corrige une erreur sur la deuxième fonctionnalité, etc. Rapidement, les fonctionnalités commencent à avoir des impacts les unes sur les autres, et modifier un comportement à un endroit peut altérer un autre comportement ailleurs. C’est ce qu’on appelle communément les effets de bord.
Plus une application grossit, plus il est difficile de la tester manuellement de manière exhaustive. Théoriquement, à chaque nouvelle modification, il faudrait faire un nouveau test, mais également refaire tous les tests précédents pour vérifier que tout fonctionne...
Tests unitaires
1. Introduction
Les tests unitaires permettent de vérifier une fonction ou un morceau de programme de manière atomique. Autrement dit, les tests unitaires assurent qu’une procédure se déroule bien comme prévu, peu importe son contexte.
Pour tester une fonction de manière unitaire, on écrira donc généralement une ou plusieurs fonctions de tests en miroir qui devront être le plus exhaustives possible. De la même manière, pour tester une classe, on créera un ensemble de tests unitaires qui couvrira l’ensemble des méthodes.
2. Créer un test unitaire
Un test unitaire vérifie le fonctionnement d’une portion de programme. Nous allons tester une fonction centToEuro d’un modèle Invoice, qui convertit un entier représentant un nombre de centimes en une chaîne de caractères avec deux chiffres après la virgule représentant des euros, telle que définie ci-dessous :
Fonction centToEuro
public function centToEuro(int $cents): string
{
return number_format($cents / 100, 2, ',', ' ');
}
On attend donc de cette fonction qu’elle ait le comportement suivant :
-
centToEuro(100) doit retourner 1,00.
-
centToEuro(15) doit retourner 0,15.
-
centToEuro(2990) doit retourner 29,90.
Le comportement est connu par contrat. C’est-à-dire que le comportement attendu est défini et connu par la personne qui a développé cette fonction. Il va maintenant falloir coder ce contrat en tant que test unitaire.
Création du modèle invoice et du test unitaire...
Base de données
Dans de nombreux cas, les tests sont exécutables de façon autonome et n’ont pas besoin de contexte. Il arrive cependant parfois qu’on ait besoin d’accéder à une base de données pour vérifier un comportement.
Dans un site e-commerce par exemple, on peut souhaiter vérifier le fonctionnement de la méthode Product::specialOffer() qui retourne la liste des produits en promotion présents dans la base de données.
Laravel fournit plusieurs outils pour tester des applications pilotées par une base de données.
Dans le contexte le plus strict des tests unitaires, on essaiera d’éviter au maximum la dépendance avec la base de données. Un test unitaire doit s’efforcer d’être autonome et atomique, il doit donc pouvoir s’exécuter sans avoir d’interactions en dehors de la fonction testée.
1. Configuration
Les tests ne doivent pas être dépendants de la configuration d’un poste de travail en particulier : les tests doivent être exécutables depuis le poste de n’importe quelle personne de l’équipe de développement, mais également sur une machine d’intégration continue (l’intégration continue sera étudiée rapidement en fin de chapitre). Il faut donc que ces tests aient une configuration propre. Cela évitera également de polluer la base de données d’un poste de travail avec des données de test.
Par défaut, Laravel Sail crée une base de données dédiée aux tests. Ainsi, la base de données utilisée en local par le développeur n’est pas impactée. Cette configuration se situe dans le fichier phpunit.xml présent à la racine du projet.
2. Migrations et transactions
Les tests devant être indépendants les uns des autres, Laravel propose un mécanisme pour créer toutes les tables de la base de données avant chaque test et les supprimer ensuite. En d’autres termes, pour chaque méthode de test, le framework lancera toutes les migrations, exécutera le test, puis lancera l’annulation de toutes les migrations afin de retrouver une base vierge pour le test suivant. La base de données sera ainsi toujours...
Tester des fonctionnalités
1. Introduction
Au-delà de traiter des fonctions une par une avec les tests unitaires, il est également possible de tester les fonctionnalités intégrées les unes avec les autres, pour vérifier le comportement d’un ensemble qui fait sens.
Souvent appelé test d’intégration, ce type de vérification valide le fonctionnement d’une partie de l’application sous la forme d’un scénario. Par exemple, on peut souhaiter vérifier qu’une page peut effectivement être chargée pour une route donnée ou qu’un utilisateur est créé dans la base de données lorsqu’un formulaire d’inscription est soumis avec les bons paramètres.
2. Créer un test de fonctionnalité
Avant de créer vos propres tests en utilisant la commande Artisan make:test, une bonne façon d’apprendre à tester son application est de s’inspirer de tests déjà écrits par la communauté de Laravel. Nous allons regarder comment le package Laravel Breeze est testé. Pour l’installer, vous pouvez vous référer au chapitre Authentification et autorisations. Une fois installé, on peut constater que la commande sail test exécute, en plus de nos tests, les tests associés au package.
Test de l’affichage du login, classe Tests\feature\auth\AuthenticationTest
public function test_login_screen_can_be_rendered():...
Laravel Dusk
1. Introduction
Tester le front-end d’une application web de manière automatisée est souvent une tâche difficile. Peu de frameworks fournissent des outils simples à utiliser pour y arriver. Laravel Dusk permet de tester une application dans des conditions réalistes, proches de l’expérience réelle des utilisateurs. Pour cela, Dusk utilise ChromeDriver, qui permet de piloter un navigateur Chrome sur le poste de travail ou la machine de test. C’est-à-dire qu’un navigateur est réellement lancé et piloté par le code et qu’il suivra donc le scénario rédigé. Cela permet de réaliser en quelques secondes ce qui aurait pris des heures à une équipe de testeurs, pour un résultat identique, voire parfois meilleur.
En quelque sorte, Laravel Dusk met à disposition un testeur fiable survitaminé, qui navigue de page en page, remplit des champs, revient en arrière ou valide des formulaires.
Le code de pilotage est élégant, simple à rédiger, à relire et permet même de gérer les SPA (Single Page Apps) ou la problématique du JavaScript chargé de manière asynchrone.
2. Installation
Pour commencer à utiliser Dusk avec Laravel Sail, il faut d’abord que le service selenium soit installé. Il fait partie des services installés par défaut lors de l’installation d’une nouvelle application Laravel mais peut être rajouté ultérieurement si besoin. Par contre, le package laravel/dusk n’est pas encore installé et doit être ajouté.
Commandes à exécuter pour l’ajout du service selenium
php artisan sail:add selenium
Commandes à exécuter pour l’installation du package laravel/dusk
sail composer require --dev laravel/dusk
sail artisan dusk:install
Par défaut, Dusk se base sur les variables d’environnement du fichier .env. Pour éviter que Dusk exécute ses tests sur notre environnement local, nous devons lui spécifier ses propres variables d’environnement. Il faut faire une copie de notre fichier .env en respectant le nommage .env.dusk.{environment}. Ce qui donne pour nos tests en local : .env.dusk.local. Les variables d’environnement...
Automatiser le lancement des tests
Pour garantir le niveau de qualité et la non-régression de l’application, le lancement de l’intégralité des tests doit avoir lieu après chaque modification. Ainsi, avant chaque déploiement ou mise en production, on s’assure que toutes les vérifications unitaires, fonctionnelles ou à travers le navigateur, sont validées.
On pourrait lancer les tests manuellement, en appelant la commande Artisan ou PHPUnit correspondante avant de livrer. Cependant, cette approche pose plusieurs problèmes :
-
Il est possible d’oublier de lancer les tests.
-
Les tests étant longs à exécuter, cela peut temporairement paralyser le développeur qui attend la fin de l’exécution avant de continuer.
-
Cette tâche est mécanique et fastidieuse : elle ne doit idéalement pas être confiée à un humain.
Il existe de nombreux outils auto-hébergés ou en ligne pour lancer les tests à chaque fois qu’un push est fait sur le dépôt GIT d’origine. Ce type d’outils exécute sur une machine dédiée à cet usage l’intégralité des tests à chaque modification et alerte l’équipe en cas d’échec des tests. De tels services permettent de rentrer dans un cycle d’intégration...