1. Installation de PHPUnit

PHPUnit est une librairie PHP permettant d’écrire des tests unitaires et fonctionnels et de les exécuter.

1.1 Composer

Il existe diverses façons d’installer PHPUnit, mais la plus simple est de l’installer via composer. Puisque PHPUnit ne doit pas être mis à disposition d’un environnement de production, il est recommandé de l’installer en tant que dépendance de développement (en précisant l’option --dev).

composer require --dev phpunit/phpunit

# Ou à partir du script mis à disposition
bin/dev/composer require --dev phpunit/phpunit

PHPUnit a été installé avec la version ^10.5, la dernière disponible à ce jour.

Pour exécuter PHPUnit, il faut utiliser le binaire phpunit mis à disposition par composer, dans vendor/bin/.

vendor/bin/phpunit

# Ou à partir du script mis à disposition
bin/dev/phpunit

PHPUnit est maintenant installé et prêt à être utilisé et vous allez pouvoir commencer à écrire vos premiers tests.

1.2 Configuration du namespace

Puisqu’il s’agit d’un projet composer, vous devez paramétrer vos namespaces dans le fichier composer.json.

C’est le cas pour le code source de l’application définie dans le dossier src/ :

{
  "autoload": {
    "psr-4": {
      "TroisOlen\\PhpunitTp\\": "src/"
    }
  }
}

Il faut faire de même pour les tests unitaires, en ajoutant un nouveau namespace :

{
  "autoload": {
    "psr-4": {
      "TroisOlen\\PhpunitTp\\": "src/",
      "TroisOlen\\PhpunitTp\\Tests\\": "tests/"
    }
  }
}

1.3 Commit

N’oubliez pas de commiter vos changements, en l’occurrence l’ajout de PHPUnit comme dépendance de développement dans le fichier composer.json et le nouveau namespace pour les tests unitaires.

ℹ️ Vous êtes libre de gérer vos commits comme vous l’entendez, mais gardez quand même de bonnes pratiques et une méthodologie similaire à celle que vous utiliserez sur votre projet transverse.

2. Premières assertions

Les tests sont généralement écrits dans le dossier tests/ à la racine du projet.

Avec PHPUnit, les tests sont écrits dans des classes qui héritent de la classe TestCase. PHPUnit va alors rechercher les méthodes de cette classe qui commencent par test et les exécuter.

2.1 Tester un DTO

Plusieurs DTO sont mis à disposition. Les DTO sont des objets qui représentent des données applicatives et qui sont immuables, c’est-à-dire qu’ils ne peuvent pas être modifiés après leur création. C’est pourquoi on appelle ces objets des “transferts de données” (Data Transfer Object).

Les tests unitaires à propos d’un DTO sont généralement assez simples : on vérifie que les données passées au constructeur sont bien celles qui sont retournées par les accesseurs.

Arborescence des tests

Généralement dans un projet PHP, vous trouverez dans un dossier Unit/ dans le dossier des tests où sont regroupées toutes les classes définissant les tests unitaires du projet.

Vous pourrez alors trouver également les dossiers Integration/ ou Functional/ dans le dossier tests/, donc au même niveau que Unit/, pour respectivement les tests d’intégration et les tests fonctionnels.

De cette manière, vous organisez la nature de vos tests et vous pouvez les exécuter par groupe selon vos besoins.

Enfin, au sein de ces dossiers, il est recommandé de garder la même arborescence définie dans le dossier src/ pour faciliter la recherche des tests.

Ainsi, la classe MediaDto du dossier src/Model/ aura son test unitaire dans le dossier tests/Unit/Model/ par le biais de la classe MediaDtoTest.

Bonne pratique

Au fur et à mesure de vos utilisations des librairies de tests, vous vous rendrez compte qu’on peut faire énormément de tests et écrire des centaines de lignes pour tester une méthode qui n’en fait que 10.

Vous aurez alors rapidement des classes de tests avec des milliers de lignes de code, ce qui compliquera la lecture et la maintenance de ces tests.

J’ai pour pratique de créer des classes de TU pour chaque méthode testée, au lieu d’avoir une seule classe TU pour la classe testée. Je trouve que c’est plus facile de s’y retrouver et de savoir exactement ce qui est testé.

Dans le cas de notre projet, j’aurais alors une classe ConstructTest située dans l’arborescence tests/Unit/Model/MediaDto/ pour tester le constructeur de la classe MediaDto.

Faites comme bon vous semble, ce n’est qu’une simple suggestion.

2.1.1 Création de la classe de test

Créez la classe MediaDtoTest dans le dossier tests/Unit/Model/ et faites-la hériter de la classe TestCase :

<?php

declare(strict_types=1);

namespace TroisOlen\PhpunitTp\Tests\Unit\Model;

use PHPUnit\Framework\TestCase;

final class MediaDtoTest extends TestCase
{
    public function testTrue(): void
    {
        static::assertTrue(true);
    }
}

Cette classe de test contient une méthode à tester avec une assertion qui vérifie que la valeur passée en paramètre est bien true.

2.1.2 Exécution du test

Vu que le test écrit teste que true est bien true, il devrait passer sans problème.

Exécutez le test avec PHPUnit :

vendor/bin/phpunit tests/

# Ou à partir du script mis à disposition
bin/dev/phpunit tests/

PHPUnit devrait alors vous retourner un rapport d’exécution en indiquant le nombre de tests exécutés, le nombre d’assertions effectuées, le nombre et le pourcentage de réussite, ainsi que le temps d’exécution et le pic d’utilisation de la mémoire.

Vous pouvez obtenir un rapport plus détaillé en ajoutant l’option --testdox.

Vous pouvez aussi définir le path à partir duquel PHPUnit doit rechercher les tests à exécuter si vous ne voulez exécuter que les tests d’un composant ou d’une classe : phpunit tests/Unit/Model/MediaDtoTest.php, par exemple.

2.1.3 Échec du test

Modifiez la méthode de test pour qu’elle vérifie que true est bien false :

public function testTrue(): void
{
    static::assertTrue(false);
}

Exécutez à nouveau le test et vous devriez obtenir un rapport d’exécution avec un test en échec.

2.1.4 Tester le constructeur

Supprimez votre dummy test et créez une méthode de test pour tester le constructeur de la classe MediaDto en utilisant l’assertion static::assertEquals().

Dans votre rapport, il devrait y avoir :

  • 1 test exécuté (réussite 100%).
  • 4 assertions effectuées.

2.2 Tester une classe Factory

L’application met à disposition plusieurs classes pour divers besoins. Parmi ces classes, il y a des Factory (basées sur le design pattern éponyme) qui permettent de créer des objets. La classe la plus simple que l’on va tester est MediaTypeEnumFactory qui permet de retourner une valeur de l’enum MediaTypeEnum à partir d’une chaîne de caractères.

Cette méthode est utilisée notamment par le DataProvider qui génère des MediaDto à partir des données du fichier JSON, contenant une vingtaine d’exemples d’énigmes.

  1. Créez la classe de test et la méthode permettant de tester la méthode getFromConverter() de la classe MediaTypeEnumFactory.
  2. Créez des assertions correspondant aux deux valeurs de l’enum MediaTypeEnum : MediaTypeEnum::MOVIE et MediaTypeEnum::SERIES.
  3. Exécutez le test et vérifiez que les assertions sont bien effectuées.
    Vous devriez avoir au moins 2 tests exécutés (réussite 100%) et 6 assertions effectuées.

Dans le code de la méthode getFromConverter(), vous pouvez voir que la méthode lève une exception si la valeur passée en paramètre n’est pas reconnue.

Ajoutez ce qu’il faut avec l’assertion qui convient pour tester le fait qu’une valeur non reconnue lève bien l’exception indiquée.

Pour la suite…

Vous avez vu les assertions de base, mais PHPUnit en propose beaucoup d’autres comme les valeurs booléennes, ou des comparaisons numériques, ou des types de variables, etc.

L’un des avantages des tests unitaires codés est de pouvoir utiliser le même test pour plusieurs cas de figure, autrement dit pour plusieurs jeux de données.