Test en isolation
Lors du développement d'un plugin, le meilleur moyen de le tester est de ne pas charger l'environnement WordPress.
Si vous écrivez du code qui peut être facilement testé sans WordPress, votre code devient meilleur .
Chaque composant testé par unité doit être testé séparément : lorsque vous testez une classe, vous devez uniquement tester cette classe spécifique, en supposant que tout le code fonctionne parfaitement.
C'est la raison pour laquelle les tests unitaires sont appelés "unit".
Un avantage supplémentaire, sans chargement de noyau, votre test s'exécutera beaucoup plus rapidement.
Éviter les crochets dans le constructeur
Un conseil que je peux vous donner est d'éviter de mettre des crochets dans les constructeurs. C'est l'une des choses qui rendra votre code testable de manière isolée.
Voyons le code de test dans OP:
class CustomPostTypes extends WP_UnitTestCase {
function test_custom_post_type_creation() {
$this->assertTrue( post_type_exists( 'foo' ) );
}
}
Et supposons que ce test échoue . Qui est le coupable ?
- le crochet n'a pas été ajouté du tout ou pas correctement?
- la méthode qui enregistre le type de publication n’a pas été appelée du tout ou avec de mauvais arguments?
- il y a un bug dans WordPress?
Comment cela peut-il être amélioré?
Supposons que votre code de classe est:
class RegisterCustomPostType {
function init() {
add_action( 'init', array( $this, 'register_post_type' ) );
}
public function register_post_type() {
register_post_type( 'foo' );
}
}
(Remarque: je vais me référer à cette version de la classe pour le reste de la réponse.)
La façon dont j'ai écrit cette classe vous permet de créer des instances de la classe sans appeler add_action
.
Dans la classe ci-dessus, il y a 2 choses à tester:
- la méthode appelle
init
en fait en lui add_action
passant des arguments appropriés
- la méthode appelle
register_post_type
réellement laregister_post_type
fonction
Je n'ai pas dit que vous deviez vérifier si le type de message existe: si vous ajoutez l'action appropriée et si vous appelez register_post_type
, le type de message personnalisé doit exister: s'il n'existe pas, il s'agit d'un problème de WordPress.
Rappelez-vous: lorsque vous testez votre plugin, vous devez tester votre code, pas le code WordPress. Dans vos tests, vous devez supposer que WordPress (comme toute autre bibliothèque externe que vous utilisez) fonctionne bien. C'est le sens du test unitaire .
Mais ... en pratique?
Si WordPress n'est pas chargé, si vous essayez d'appeler les méthodes de classe ci-dessus, vous obtenez une erreur irrécupérable. Vous devez donc vous moquer des fonctions.
La méthode "manuelle"
Bien sûr, vous pouvez écrire votre bibliothèque moqueuse ou "manuellement" simuler chaque méthode. C'est possible. Je vais vous dire comment faire cela, mais ensuite je vais vous montrer une méthode plus facile.
Si WordPress n'est pas chargé pendant l'exécution des tests, cela signifie que vous pouvez redéfinir ses fonctions, par exemple add_action
ou register_post_type
.
Supposons que vous avez un fichier, chargé à partir de votre fichier d'amorçage, où vous avez:
function add_action() {
global $counter;
if ( ! isset($counter['add_action']) ) {
$counter['add_action'] = array();
}
$counter['add_action'][] = func_get_args();
}
function register_post_type() {
global $counter;
if ( ! isset($counter['register_post_type']) ) {
$counter['register_post_type'] = array();
}
$counter['register_post_type'][] = func_get_args();
}
J'ai réécrit les fonctions pour simplement ajouter un élément à un tableau global à chaque appel.
Vous devez maintenant créer (si vous n'en avez pas déjà) votre propre classe de cas de test de base étendue PHPUnit_Framework_TestCase
: cela vous permet de configurer facilement vos tests.
Cela peut être quelque chose comme:
class Custom_TestCase extends \PHPUnit_Framework_TestCase {
public function setUp() {
$GLOBALS['counter'] = array();
}
}
De cette manière, avant chaque test, le compteur global est réinitialisé.
Et maintenant, votre code de test (je me réfère à la classe réécrite que j'ai postée ci-dessus):
class CustomPostTypes extends Custom_TestCase {
function test_init() {
global $counter;
$r = new RegisterCustomPostType;
$r->init();
$this->assertSame(
$counter['add_action'][0],
array( 'init', array( $r, 'register_post_type' ) )
);
}
function test_register_post_type() {
global $counter;
$r = new RegisterCustomPostType;
$r->register_post_type();
$this->assertSame( $counter['register_post_type'][0], array( 'foo' ) );
}
}
Vous devriez noter:
- J'ai pu appeler les deux méthodes séparément et WordPress n'est pas chargé du tout. De cette façon, si un test échoue, je sais exactement qui est le coupable.
- Comme je l'ai dit, je teste ici que les classes appellent des fonctions WP avec les arguments attendus. Il n'est pas nécessaire de vérifier si le CPT existe réellement. Si vous testez l'existence de CPT, vous testez le comportement de WordPress, pas le comportement de votre plugin ...
Bien .. mais c'est un pita!
Oui, si vous devez vous moquer manuellement de toutes les fonctions de WordPress, c'est vraiment pénible. Un conseil général que je peux vous donner est d'utiliser le moins de fonctions possible de WP: vous n'avez pas à réécrire WordPress, mais des fonctions abstraites de WP que vous utilisez dans des classes personnalisées, afin de pouvoir les simuler et les tester facilement.
Par exemple, concernant l'exemple ci-dessus, vous pouvez écrire une classe qui enregistre des types de publication en faisant appel register_post_type
à 'init' avec des arguments donnés. Avec cette abstraction, vous devez toujours tester cette classe, mais à d'autres endroits de votre code qui enregistrent des types de publication, vous pouvez utiliser cette classe en la moquant dans des tests (en supposant que cela fonctionne).
Ce qui est génial, c’est que si vous écrivez une classe qui résume l’enregistrement CPT, vous pouvez créer un référentiel distinct et, grâce à des outils modernes comme Composer, l’ incorporer dans tous les projets pour lesquels vous en avez besoin: testez une fois, utilisez-le partout . Et si jamais vous rencontrez un bogue, vous pouvez le réparer en un seul endroit et avec un simple, composer update
tous les projets où il est utilisé sont également corrigés.
Pour la deuxième fois: écrire du code qui peut être testé isolément signifie écrire un meilleur code.
Mais tôt ou tard, j'ai besoin d'utiliser les fonctions de WP quelque part ...
Bien sûr. Vous ne devriez jamais agir parallèlement au noyau, cela n'a aucun sens. Vous pouvez écrire des classes qui encapsulent des fonctions WP, mais ces classes doivent également être testées. La méthode "manuelle" décrite ci-dessus peut être utilisée pour des tâches très simples, mais quand une classe contient beaucoup de fonctions WP, cela peut être pénible.
Heureusement, là-bas, il y a de bonnes personnes qui écrivent de bonnes choses. 10up , une des plus grandes agences WP, maintient une très bonne bibliothèque pour ceux qui veulent tester les plugins de la bonne façon. C'est WP_Mock
.
Il vous permet de simuler des fonctions WP et des hooks . En supposant que vous ayez chargé dans vos tests (voir le fichier repo), le même test que j'ai écrit ci-dessus devient:
class CustomPostTypes extends Custom_TestCase {
function test_init() {
$r = new RegisterCustomPostType;
// tests that the action was added with given arguments
\WP_Mock::expectActionAdded( 'init', array( $r, 'register_post_type' ) );
$r->init();
}
function test_register_post_type() {
// tests that the function was called with given arguments and run once
\WP_Mock::wpFunction( 'register_post_type', array(
'times' => 1,
'args' => array( 'foo' ),
) );
$r = new RegisterCustomPostType;
$r->register_post_type();
}
}
Simple, n'est ce pas? Cette réponse n’est pas un tutoriel pour WP_Mock
, alors lisez le fichier lisez-moi pour plus d’informations, mais l’exemple ci-dessus devrait être assez clair, je pense.
De plus, vous n'avez pas besoin d'écrire vous-même add_action
ou register_post_type
de vous moquer , ni de maintenir des variables globales.
Et des cours WP?
WP a aussi des classes, et si WordPress n’est pas chargé lorsque vous exécutez des tests, vous devez vous en moquer.
C'est beaucoup plus facile que de se moquer de fonctions, PHPUnit a un système embarqué pour se moquer d'objets, mais je veux ici vous suggérer Mockery . C'est une bibliothèque très puissante et très facile à utiliser. De plus, c'est une dépendance de WP_Mock
, donc si vous l'avez, vous avez aussi Mockery.
Mais qu'en est-il WP_UnitTestCase
?
La suite de tests WordPress a été créée pour tester le noyau de WordPress , et si vous souhaitez contribuer au noyau, elle est essentielle, mais son utilisation pour les plugins ne vous permet pas de tester en vase clos.
Regardez vers le monde WP: il existe de nombreux frameworks PHP modernes et CMS, et aucun d’entre eux ne suggère de tester des plugins / modules / extensions (ou peu importe leur nom) avec du code framework.
Si vous manquez des usines, une fonctionnalité utile de la suite, vous devez savoir qu'il existe des choses incroyables là-bas.
Les pièges et les inconvénients
Il existe un cas où le flux de travail que j'ai suggéré ici manque: les tests de base de données personnalisés .
En fait, si vous utilisez des tables de WordPress et fonctions standard pour y écrire (au plus bas niveau des $wpdb
méthodes) vous ne devez jamais réellement l' écriture de données ou de test si les données sont en fait dans la base de données, assurez - vous simplement que les méthodes appropriées sont appelées avec des arguments appropriés.
Cependant, vous pouvez écrire des plugins avec des tables et des fonctions personnalisées qui construisent des requêtes pour y écrire, et tester si ces requêtes fonctionnent, cela relève de votre responsabilité.
Dans ces cas, la suite de tests WordPress peut vous aider beaucoup, et le chargement de WordPress peut être nécessaire dans certains cas pour exécuter des fonctions telles que dbDelta
.
(Il n'y a pas besoin de dire d'utiliser une autre base de données pour les tests, n'est-ce pas?)
Heureusement, PHPUnit vous permet d'organiser vos tests en "suites" pouvant être exécutées séparément. Vous pouvez donc écrire une suite pour des tests de base de données personnalisés dans lesquels vous chargez un environnement WordPress (ou une partie de celui-ci), laissant ainsi le reste de vos tests sans WordPress .
Assurez-vous seulement d'écrire des classes qui résument autant d'opérations de base de données que toutes les autres classes de plug-in, afin que vous puissiez tester correctement la majorité des classes sans utiliser la base de données.
Pour la troisième fois, écrire du code facilement testable de manière isolée signifie écrire un meilleur code.
phpunit
, pouvez-vous voir des tests ayant échoué ou réussi? Avez-vous installébin/install-wp-tests.sh
?