Archives Wild Code School
PHP Avancé

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

Quête 13 sur 19 · 2 images archivées

Les robots au boulot

title-book
 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...

alt

... 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
<?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
<?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.

bash
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 :

  1. 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 classe TestCase :
php
<?php

include 'functions.php';

use PHPUnit\Framework\TestCase;

class SimpleTest extends TestCase
{
}
  1. Ensuite on déclare une fonction publique qui ne retourne rien et qui hébergera le code de notre test automatisé.
Attention

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
<?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 😤.

  1. 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
<?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 :-("
        );
    }
}
  1. 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
<?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 :-("
        );
    }
}
  1. 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
<?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 :

bash
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 :

php
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 :

bash
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 :

bash
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 fichier Calculator, crée dans la même classe une méthode multiplication() 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 retourne true si il est pair ou false si 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 CalculatorTest avec 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