Aller au contenu principal

PHPUnit

Vérifier automatiquement que son code fonctionne, sans avoir à tout retester à la main

Notions théoriques

Qu'est-ce que PHPUnit ?

PHPUnit est le framework de tests automatisés le plus utilisé pour le langage PHP. Il permet d'écrire du code qui vérifie que votre code fonctionne comme prévu, sans avoir besoin de le tester manuellement à chaque modification.

Pourquoi tester son code automatiquement ?

  • Détecter les régressions : quand vous modifiez une classe, vous savez immédiatement si vous avez cassé un comportement qui fonctionnait avant.
  • Documenter le comportement attendu : un test bien écrit montre clairement ce qu'une méthode est censée faire, pour n'importe quelle valeur entrée.
  • Gagner du temps : plus besoin de relancer manuellement l'application et de vérifier à l'œil chaque scénario après chaque changement.
  • Donner confiance avant de livrer une nouvelle version du programme.

Installation via Composer

PHPUnit s'installe comme dépendance de développement, avec Composer :

composer require --dev phpunit/phpunit
info

L'option --dev indique que PHPUnit n'est utile que pendant le développement (pour lancer les tests), pas en production. Il est ajouté dans la section require-dev du fichier composer.json.

Anatomie d'un test

Un test PHPUnit est écrit dans une classe qui étend PHPUnit\Framework\TestCase. Chaque méthode dont le nom commence par test est exécutée automatiquement comme un test indépendant.

<?php
use PHPUnit\Framework\TestCase;

class CalculatriceTest extends TestCase {
public function testAdditionne() {
$resultat = 2 + 2;
$this->assertEquals(4, $resultat);
}
}

Ici, testAdditionne() est un test : il vérifie que 2 + 2 vaut bien 4, grâce à une assertion.

Les assertions courantes

Une assertion vérifie qu'une condition est vraie ; si ce n'est pas le cas, le test échoue et PHPUnit affiche précisément où et pourquoi.

AssertionVérifie que...
assertEquals($attendu, $obtenu)$attendu et $obtenu sont égaux (valeur)
assertSame($attendu, $obtenu)$attendu et $obtenu sont strictement identiques (valeur et type)
assertTrue($condition)$condition vaut true
assertFalse($condition)$condition vaut false
assertNull($valeur)$valeur vaut null
assertCount($nombre, $tableau)$tableau contient exactement $nombre éléments

Exécuter les tests

Une fois PHPUnit installé, on lance tous les tests du projet avec :

vendor/bin/phpunit tests

PHPUnit affiche un point . pour chaque test réussi, un F pour chaque test en échec, puis un résumé.

Exemple de mise en application

Reprenons notre jeu de combat. Le projet TP2_phpunit contient déjà une classe Character et une classe Fight, organisées avec Composer et l'autoload PSR-4 :

{
"name": "bouquet/tp-tests-unitaires",
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"require-dev": {
"phpunit/phpunit": "10"
}
}
<?php
namespace App;

class Character {
public $name;
public $health;
public $strength;

public function __construct($name, $health, $strength) {
$this->name = $name;
$this->health = $health;
$this->strength = $strength;
}
}
<?php
namespace App;

class Fight {
public $character1;
public $character2;

public function __construct(Character $character1, Character $character2) {
$this->character1 = $character1;
$this->character2 = $character2;
}

public function start() {
if ($this->character1->strength > $this->character2->strength) {
return $this->character1;
} else if ($this->character1->strength < $this->character2->strength) {
return $this->character2;
} else {
return null; // Il n'y a pas de vainqueur, donc on renvoie null
}
}
}

Le fichier tests/FightTest.php vérifie deux comportements : que le combat retient bien les deux personnages passés au constructeur, et que le personnage le plus fort remporte le combat :

<?php
use App\Fight;
use App\Character;
use PHPUnit\Framework\TestCase;

class FightTest extends TestCase {
public function testConstructor() {
$character1 = new Character('Guerrier', 100, 20);
$character2 = new Character('Monstre', 80, 15);
$fight = new Fight($character1, $character2);

$this->assertSame($character1, $fight->character1);
$this->assertSame($character2, $fight->character2);
}

public function testStart() {
$character1 = new Character("John", 100, 50);
$character2 = new Character("Jane", 100, 60);
$fight = new Fight($character1, $character2);

$winner = $fight->start();

$this->assertSame($character2, $winner);
}
}

En lançant vendor/bin/phpunit tests, ces deux tests s'exécutent et confirment que Fight se comporte comme prévu, sans qu'il soit nécessaire d'écrire une seule ligne de code pour les tester manuellement.

Test de mémorisation/compréhension


À quoi sert PHPUnit ?


Comment installe-t-on PHPUnit dans un projet ?


De quelle classe une classe de test PHPUnit doit-elle hériter ?


Comment PHPUnit reconnaît-il qu'une méthode est un test à exécuter ?


Que fait `assertEquals(4, $resultat)` ?


Quelle commande lance les tests d'un projet PHPUnit installé via Composer ?


Pourquoi installe-t-on PHPUnit avec l'option '--dev' ?


Quel est l'intérêt principal des tests automatisés lors d'une modification de code ?



TP pour réfléchir et résoudre des problèmes

Le projet TP2_phpunit gère un combat entre deux personnages, mais aucune attaque ne fait réellement perdre de points de vie. Nous allons ajouter une méthode attack() à la classe Character, puis écrire des tests PHPUnit pour vérifier son comportement.

Étape 1 — Installer PHPUnit dans le projet

Dans le dossier TP2_phpunit, installez PHPUnit comme dépendance de développement.


Bonne pratique - Séparer dépendances et outils de test

Toujours installer PHPUnit avec --dev : cela évite d'alourdir inutilement le déploiement en production avec des outils qui ne servent qu'aux développeurs.

Étape 2 — Ajouter la méthode attack() à Character

Ajoutez à la classe Character une méthode attack(Character $target) qui réduit les points de vie (health) de la cible $target de la valeur de la force (strength) de l'attaquant. Les points de vie ne doivent jamais descendre en dessous de 0.


Bonne pratique - Protéger les valeurs limites

Vérifiez toujours les valeurs limites (ici, ne jamais descendre sous 0 point de vie). Sans cette protection, un personnage pourrait avoir des points de vie négatifs, ce qui n'a pas de sens dans le jeu et compliquerait la détection de la défaite.

Étape 3 — Tester qu'une attaque réduit les points de vie

Ajoutez dans tests/FightTest.php un test testAttackReducesHealth qui vérifie qu'après une attaque, les points de vie de la cible ont bien diminué du montant attendu.


Bonne pratique - Un test, un comportement

Un test doit vérifier un seul comportement précis (ici, la perte de points de vie), avec des valeurs simples à recalculer de tête. Cela rend l'échec du test immédiatement compréhensible, sans avoir à déchiffrer un scénario complexe.

Étape 4 — Tester la défaite à 0 point de vie

Ajoutez un second test testCharacterDefeatedWhenHealthReachesZero qui vérifie qu'un personnage attaqué avec une force supérieure à ses points de vie restants tombe bien exactement à 0 (et jamais en dessous).


Bonne pratique - Tester aussi les cas limites

Ne testez pas uniquement le cas "normal" (une attaque qui réduit sans achever). Testez aussi le cas limite où l'attaque devrait faire passer les points de vie sous 0 : c'est souvent à ces frontières que les bugs se cachent.

📌 Une solution