De PHPStan à Rector en passant par Pint, l'analyse statique n'a jamais été aussi présente et accessible dans nos projets Laravel.
Que ce soit pour détecter des anomalies ou corriger automatiquement des erreurs, ces outils sont devenus ces dernières années des alliés essentiels pour améliorer la qualité de nos projets.
Parmi les plus populaires d'entre eux, PHPStan identifie les bugs de votre application avant qu'ils n'arrivent en production en effectuant des analyses de votre code à l'aide d'un ensemble de règles.
PHPStan offre également la possibilité de créer vos propres règles de validation, voyons dans cet article comment tester unitairement l'une de ces règles personnalisées !
Créer une règle personnalisée
Dans le cadre de ce tutoriel, nous allons créer une règle personnalisée que nous allons par la suite tester unitairement.
Si vous voulez en savoir plus sur le fonctionnement de PHPStan, nous vous conseillons notre article sur comment créer vos propres règles avec le package.
Imaginons que nous souhaitions interdire l'écriture de méthode privée dans notre application, cela est facilement vérifiable avec la règle personnalisée suivante :
1<?php 2 3namespace App\Rules; 4 5use PhpParser\Node; 6use PHPStan\Analyser\Scope; 7use PHPStan\Rules\Rule; 8use PHPStan\Node\InClassMethodNode; 9 10class ClassesHaveNoPrivateMethodsRule implements Rule11{12 public function getNodeType(): string13 {14 return InClassMethodNode::class;15 }16 17 public function processNode(Node $node, Scope $scope): array18 {19 $methodReflection = $node->getMethodReflection();20 21 return $methodReflection->isPrivate()22 ? ['No private method allowed.']23 : [];24 }25}
Une fois la règle écrite, il faut désormais la référencer auprès de PHPStan pour quelle soit prise en compte lors des analyses, pour se faire, il sera nécessaire de créer un fichier de configuration à l'aide de la commande suivante :
1touch phpstan.neon.dist
Dans ce fichier, renseignez les quelques informations suivantes ainsi que le chemin vers votre règle dans la section rules
:
1rules:2 - App\Rules\ClassesHaveNoPrivateMethodsRule34parameters:56 paths:7 - app
Vous pouvez dès lors analyser votre projet à l'aide de votre règle en exécutant la commande :
1vendor/bin/phpstan
Félicitations, vous venez d'analyser votre projet à l'aide d'une règle personnalisée ... cependant, comment s'assurer de son bon fonctionnement ? Peut-être que votre règle comporte des bugs, nous avons peut-être fait des erreurs ?
Il est important de pouvoir s'assurer du bon fonctionnement d'une règle personnalisée avant de l'utiliser en production, voyons désormais ce que propose PHPStan afin de tester unitairement une règle !
Tester unitairement la règle
Tout d'abord, créons le fichier de test associé à notre règle :
1namespace Tests\Unit;2 3use PHPStan\Testing\RuleTestCase;4 5final class ClassesHaveNoPrivateMethodsRuleTest extends RuleTestCase6{7 //8}
Ce fichier de test sera semblable à n'importe quel test unitaire de votre projet Laravel à la seule différence qu'il devra étendre de la classe RuleTestCase
.
Ce testcase est celui fourni par PHPStan et contient un ensemble de méthodes permettant de tester unitairement une règle personnalisée.
Votre fichier de test devra également implémenter une méthode getRule
retournant une instance de la règle que vous souhaitez manipuler :
1 2namespace Tests\Unit; 3 4use PHPStan\Testing\RuleTestCase; 5use PHPStan\Rules\Rule; 6use App\Rules\ClassesHaveNoPrivateMethodsRule; 7 8final class ClassesHaveNoPrivateMethodsRuleTest extends RuleTestCase 9{10 protected function getRule(): Rule 11 { 12 return new ClassesHaveNoPrivateMethodsRule(); 13 } 14}
Maintenant, ajoutons les méthodes correspondant aux différents cas d'utilisation que nous chercherons à valider par la suite.
Notre règle contient deux cas d'utilisation : elle peut soit détecter une méthode privée dans une classe et lever une erreur, soit ne rien faire dans le cas contraire.
1 2namespace Tests; 3 4use PHPStan\Testing\RuleTestCase; 5use PHPUnit\Framework\Attributes\Test; 6use App\Rules\ClassesHaveNoPrivateMethodsRule; 7use PHPStan\Rules\Rule; 8 9final class ClassesHaveNoPrivateMethodsRuleTest extends RuleTestCase10{11 protected function getRule(): Rule12 {13 return new ClassesHaveNoPrivateMethodsRule();14 }15 16 #[Test]17 public function it_not_raise_an_error_if_all_the_methods_are_public(): void 18 { 19 20 } 21 22 #[Test]23 public function it_raises_an_error_if_it_detects_a_private_method(): void 24 { 25 26 } 27}
Une règle PHPStan vérifie du code en le lisant, le test unitaire n'y fait pas exception ... pour répondre à cette contrainte, nous pouvons écrire des stubs contenant le code que nous allons par la suite soumettre à nos cas de test :
1touch tests/stubs/ClassWithPublicMethod.php2touch tests/stubs/ClassWithPrivateMethod.php
Le stub est un pattern faisant partie de l'attirail des doublures au même titre que les mocks, les spies ou bien encore les dummies.
Dans notre cas, un stub est un fichier écrit en dur, ne possédant pas de logique métier, permettant de simuler un objet répondant à un cas d'utilisation.
Le premier de ces stubs sera une classe sans méthode privée :
1class ClassWithPublicMethod2{3 public function foo()4 {5 //6 }7}
Tandis que le second stub sera une classe contenant une méthode privée :
1class ClassWithPrivateMethod2{3 private function bar()4 {5 //6 }7}
Maintenant que nous possédons nos stubs, nous pouvons compléter les différentes méthodes de test.
Le testcase
de PHPStan offre plusieurs méthodes permettant de simuler le lancement d'une analyse.
La première de ces méthodes, gatherAnalyserErrors
, reçoit en argument le chemin des fichiers à tester, dans notre cas, le stub ClassWithPublicMethod
et retournera un array
contenant les erreurs identifiées par la règle :
1 2namespace Tests; 3 4use PHPStan\Testing\RuleTestCase; 5use PHPUnit\Framework\Attributes\Test; 6use App\Rules\ClassesHaveNoPrivateMethodsRule; 7use PHPStan\Rules\Rule; 8 9final class ClassesHaveNoPrivateMethodsRuleTest extends RuleTestCase10{11 protected function getRule(): Rule12 {13 return new ClassesHaveNoPrivateMethodsRule();14 }15 16 #[Test]17 public function it_not_raise_an_error_if_all_the_methods_are_public(): void18 {19 $errors = $this->gatherAnalyserErrors([ 20 __DIR__ . '/stubs/ClassWithPublicMethod.php' 21 ]); 22 23 $this->assertCount(0, $errors); 24 }25 26 #[Test]27 public function it_raises_an_error_if_it_detects_a_private_method(): void28 {29 //30 }31}
PHPUnit est disponible afin de valider des assertions !
La seconde méthode proposée par PHPStan, analyse
, reçoit également en premier argument les fichiers à analyser ainsi qu'un tableau en second argument contenant les erreurs que la règle doit détecter !
1 2namespace Tests; 3 4use PHPStan\Testing\RuleTestCase; 5use PHPUnit\Framework\Attributes\Test; 6use App\Rules\ClassesHaveNoPrivateMethodsRule; 7use PHPStan\Rules\Rule; 8 9final class ClassesHaveNoPrivateMethodsRuleTest extends RuleTestCase10{11 protected function getRule(): Rule12 {13 return new ClassesHaveNoPrivateMethodsRule();14 }15 16 #[Test]17 public function it_not_raise_an_error_if_all_the_methods_are_public(): void18 {19 $errors = $this->gatherAnalyserErrors([20 __DIR__ . '/stubs/ClassWithPublicMethod.php'21 ]);22 23 $this->assertCount(0, $errors);24 }25 26 #[Test]27 public function it_raises_an_error_if_it_detects_a_private_method(): void28 {29 $this->analyse( 30 [__DIR__ . '/stubs/ClassWithPrivateMethod.php'],31 expectedErrors: [32 [33 'No private method allowed.',34 10,35 ],36 ],37 );38 }39}
L'argument expectedErrors
est un tableau multidimensionnel où la première valeur de chaque ligne correspond au message d'erreur et la seconde valeur correspond à la ligne correspondante dans le fichier analysé.
Maintenant que notre fichier de test est complet et opérationnel, lancez vos tests unitaires à l'aide de la commande suivante :
1vendor/bin/phpunit tests/Unit/ClassesHaveNoPrivateMethodsRuleTest.php
Félicitation, vous savez désormais comment tester unitairement des règles PHPStan !
A lire
Autres articles de la même catégorie
Conteneuriser son application Laravel avec Docker
Création du Dockerfile de notre application Laravel
William Suppo
Améliorer ses requêtes en base de données
L'ORM de Laravel est un outil remarquable. Mais tout n'est pas forcément dans la doc : connaissiez-vous ces astuces ?
Laravel Jutsu
Pourquoi Vite ne build pas ?
Un retour d'experience douloureux mais formateur sur le fonctionnement de Vite !
Mathieu De Gracia