T: / Corrigés des challenges / PHP
Découvrir PHPUnit en écrivant 4 premiers tests.
Les tests unitaires sont une pierre angulaire du développement web. Ils permettent d’assurer que chaque « unité » de code fonctionne comme prévu et que des modifications réalisées dans le code n’introduisent pas de bug inattendus. Si tu n’es pas familier du concept, je ne peux que te conseiller notre article : introduction aux tests unitaires, sans frameworks. Si c’est OK, alors continue 🙂
En PHP, l’un des outils les plus reconnus pour cette tâche est PHPUnit. C’est un framework de tests qui facilite la création et l’exécution des tests pour ton code PHP. Dans cet article, nous allons le découvrir en écrivant de premiers tests. PHPUnit deviendra alors un allié inestimable dans ta quête d’un code robuste et fiable !
Au programme :
Ce corrigé s’appuie sur l’organisation du code proposé dans la Sandbox PHP. De cette manière, les dossiers, fichiers et namespaces nécessaires sont prêts à être utilisés.
Je travaille ici à 2 endroits :
Dans ce challenge, il est question de trouver le coup correspondant à celui de l’adversaire, puis de retourner la liste complète des coups à jouer pour battre l’adversaire à chaque coup. Je décide donc de créer 2 méthodes :
Dans le dossier /challenges/PIERRE_FEUILLE_CISEAUX je crée un objet PFCGame dans un fichier PFCGame.php qui contiendra ces méthodes :
namespace Challenges\PIERRE_FEUILLE_CISEAUX;
final class PFCGame
{
public function play(string $play): string
{
return '';
}
public function party(string $plays): string
{
return '';
}
}
La structure de mes méthodes est prête.
Je peux écrire mes tests dès maintenant, pas besoin de coder les méthodes play et party tout de suite.
Dans le fichier Pierre_feuille_ciseauxTest.php, je commence par faire 2 choses :
use PHPUnit\Framework\TestCase;
use Challenges\PIERRE_FEUILLE_CISEAUX\PFCGame;
final class Pierre_feuille_ciseauxTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
$this->PFCGame = new PFCGame;
}
}
Pour tester le bon fonctionnement de ma méthode play, je dois vérifier 3 choses :
Je peux donc écrire ces 3 tests :
public function test_Feuille_donne_Ciseaux(): void
{
$this->assertEquals(
$this->PFCGame->play('F'),
'C'
);
}
public function test_Ciseaux_donne_Pierre(): void
{
$this->assertEquals(
$this->PFCGame->play('C'),
'P'
);
}
public function test_Pierre_donne_Feuille(): void
{
$this->assertEquals(
$this->PFCGame->play('P'),
'F'
);
}
Et voilà ! 3 premiers tests écrits avec PHPUnit !
Dans chaque test, j’utilise la méthode de PHPUnit « assertEquals » qui pourrait se traduire en « vérifie que c’est égal ». Je vérifie donc que le retour de ma fonction play quand le paramètre est « F » est égal à « C » (pour le premier test).
Maintenant, je lance mes tests avec ma console : (rappel de la commande, à la racine du projet : ./bin/vendor/phpunit –testsuite PHPUnit )
Forcément, chaque test est faux puisque la fonction play n’est pas encore codée !
On a vu dans un précédent article qu’on peut résoudre ce challenge en le codant de 11 façons différentes. En voici une :
final class PFCGame
{
public function play(string $play): string
{
if ($play === 'P') {
return 'F';
}
if ($play === 'F') {
return 'C';
}
if ($play === 'C') {
return 'P';
}
}
public function party(string $plays): string
{
return '';
}
}
Si je relance mes tests :
Comme mes tests verrouillent le bon fonctionnement de ma méthode, je peux en profiter pour faire un peu de refactoring, c’est-à-dire réécrire mon code. Ici, je vais remplacer mes chaînes de caractères par des constantes de classe. Cela me permet d’avoir quelque chose d’encore plus lisible et de sécuriser mon code. Si je tape « CC » au lieu de « C » par inadvertance, mon code ne renvoie pas d’erreur. Alors que si j’écris self::CISEAU (manque le X) alors mon code renverra une erreur.
final class PFCGame
{
private const PIERRE = 'P';
private const FEUILLE = 'F';
private const CISEAUX = 'C';
public function play(string $play): string
{
if ($play === self::PIERRE) {
return self::FEUILLE;
}
if ($play === self::FEUILLE) {
return self::CISEAUX;
}
if ($play === self::CISEAUX) {
return self::PIERRE;
}
}
public function party(string $plays): string
{
return '';
}
}
Si on relance les tests, on a toujours du vert, donc tout est OK !
Libre à toi de continuer à refactoriser ce code 😉 je crois qu’on peut faire mieux que ces 3 if…
Il reste à écrire le test pour la méthode party. Celle-ci va donc devoir renvoyer plusieurs coups.
Voici comment on peut écrire ce test :
public function test_partie_3_coups(): void
{
$this->assertEquals(
$this->PFCGame->party('PFC'),
'FCP'
);
}
Je vérifie que le résultat de la méthode « party » lorsqu’elle prend en paramètre « PFC » est égal à « FCP ».
Si je relance mes tests :
Quelques explications :
Voici donc une version possible pour la méthode party :
public function party(string $plays): string
{
$response = '';
$nbPlays = strlen($plays) - 1;
for ($i = 0; $i <= $nbPlays; $i++) {
$response .= $this->play($plays[$i]);
}
return $response;
}
Et si je relance mes tests :
Et voilà ! 4 tests d’écrits et validés avec PHPUnit !
Là on a testé le « good path », « le bon chemin », c’est-à-dire qu’on a testé que tout ce qui devait bien se passer se passe bien. C’est toujours par là qu’il faut commencer.
Pour aller + loin, on peut tester les cas limites, passer de mauvais paramètres, etc. c’est-à-dire le « wrong path », « le mauvais » chemin et donc vérifier que ce qui ne doit pas se passer ne se passe pas. Qu’est ce qui se passe si je passe une chaîne vide à la méthode play, si je passe un autre caractère que « P », « F » ou « C », etc.
On aurait pu aussi faire + de tests pour la méthode party, avec 1, 2, 3, 5, 10, 20 coups.
Pour continuer dans ce sujet des tests unitaires, tu peux découvrir Les tests unitaires avec PHP #2.
On a bien codé ! Pour aller au bout, autant valider sa progression dans Tainix en se servant de ce code.
Dans le fichier pierre_feuille_ciseaux_api.php :
// CODE DU CHALLENGE ------------------
$output = (new PFCGame)->party($coups);
// REPONSE ----------------------------
$game->output(['data' => $output]);
Le code est également disponible sur Github.
PFCGame.php
declare(strict_types=1);
namespace Challenges\PIERRE_FEUILLE_CISEAUX;
final class PFCGame
{
private const PIERRE = 'P';
private const FEUILLE = 'F';
private const CISEAUX = 'C';
public function play(string $play): string
{
if ($play === self::PIERRE) {
return self::FEUILLE;
}
if ($play === self::FEUILLE) {
return self::CISEAUX;
}
if ($play === self::CISEAUX) {
return self::PIERRE;
}
}
public function party(string $plays): string
{
$response = '';
$nbPlays = strlen($plays) - 1;
for ($i = 0; $i <= $nbPlays; $i++) {
$response .= $this->play($plays[$i]);
}
return $response;
}
}
Pierre_feuille_ciseauxTest.php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Challenges\PIERRE_FEUILLE_CISEAUX\PFCGame;
final class Pierre_feuille_ciseauxTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
$this->PFCGame = new PFCGame;
}
public function test_Feuille_donne_Ciseaux(): void
{
$this->assertEquals(
$this->PFCGame->play('F'),
'C'
);
}
public function test_Ciseaux_donne_Pierre(): void
{
$this->assertEquals(
$this->PFCGame->play('C'),
'P'
);
}
public function test_Pierre_donne_Feuille(): void
{
$this->assertEquals(
$this->PFCGame->play('P'),
'F'
);
}
public function test_partie_3_coups(): void
{
$this->assertEquals(
$this->PFCGame->party('PFC'),
'FCP'
);
}
}
Other content to discover