T: / Corrigés des challenges / PHP
Découvrir Pest PHP, l’alternative à PHPUnit, en écrivant une dizaine de tests unitaires.
Pest est un framework de tests unitaires pour PHP qui propose une syntaxe différente de celle de PHPUnit. En s’inspirant notamment de ce qui existe côté javascript, avec Jest. Dans le moteur, il s’appuie sur PHPUnit, donc il peut être considéré comme une surcouche de PHPUnit. D’ailleurs, lancer la commande « .\vendor\bin\pest » permet de lancer à la fois des tests de Pest ET de PHPUnit (l’inverse n’est pas vrai).
Pest se présente comme un framework « élégant », la syntaxe est en effet plus directe et fonctionne avec l’écriture de fonctions et de closures plutôt qu’en créant des objets comme dans PHPUnit.
Partons à la découverte de ce framework de tests unitaires !
Au programme :
Pour présenter les premières fonctionnalités de Pest, on va s’appuyer sur le challenge débutant COLLECTION_1. Le code complet est disponible sur Github.
Ce challenge propose de déterminer le prix d’une collection de figurine. Selon le nombre d’exemplaires de la figurine, celle-ci vaut 15 ou 30€ à l’achat. A la revente, c’est son prix d’achat, multiplié par une côte.
Figurine.php
<?php
declare(strict_types=1);
namespace Challenges\COLLECTION_1;
final class Figurine
{
private const LIMIT_RARE = 2000;
private const PRICE_BUY_RARE = 30;
private const PRICE_BUY_NOT_RARE = 15;
private int $priceBuy;
public function __construct(
private int $exemplaires,
private float $cote
)
{
$this->calculPriceBuy();
}
private function calculPriceBuy(): void
{
if ($this->exemplaires < self::LIMIT_RARE) {
$this->priceBuy = self::PRICE_BUY_RARE;
return;
}
$this->priceBuy = self::PRICE_BUY_NOT_RARE;
}
public function getPriceBuy(): int
{
return $this->priceBuy;
}
public function getPriceSell(): float
{
return $this->priceBuy * $this->cote;
}
}
Un peu d’explications :
Je vais travailler dans le fichier : ./pests/COLLECTION_1/FigurineTest.php
Les fichiers de tests de Pest doivent être suffixés par Test.php.
Premiers tests, j’instancie mon objet et je fais une assertion :
<?php
declare(strict_types=1);
use Challenges\COLLECTION_1\Figurine;
test('un prix d\'achat à 15€', function() {
$figurine = new Figurine(exemplaires: 5000, cote: 1);
$this->assertEquals(
15,
$figurine->getPriceBuy()
);
});
test('un prix d\'achat à 30€', function() {
$figurine = new Figurine(exemplaires: 500, cote: 1);
$this->assertEquals(
30,
$figurine->getPriceBuy()
);
});
Un peu d’explications :
Et voici la sortie dans la console à l’exécution des tests :
De la même façon, on peut tester le prix de vente :
test('un prix de vente à partir d\'une figurine non rare', function() {
$figurine = new Figurine(exemplaires: 5000, cote: 2.5);
$this->assertEquals(
15 * 2.5,
$figurine->getPriceSell()
);
});
test('un prix de vente à partir d\'une figurine rare', function() {
$figurine = new Figurine(exemplaires: 500, cote: 1.5);
$this->assertEquals(
30 * 1.5,
$figurine->getPriceSell()
);
});
Là j’ai testé 1 seule information à chaque fois. On a la possibilité de passer un lot de données, qui vont valider le même test. Pour le prix d’achat, j’ai vérifié qu’une figurine produite à 5000 exemplaires valait bien 15€. Mais qu’en est il d’une figurine produite à 2000 ? 2001 ? 3000 ? 10000 ? 50000 ? Etc.
On peut écrire un dataprovider à l’aide de la méthode with() :
test('un prix d\'achat à 15€ pour plein de nombres d\'exemplaires', function($exemplaires) {
$figurine = new Figurine(exemplaires: $exemplaires, cote: 1);
$this->assertEquals(
15,
$figurine->getPriceBuy()
);
})->with(
[2000, 2001, 2500, 5000, 10000, 50000]
);
test('un prix d\'achat à 30€ pour plein de nombres d\'exemplaires', function($exemplaires) {
$figurine = new Figurine(exemplaires: $exemplaires, cote: 1);
$this->assertEquals(
30,
$figurine->getPriceBuy()
);
})->with(
[1, 20, 50, 500, 1999]
);
Un peu d’explications :
Pour résoudre le challenge, je crée maintenant une classe « Collection » qui contiendra toutes les figurines.
<?php
declare(strict_types=1);
namespace Challenges\COLLECTION_1;
class Collection
{
/**
* @var array<int, Figurine> $figurines
*/
private array $figurines = [];
/**
* @param array<int, int> $informationsExemplaires
* @param array<int, float> $informationsCotes
*/
public function __construct(array $informationsExemplaires, array $informationsCotes)
{
foreach ($informationsExemplaires as $key => $exemplaires) {
$this->figurines[] = new Figurine(
exemplaires: $exemplaires,
cote: $informationsCotes[$key]
);
}
}
/**
* @return array<int, Figurine>
*/
public function getFigurines(): array
{
return $this->figurines;
}
public function getTotalPriceBuy(): int
{
$total = 0;
foreach ($this->figurines as $figurine) {
$total += $figurine->getPriceBuy();
}
return $total;
}
public function getTotalPriceSell(): float
{
$total = 0;
foreach ($this->figurines as $figurine) {
$total += $figurine->getPriceSell();
}
return $total;
}
public function getTotalDifference(): float
{
return $this->getTotalPriceSell() - $this->getTotalPriceBuy();
}
}
Un peu d’explications :
Pour tester chacune de mes méthodes, et garder 1 seule assertion par test, je vrais créer des informations au début de mon fichier de test, que je vais passer à chacun de mes tests, à l’aide de « use », dans un fichier CollectionTest.php :
<?php
declare(strict_types=1);
use Challenges\COLLECTION_1\Collection;
use Challenges\COLLECTION_1\Figurine;
/**
* Jeux de données
*/
$exemplaires = [50, 5000];
$cotes = [1.5, 2.5];
$collection = new Collection($exemplaires, $cotes);
test('on instancie bien des Figurines', function() use ($collection) {
$this->assertInstanceOf(
Figurine::class,
$collection->getFigurines()[0]
);
});
test('total d\'achat', function() use ($collection) {
$this->assertEquals(
30 + 15,
$collection->getTotalPriceBuy()
);
});
test('total du prix de vente', function() use ($collection) {
$this->assertEquals(
30 * 1.5 + 15 * 2.5,
$collection->getTotalPriceSell()
);
});
Un peu d’explications :
Depuis la page d’un challenge, tu peux récupérer un jeu de données complet, avec la valeur attendue, idéal pour écrire un test !
/**
* Jeux de données complexe issu de Tainix diretement
*/
test('jeu de données complet', function() {
$exemplaires = [50, 50, 50000, 2000, 50000, 2000, 2000, 2000, 50000, 2000, 2000, 50000, 50000, 2000, 2000, 2000, 50000];
$cotes = [2, 8, 1, 0.6, 1, 1.2, 1, 0.6, 0.6, 1, 1, 1, 0.8, 1.2, 1, 1, 0.6];
$collection = new Collection($exemplaires, $cotes);
$this->assertEquals(
219, // Réponse fournie par Tainix
$collection->getTotalDifference()
);
});
Et voilà la sortie complète dans la console :
Voici pour un premier tour d’horizon de Pest PHP ! On a vu :
Voici le lien vers la doc de Pest : https://pestphp.com/
Pour ceux et celles qui voudraient comparer avec PHPUnit, les tests ont aussi été écrits avec PHPUnit. Les fichiers sont disponibles sur Github. L’important n’est pas la librairie utilisée, l’important c’est d’être à l’aise et efficace avec les outils qu’on utilise. Et que son code soit le plus couvert possible par des tests.
Pour aller + loin, n’hésite pas à choisir un challenge de code sur lequel mettre tout ça en pratique ! Ces autres contenus mettent également Pest en avant :
PS : Si tu utilises la sandbox PHP, tu as tout de suite à ta disposition les 2 librairies de tests unitaires : PHPUnit et Pest.
Other content to discover