PEST est un nouveau framework en PHP, permettant de rédiger des tests unitaires.
Basé sur le célèbre (pour ceux qui développent en PHP) PHUnit, PEST a pour lui d’être plus élégant et simple à utiliser, et apporte notamment une plus grande fluidité dans l’écriture des tests unitaires.
Il a été créé par Nuno Maduro, membre de la core team de Laravel, en sponsorware. Depuis le printemps 2020, il est publié sous licence MIT. Il ne possède aucune filiation avec Laravel, on peut tout à fait s’en servir sans.
Petit tour d’horizon !
(tous les exemples de code de cette dépêche proviennent de la documentation officielle de PEST)
Sommaire
Premiers tests
Pour commencer, voyons à quoi ressemble un test basique, avec PEST.
<?php
test('asserts true is true', function () {
$this->assertTrue(true);
expect(true)->toBeTrue();
});
Contrairement à PHPUnit, qui regroupe chaque test dans une méthode de classe, chaque test dans PEST consiste à appeler la fonction test
en lui passant 2 paramètres : la description du test (texte libre) et une fonction anonyme (Closure) contenant le code du test proprement dit. Le tout dans un simple fichier PHP (pas de classe, pas de namespace, rien de tout ça n’est nécessaire).
Comme PEST repose sur PHPUnit, on peut utiliser les méthodes assertXXX
déjà existantes et connues de tous, à travers $this
(qui est un objet de type PHPUnit\Framework\TestCase
, ce qui montre que PEST est une surcouche à PHPUnit, avec du sucre syntaxique dedans).
Mais on peut aussi utiliser les nouvelles méthodes toBeXXX
mises à notre disposition par PEST, et dont on parlera plus loin.
Si le test ci-dessus réussit, PEST affichera :
✓ asserts true is true
À noter que la fonction test
a une petite sœur : it
(même signature). La seule différence est que cette dernière affichera ceci, si le test réussit :
✓ it asserts true is true
Expectations
Bien que PEST puisse s’utiliser comme PHPUnit (ce qui permet doucement de basculer d’un framework à l’autre), on peut bien évidemment aller plus loin, en tirant profit de sa spécificité : les expectations.
Concrètement, cela consiste en une méthode expect
, à qui on passe la variable à tester, et toute une série de méthodes fluides (fluent interfaces) qui vont chacune vérifier que la variable respecte telle ou telle règle.
Reprenons l’exemple du début :
<?php
test('expect true to be true', function () {
// assertion
$this->assertTrue(true);
// expectation
expect(true)->toBe(true);
});
On voit que la méthode toBe
vérifie que la valeur true
(passée en paramètre de la fonction expect
) est bien égale à true
.
Voyons maintenant un cas un peu plus poussé (et plus proche de ce qu’on peut être amené à écrire, dans la vraie vie) :
expect($user)->toBeInstanceOf(User::class);
Ici, la méthode toBeInstanceOf
vérifie que la variable $user
est bien de type User
.
On ne va pas lister ici la palanquée de méthodes toBeXXX
qui existent : la documentation est là pour ça, avec un exemple pour chacune d’elles.
Rappelons toutefois qu’il est tout à fait possible de chaîner les appels de méthodes, pour tester plusieurs choses sur une même valeur.
Exemple :
expect('{"name":"Nuno","credit":1000.00}')
->json()
->toHaveCount(2);
Higher Order Tests
Avec le chaînage des méthodes vient aussi une autre possibilité de PEST : pouvoir tester les valeurs au sein de la variable passée à la fonction expect
. Ainsi, plutôt que de faire plusieurs appels à expect
(ce qui reste tout à fait possible), comme suit :
expect($user->first_name)->toEqual('Nuno');
expect($user->last_name)->toEqual('Maduro');
expect($user->withTitle('Mr'))->toEqual('Mr Nuno Maduro');
On peut tout à fait faire ceci :
expect($user)
->first_name->toEqual('Nuno')
->last_name->toEqual('Maduro')
->withTitle('Mr')->toEqual('Mr Nuno Maduro');
Et là, on commence à toucher à ce qui rend PEST si attrayant : le chaînage est sans limite ! On peut tester toutes les données contenues à l’intérieur de la valeur passée en paramètre à expect
, que ce soit les propriétés d’un objet, comme on vient de le voir, ou les différents éléments d’un tableau, comme ci-dessous :
expect(['name' => 'Nuno', 'companies' => ['Pest', 'Laravel']])
->name->toEqual('Nuno')
->companies->toHaveCount(2)->each->toBeString
(notez l’usage de each
, qui permet de boucler sur les sous-éléments du tableau)
Comme dit précédemment, il n’y a pas de limite dans le chaînage, ni dans le parcours en profondeur d’un objet ou d’un tableau.
Ainsi, il est tout à fait possible d’aller chercher les propriétés d’un objet, et de tester qu’elles répondent elles aussi à certains critères.
Exemple :
expect($user)
->companies->first()->owner->toBeInstanceOf(User::class)->not->toEqual($user)
->name->toEqual('Nuno');
Dans cet exemple, on comprend que l’objet $user
a 2 propriétés testées : companies
(qui semble renvoyer une liste d’objets), dont on vérifie que le owner
du premier élément est bien un objet de type User
, mais n’est pas $user
. Et name
, dont on vérifie la valeur. Ici, name
se rapporte à $user
, et non à companies
: PEST est capable de revenir à l’objet initialement traité (celui passé en paramètre à expect
). Et ce, sans limite !
Custom expectations
La liste des expectations a beau être très grande, et de nouvelles méthodes apparaissent chaque semaine, il y a toujours un moment où il manque celle dont on a besoin, pour couvrir son cas particulier.
Fort heureusement, PEST permet l’ajout de nouvelles méthodes, grâce à la méthode extend()
.
Exemple :
// tests/Pest.php
expect()->extend('toBeWithinRange', function ($min, $max) {
return $this->toBeGreaterThanOrEqual($min)
->toBeLessThanOrEqual($max);
});
test('numeric ranges', function () {
expect(100)->toBeWithinRange(90, 110);
});
Aller plus loin
- Site officiel (138 clics)
- Dépôt officiel (github) (21 clics)
- Licence MIT (12 clics)
- Nuno Maduro créateur du projet (16 clics)
- PEST in practice (série de vidéos) (15 clics)
# Yes!
Posté par Lol Zimmerli (site web personnel, Mastodon) . Évalué à 3.
Merci pour la découverte!
La gelée de coings est une chose à ne pas avaler de travers.
[^] # Re: Yes!
Posté par Christophe "CHiPs" PETIT (site web personnel) . Évalué à 1.
On va regarder ça aussi au boulot !
# intéressant
Posté par Fab'Blab (site web personnel) . Évalué à 2. Dernière modification le 02 décembre 2021 à 09:27.
Merci de l'infos.
Cela semble une très bonne alternative à PHPUnit.
On leur pardonnerait même leur logo qui pique les yeux :)
# PEST est capable de revenir à l’objet initialement traité
Posté par oliverpool (site web personnel) . Évalué à 1.
Merci pour cette dépêche très intéressante et qui donne envie d'aller tester soit-même !
Question bête concernant cet exemple:
Comment est-ce PEST sait qu'on veut faire le dernier test sur
$user
et non surowner
?Formulé autrement, comment est-ce que je pourrais faire un test supplémentaire sur
owner
(pour vérifier son nom par exemple).[^] # Re: PEST est capable de revenir à l’objet initialement traité
Posté par azerttyu (site web personnel) . Évalué à 2.
Hello
De ce que je lis, dans les exemples, les méthodes toXYZ() retournent l'objet sur lequel on travaille (celui défini dans expect()). La logique de chainage fait alors qu'on peut continuer les tests sur les autres champs de l'objet initial comme name.
Cela me fait penser à d'autres framework qui aiment bien le chainage comme jQuery.
[^] # Re: PEST est capable de revenir à l’objet initialement traité
Posté par oliverpool (site web personnel) . Évalué à 1. Dernière modification le 03 décembre 2021 à 14:30.
Mais du coup le
->not->toEqual($user)
étant précédé d'untoBeInstanceOf
, il s'appliqueexpect($user)
?J'ai retrouvé la doc officielle d'où cet exemple est tiré : https://pestphp.com/docs/expectations#higher-order-expectations
J'ai demandé sur github pour vérifier tout ça : https://github.com/pestphp/pest/issues/447
[^] # Re: PEST est capable de revenir à l’objet initialement traité
Posté par oliverpool (site web personnel) . Évalué à 3.
Réponse de https://github.com/pestphp/pest/issues/447
En fait on peut faire plusieurs expectation à la suite (
toBeInstanceOf
,not
,toEqual
).L'objet initial sera rétabli sur la prochaine méthode qui ne soit pas une expectation (
->name
en l'occurence).Donc l'exemple fonctionne bien, j'avais juste compris de travers :-)
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.