Référentiel pour mon atelier au WordCamp Catania 2019
Facultatif, mais vous devrez peut-être installer Docker :
sudo apt-get install docker-ce docker-ce-cli containerd.io
Je suppose que Composer est installé. Installons d'abord PHPUnit :
composer require --dev phpunit/phpunit ^8.3
S'il vous plaît, vérifiez également les exigences !
PHPUnit 8.3 nécessite au moins PHP 7.2 ! À propos, le support de sécurité pour PHP 7.1 prend fin le 1er décembre 2019.
Indice : Vous n'avez pas installé Composer ? Essayez ceci !
docker run --rm -it -v $PWD:/app -u $(id -u):$(id -g) composer install
Il existe au moins deux frameworks valides qui s’avèrent utiles lorsque vous envisagez de tester des extensions WordPress :
Essayons Brain Monkey :
composer require --dev brain/monkey:2.*`
Cela installera automatiquement également Mockery et Patchwork. Exécutez simplement composer install
et vous êtes prêt à partir.
Créez un répertoire qui hébergera une petite classe de test nommée WcctaTest.php :
mkdir -p tests/wccta
Excellent! Créons maintenant un fichier de configuration phpunit.xml dans le répertoire racine.
Vous pouvez également décider d'exécuter vos tests avec les paramètres de configuration depuis la ligne de commande. Voir la partie suivante (indice : 'scripts') !
Super! Ajoutez quelques sections au fichier composer.json :
composer test
Créons un répertoire qui hébergera notre code source. C'est ici que vous placerez un premier cours que vous testerez prochainement.
mkdir -p src/wccta && touch src/wccta/Plugin.php
rm -f tests/wccta/WcctaTest.php && touch tests/wccta/PluginTest.php
touch wordpress-plugins-phpunit.php
Nous souhaitons tester quelques méthodes de la classe Plugin
. Imaginez une méthode appelée is_loaded
qui renvoie true
en cas de succès. Lorsque vous êtes prêt, exécutez :
composer test
Astuce : Votre version système ou PHP n'est pas à jour ? Vous pouvez simplement sauter cette étape, mais essayons quelque chose de [pas si] nouveau !
docker run -it --rm -v $PWD:/app -w /app php:7.3-alpine php ./vendor/bin/phpunit
Vous pouvez probablement imaginer que certains plugins auront beaucoup de classes et que vous pourrez facilement oublier de tester toutes les fonctionnalités qui nécessitent des tests.
Alors parlons couverture !
Ajoutez simplement une commande personnalisée à la section scripts de votre composer.json :
"coverage": "./vendor/bin/phpunit --coverage-html ./reports/php/coverage"
et un filtre sur votre phpunit.xml :
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory>./src</directory>
</whitelist>
</filter>
Maintenant, exécutez simplement composer coverage
! Cela créera un répertoire ./reports/php/coverage
avec quelques fichiers html. Enfin, pas sur tous les ordinateurs. Certains recevront toujours des messages d'erreur tels que :
Error: No code coverage driver is available
Corrigeons cela dans notre image docker. J'ai préparé un Dockerfile pour que vous puissiez simplement exécuter :
docker build -t coverage .
Et une fois le processus de construction terminé :
docker run -it --rm -v $PWD:/app -w /app coverage:latest php ./vendor/bin/phpunit --coverage-html ./reports/php/coverage
Maintenant vous connaissez le Kung Fu ! Veuillez ouvrir le fichier ./reports/php/coverage/index.html dans votre navigateur !
Connectons notre classe Plugin
au plugin. Avant de vraiment passer aux tests, je vais juste vous montrer comment déclarer des parties de vos codes comme ne pas tester.
@codeCoverageIgnore
C'est l'une des annotations importantes disponibles. Nous y reviendrons plus tard, mais d'abord :
Exécutez à nouveau les tests unitaires avec le rapport de couverture !
Vous avez peut-être remarqué la colonne CRAP
dans le rapport de couverture. CRAP est un acronyme pour anti-modèles de risque de changement . Cela indique à quel point un changement de code dans une classe ou une méthode peut être risqué. Vous pouvez réduire le risque (et donc l'indice) avec un code moins complexe et une couverture complète des tests.
Commençons par tester quelque chose. Mais quoi ? Il n’y a toujours aucune autre fonctionnalité écrite qui nécessite des tests.
Voici le TDD (Test Driven Development) qui entre en jeu.
Même si vous décidez de ne pas utiliser cette technique, vous devez au moins savoir de quoi nous parlons.
Créons d'abord un Test CarTest
qui devrait tester si la méthode get_price
renvoie la chaîne '€ 14.500'
. Créez ensuite une Class Car
et écrivez la méthode get_price
qui satisfait au test. Ne commencez pas par la mise en œuvre.
À ce stade, permettez-moi de vous présenter également le modèle de test AAA (Arrange Act Assert) qui est largement accepté dans TDD . Il décrit comment organiser un test et est très similaire à GWT (Given When Then) de BDD (Behavior-driven Development).
Vous pouvez tester vos classes si elles lèvent une exception dans certaines conditions. Implémentons maintenant la méthode get_price
.
Créez simplement un Registry
de classe qui définit une valeur mixte en tant qu'élément nommé dans un tableau interne. Utilisez une méthode set()
ou la méthode magique __set()
pour cela. Tout d’abord, supposons que nous puissions transmettre un objet JSON à notre classe Car
. Cela donnera un peu plus de valeur à notre classe.
Une autre méthode get
ou __get()
devrait vérifier si un élément avec un élément donné existe et le renvoyer en cas de succès. S’il n’existe aucun élément de ce type, lancez une OutOfBoundsException
. Écrivez maintenant un constructeur qui gère l'entrée JSON et stocke l'objet dans un fichier member-var data
. La méthode get_price
doit prendre le prix de la variable data
et prendre en charge la sortie formatée.
Vérifiez l'étape 10 de la branche si vous avez du mal à écrire le code ! Le price
variable doit être un nombre entier. Ce n'est probablement pas un problème pour le moment car vous pouvez utiliser la fonction PHP number_format()
pour créer la sortie correcte. Mais dans une installation WordPress , vous vous attendez à ce que les paramètres régionaux soient définis, sur it_IT
(italien) par exemple.
La bonne façon de formater les nombres dans WordPress est d'utiliser la fonction number_format_i18n()
.
Alors changeons cela et voyons ce qui se passe :
Error: Call to undefined function wcctanumber_format_i18n()
Nous allons résoudre ce problème dans une seconde, mais préparons cela un peu d'abord. Brain Monkey utilise setUp()
et tearDown()
fournis par PHPUnit . Vous pouvez remplacer ces méthodes. Créons un TestCase
personnalisé - nommez- WcctaCase
- que nous pouvons étendre car nous le ferons probablement dans chaque classe de test.
Incluons maintenant l'espace de noms pour les tests dans la section autoload-dev :
"autoload-dev": {
"psr-4": {
"tests\wccta\": "tests/wccta"
}
},
Enfin, changeons le parent de nos classes de test.
class CarTest extends WcctaTestCase { // ... }
Nous sommes prêts à nous moquer de notre première fonction WordPress avec
Functionsexpect( $name_of_function )->andReturn( $value );
Écrire un test pour une seule attente semble trop d'effort. Et si vous souhaitez tester différentes valeurs ?
Fournisseur de données à la rescousse. J'ai déjà parlé des annotations à l'étape 5. Celle-ci est également très utile :
@dataprovider method_that_returns_data
Jetez un œil à mon exemple. getData
renvoie un tableau de tableaux. Chacun de ces tableaux contient 3 valeurs. Notre méthode test_getPrice
peut donc non seulement accepter le fournisseur de données avec l'annotation, mais elle peut également définir les variables d'entrée comme paramètres.
Vous pouvez tester vos classes si elles lèvent une exception dans certaines conditions.
Créez simplement un Registry
de classe qui définit une valeur mixte en tant qu'élément nommé dans un tableau interne. Utilisez une méthode set()
ou la méthode magique __set()
pour cela.
Une autre méthode get
ou __get()
devrait vérifier si un élément avec une clé donnée existe et le renvoie en cas de succès. S’il n’existe aucun élément de ce type, lancez une OutOfBoundsException
.
Vérifiez l'étape 10 de la branche si vous avez du mal à écrire le code !
Les dernières étapes nous ont amenés aux Usines . Qu'est-ce qu'une usine ? Parfois, vous créez des fonctions ou des méthodes qui masquent simplement le processus complexe de création d'un objet spécifique. Et parfois, il faut décider quel type d’objet vous souhaitez créer.
Dans les plugins WordPress, je préfère ajouter des hooks dans les usines aux objets. Il existe des plugins qui ajoutent des hooks dans les constructeurs de classes. Ce n’est pas une bonne chose (surtout quand vous testez encore la méthode classique : créer un environnement complet avec WordPress opérationnel).
Créons une classe Factory
avec une fonction statique nommée create
. Cette méthode doit renvoyer un objet Car
. Mais refactorisons le constructeur de Car
pour qu'il attende déjà un objet et aucune chaîne JSON. Nous ferons cela dans la méthode create de la classe Factory
.
Testez votre plugin maintenant avec composer test
et vous verrez quelques erreurs :
TypeError: Argument 1 passed to wcctaCar::__construct() must be an object, string given, called in ...
Nous devrions aussi corriger nos tests...
Excellent! Créons un test pour notre usine. Nous allons laisser la méthode sans aucun contenu pour l'instant. Refaites les tests !
There was 1 risky test:
1) testswcctaFactoryTest::test_create
This test did not perform any assertions
Les tests réussissent mais vous recevez le message qu’il y a eu un test risqué. Au fait : nommez la fonction test_create
create
simplement et utilisez l'annotation @test
. Je crois que l'utilisation de cette annotation dépend de vos goûts personnels !
Nous allons maintenant approfondir cela un peu plus.
Créez une interface FooterInterface
qui définit une info
de méthode publique qui n'attendra aucune valeur de retour. Implémentez l'interface dans Car
, info
pourrait - par exemple - afficher un message amusant.
Définissez le type de retour FooterInterface
pour la méthode create
de Factory
et ajoutez la méthode info
de Car
à l'action WordPress wp_footer
.
Testons maintenant cela dans FactoryTest
. Il existe au moins deux façons de tester cela correctement. Utilisez has_action ou ActionsexpectAdded()
. Un test pour les filtres serait similaire et est bien décrit sur la page liée.
Vérifiez si composer test
réussit toujours tous les tests.
Comment est la couverture actuellement ? Exécutez composer coverage
et vérifiez la sortie générée.
La méthode info
de notre classe Car
n’est couverte par aucun test. Mais peut-on tester le résultat d’une méthode ?
Il s'avère que c'est assez simple avec expectOutputString.
Célébrons ce que nous avons appris !
Créez une classe Locale
dotée d'une méthode publique get
qui renvoie get_locale()
. Excluez la méthode de la couverture !
Créez maintenant un constructeur dans notre classe Plugin
qui accepte une instance Locale
et stockez-le dans une variable membre $this->locale
. Créez ensuite une méthode get_region_code
qui renvoie la valeur de $this->locale->get()
. Ah, et supprimez la méthode is_loaded
. ;)
Dans notre test, nous avons pu créer un objet de type Locale
, nous moquer de la fonction WordPress get_locale
et le transmettre au constructeur Plugin
! Mais je veux Tuse Mocker ici :
public function test_get_region_code() {
$code = 'it_IT';
$locale = Mockery::mock( Locale::class );
$locale->shouldReceive( 'get' )->andReturn( $code );
$sut = new Plugin( $locale );
$this->assertEquals( $code, $sut->get_region_code() );
}
Vous pouvez maintenant rendre vos plugins WordPress à l’épreuve des balles !
Amusez-vous!