Les tests automatisés - L'invasion des robots !

Les tests unitaires en PHP
Le métier de développeur web peut vite se réveler long et fastidieux.
En effet, chaque fonctionnalité ajoutée à un projet doit faire l'objet d'une vérification :
- Je développe une fonctionnalité ... je vérifie qu'elle marche
- Je développe une autre fonctionnalité ... je vérifie qu'elle marche
- Puis une troisième ... je vérifie qu'elle marche
- et ainsi de suite
Mais en réalité, cela ne suffit pas 😢. Le développement d'applications étant une affaire complexe, il favorise l'intrication de pans entiers de code. Si bien que toutes les parties d'un site peuvent avoir en partage certains morceaux de code dont la manipulation devient hasardeuse, au risque de voir apparaître des régressions dans son code 😱.
La rigueur nous imposerait donc la démarche suivante :
- Je développe une fonctionnalité ... je vérifie qu'elle marche
- Je développe une autre fonctionnalité ... je vérifie qu'elle marche, ainsi que la précédente.
- Puis une troisième ... je vérifie qu'elle marche, et je reteste la première et la deuxième pour m'assurer de n'avoir rien cassé...
- et ainsi de suite, jusqu'à la fin des temps...

... Alors que faire ?
Et bien c'est très simple : mettre en place des tests automatisés 🤓.
🤖 À la fin de cette quête, tu seras capable de :
- ✅ Installer PHPUnit dans un projet
- ✅ Ecrire un test
- ✅
Conquérir le monde !Confier la partie rébarbative de ton travail à un robot 😎
Sommaire
La théorie - Au boulot les robots 🤖🤖🤖
Dans un sens les tests automatisés sont assez simples à comprendre : les tests sont des scripts qui vont vérifier que d'autres scripts font bien leur travail et ont bien le comportement qu'on attend d'eux.
Il existe différents types de tests automatisés:
- les tests unitaires
- les tests d'intégrations
- les tests fonctionnels
- les tests de performance
- ...
Pour l'heure nous allons parler uniquement des tests unitaires.
Les test unitaires
Comme tu le sais à présent un bon code est un code SOLID !
Dans cet acronyme, ce qui nous intéresse le plus est le "S" pour Single responsibility principle : les fonctions que l'on va écrire ne doivent avoir qu'une seule responsabilité.
Et c'est justement cette responsabilité que l'on veut tester unitairement.
Pour être plus concret, prenons un exemple :
<?php
function mySum(int $firstValue, int $secondValue ): int
{
return $firstValue + $secondValue;
}
Ici j'ai un fichier functions.php, dans lequel se trouve la fonction mySum(). Cette fonction est peut être utilisée à différents endroits de mon site, pour calculer des factures, gérer des stock ou déterminer le résultat d'une équation complexe... Au final peu importe. Si une fonction est bien faite elle doit pouvoir être utilisée partout, et donc être testée hors de son contexte, ce qui va nous servir 😉!
Comment écrire un bon test - la règle des 3 "A"
Écrire du code venant vérifier un comportement peut sembler un peu étrange au début, mais l'exercice devient plus facile si on observe la règle des 3 A :
- Arrange : préparer les données qui vont servir au test
- Act : appeler la fonction à tester avec les données préparées pour en récolter un résultat
- Assert : vérifier que le résultat obtenu est bien celui attendu
<?php
include "functions.php";
//Arrange
$firstNumber = 5;
$secondNumber = 8;
$expectedResult = 13;
//Act
$result = mySum($firstNumber, $secondNumber);
//Assert
if($result === $expectedResult) {
echo '✅ Youpi ! Notre fonction retourne le bon résultat.';
} else {
echo '💀 Erreur Fatale : on a un gros soucis là.';
}
Comme on peut le voir dans cet exemple, l'organisation Arrange\Act\Assert nous permet de bien distinguer les étapes nécessaires à un bon test. Si j'exécute le fichier test.php, je saurai si la fonction mySum() a le bon comportement.
La pratique - PHPUnit : un framework pour les gouverner tous
Une fois mon test en place, je peux donc le relancer à chaque modification de mon application pour vérifier que ma fonction continue à faire son travail 👍.
- Mais pour l'instant je dois le lancer à la main : c'est embêtant 🤔.
- Et quand j'aurai écrit des tests pour chacune des fonctions de mon programme, je devrai ensuite les lancer tous les uns à la suite des autres : ça va être long 😖.
- Naturellement, je devrai ensuite lire tous les retours de mes tests pour savoir si l'un d'eux à échoué... Non mais dans quoi je me suis lancé 😵 !
Pas de panique ! Il y a une solution : PHPUnit
PHPUnit est un framework, qui permet de facilement centraliser l'exécution de tous les tests d'une application, mais également leur interprétation, afin que ne ressortent que les tests qui ont échoué.
Comment cela fonctionne t-il ? 🤓
Installation
Pour installer PHPUnit dans un projet, le plus simple est de le déclarer comme une dépendance et donc... de laisser faire composer 😎.
Les tests étant déclenchés en phase de développement, nous indiquons à composer d'installer cette dépendance avec l'option --dev.
composer require phpunit/phpunit --dev
Utilisation
Maintenant que PHPUnit est installé, regardons comment il nous demande d'écrire nos tests et en quoi cela diffère du test que nous avons écrit tout à l'heure :
- Dans un fichier
SimpleTest.php, PHPUnit nous demande de créer une classe qui hérite d'une des classes qu'il nous fournit, la classeTestCase:
<?php
include 'functions.php';
use PHPUnit\Framework\TestCase;
class SimpleTest extends TestCase
{
}
- Ensuite on déclare une fonction publique qui ne retourne rien et qui hébergera le code de notre test automatisé.
Attention les noms de vos fonctions de tests doivent obligatoirement débuter par test. Une bonne pratique consiste à écrit "test" suivi du nom de la fonction que l'on teste : testMySum()
<?php
include 'functions.php';
use PHPUnit\Framework\TestCase;
class SimpleTest extends TestCase
{
public function testMySum(): void
{
}
}
Il est temps de nous occupé de nos trois A 😤.
- On commence par le Arrange, où l'on prépare toutes les données qui vont servir au test : valeurs de saisie, résultat attendu et/ou toutes autres données qui vont servir au test.
<?php
include 'functions.php';
use PHPUnit\Framework\TestCase;
class SimpleTest extends TestCase
{
public function testMySum(): void
{
//Arrange
$firstValue = 5;
$secondValue = 8;
$expectedResult = 13;
//Act
$sum = mySum($firstValue, $secondValue);
//Assert
$this->assertSame($expectedResult, $sum,
"La méthode MySum() ne fonctionne plus :-("
);
}
}
- On enchaine par la partie Act, où l'on vient appeler la fonction dont on souhaite vérifier le comportement, avec les données préparées dans le Arrange.
<?php
include 'functions.php';
use PHPUnit\Framework\TestCase;
class SimpleTest extends TestCase
{
public function testMySum(): void
{
//Arrange
$firstValue = 5;
$secondValue = 8;
$expectedResult = 13;
//Act
$sum = mySum($firstValue, $secondValue);
//Assert
$this->assertSame($expectedResult, $sum,
"La méthode mySum() ne fonctionne plus :-("
);
}
}
- Et enfin l'Assert.
Cette partie est cruciale car c'est ici que nous venons vérifier que le résultat obtenu dans le Act est bien celui prévu. Parce que PHPUnit a pour mission de compiler les résultats des tests, il nous confie des fonctions à utiliser : les asserts. Ici, nous avons utilisé la méthode
assertSame()de PHPUnit qui vérifie si deux valeurs sont identiques. Elle prend deux paramètres obligatoires et un troisième optionnel:
- le premier, la valeur attendue,
- le second, la valeur à tester (qui si tout se passe bien sera la même que la valeur attendue 🤓)
- le troisième, optionnel, permet d'écrire le message d'erreur retourné si le test n'est pas validé.
<?php
use PHPUnit\Framework\TestCase;
class SimpleTest extends TestCase
{
public function testMySum(): void
{
//Arrange
$firstValue = 5;
$secondValue = 8;
$expectedResult = 13;
//Act
$sum = mySum($firstValue, $secondValue);
//Assert
$this->assertSame($expectedResult, $sum,
"La méthode mySum() ne fonctionne plus :-("
);
}
}
Jettes un œil sur la longue liste des asserts PHPUnit, tu y trouveras forcément la validation de test que tu cherches 😉.
L'écriture du test est terminée 🎉 ! Il faut maintenant demander à PHPUnit de le lancer. Comment ? Poliment 😅
Positionne-toi à la racine de ton projet avec ton terminal et entre la commande :
vendor/bin/phpunit SimpleTest.php
Naturellement ici on demande à PHPUnit de ne lancer que la classe de test que l'on a créé, mais nous pourrions tout à fait ranger toutes nos classes de test dans un répertoire /tests pour ensuite lancer la commande :
vendor/bin/phpunit tests
Cela aura pour effet de récupérer toutes nos classes et de lancer tous les tests qui les composent. D'ailleurs, c'est ce qui a été préparé pour le challenge de cette quête, on en reparle bientôt 😎.
Au terme du lancement de PHPUnit, nous obtenons le résultat suivant :
PHPUnit 10.1-dev by Sebastian Bergmann and contributors.
Runtime: PHP 8.2.0
. 1 / 1 (100%)
Time: 00:00.003, Memory: 6.00 MB
OK (1 test, 1 assertion)
🏆Victoire notre test est bien passé ! PHPUnit nous dit même le nombre de tests effectués et le nombre d'assertion qui sont passées. Et tu l'auras sans doute remarqué, les tests se déclenchent dans l'ordre de leur déclaration.
Challenge 💪
Partir bille en test !
Tout d'abord, crée un nouveau repository à partir de celui-ci grâce au bouton vert Use this template puis clone-le en local.
D'autres instructions te sont données dans le fichier README.md pour démarrer le projets.
Quelques précisions pour ne pas te perdre.
Le projet que tu viens de cloner est construit sur une structure de fichiers respectant les recommandations PSR-4.
src/
|--Calculator.php
tests/
|--CalculatorTest.php
La fonction mySum() que nous venons de voir en détail est à présent la méthode sum() de la classe Calculator. Et de la même façon, le test testMySum() est écrit dans la méthode testSum de la classe CalculatorTest et teste donc la méthode sum() de la classe Calculator.
Pour lancer les tests, il faut donc utiliser la commande que tu as vue un peu plus tôt :
vendor/bin/phpunit tests --color
L'option --color, c'est pour faire joli et surtout pour mieux identifier les erreurs dans le terminal 😇.
Maintenant, à toi de jouer
Second test
- Tout d'abord, en te basant sur la méthode
sum()présente dans le fichierCalculator, crée dans la même classe une méthodemultiplication()qui prend deux entiers en paramètres et renvoie leur produit 💪. - Puis crée un test pour cette nouvelle méthode dans la classe
CalculatorTest.
Troisième test
Pour ce dernier test, tu vas devoir chercher dans la documentation de PHPUnit un assert qui permet de tester un booléen.
- Crée à présent une méthode
isEven()qui prend un entier en paramètre et retournetruesi il est pair oufalsesi il est impair. 💡Comment vérifier qu'on nombre est pair ou impair ? As-tu déjà entendu parlé de modulo 😏 ? - Enfin, crée un test pour cette méthode dans la classe
CalculatorTestavec l'assert approprié 🤖🤖!
Lorsque tu as terminé, fais un commit et un push de ton travail et publie le lien vers ton repository.
Critères de validation
- En tant que correcteur, clone le projet soumit et pense à lancer un
composer install. - Les noms des méthodes de test doivent être préfixés par
test - Tous les tests doivent fonctionner