Test in isolamento
Quando si sviluppa un plug-in, il modo migliore per testarlo è senza caricare l'ambiente WordPress.
Se scrivi codice che può essere facilmente testato senza WordPress, il tuo codice diventa migliore .
Ogni componente sottoposto a test unitario deve essere testato separatamente : quando si verifica una classe, è necessario testare solo quella classe specifica, assumendo che tutto il resto del codice funzioni perfettamente.
Questo è il motivo per cui i test unitari sono chiamati "unit".
Come ulteriore vantaggio, senza caricare il core, il test verrà eseguito molto più velocemente.
Evitare ganci nel costruttore
Un consiglio che posso darti è di evitare di mettere ganci nei costruttori. Questa è una delle cose che renderà il tuo codice testabile da solo.
Vediamo il codice di prova in OP:
class CustomPostTypes extends WP_UnitTestCase {
function test_custom_post_type_creation() {
$this->assertTrue( post_type_exists( 'foo' ) );
}
}
E supponiamo che questo test fallisca . Chi è il colpevole ?
- il gancio non è stato aggiunto affatto o non correttamente?
- il metodo che registra il tipo di post non è stato chiamato affatto o con argomenti sbagliati?
- c'è un bug in WordPress?
Come può essere migliorato?
Supponiamo che il codice della tua classe sia:
class RegisterCustomPostType {
function init() {
add_action( 'init', array( $this, 'register_post_type' ) );
}
public function register_post_type() {
register_post_type( 'foo' );
}
}
(Nota: farò riferimento a questa versione della classe per il resto della risposta)
Il modo in cui ho scritto questa classe ti consente di creare istanze della classe senza chiamare add_action
.
Nella classe sopra ci sono 2 cose da testare:
- il metodo in
init
realtà chiama add_action
passando gli argomenti propri
- il metodo chiama
register_post_type
effettivamente laregister_post_type
funzione
Non ho detto che devi controllare se esiste il tipo di post: se aggiungi l'azione corretta e se chiami register_post_type
, il tipo di post personalizzato deve esistere: se non esiste è un problema di WordPress.
Ricorda: quando provi il tuo plugin devi testare il tuo codice, non il codice WordPress. Nei tuoi test devi presumere che WordPress (proprio come qualsiasi altra libreria esterna che usi) funzioni bene. Questo è il significato di unit test.
Ma ... in pratica?
Se WordPress non viene caricato, se si tenta di chiamare i metodi di classe sopra, si ottiene un errore irreversibile, quindi è necessario prendere in giro le funzioni.
Il metodo "manuale"
Sicuramente puoi scrivere la tua libreria beffarda o deridere "manualmente" ogni metodo. È possibile. Ti dirò come farlo, ma poi ti mostrerò un metodo più semplice.
Se WordPress non è caricato mentre i test sono in esecuzione, significa che è possibile ridefinire le sue funzioni, ad esempio add_action
o register_post_type
.
Supponiamo che tu abbia un file, caricato dal tuo file bootstrap, dove hai:
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();
}
Ho riscritto le funzioni per aggiungere semplicemente un elemento a un array globale ogni volta che vengono chiamate.
Ora dovresti creare (se non ne hai già uno) estendere la tua classe di test case di base PHPUnit_Framework_TestCase
: ciò ti consente di configurare facilmente i tuoi test.
Può essere qualcosa del tipo:
class Custom_TestCase extends \PHPUnit_Framework_TestCase {
public function setUp() {
$GLOBALS['counter'] = array();
}
}
In questo modo, prima di ogni test, il contatore globale viene resettato.
E ora il tuo codice di prova (mi riferisco alla classe riscritta che ho pubblicato sopra):
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' ) );
}
}
Dovresti notare:
- Sono stato in grado di chiamare i due metodi separatamente e WordPress non è stato caricato affatto. In questo modo se un test fallisce, so esattamente chi è il colpevole.
- Come ho detto, qui provo che le classi chiamano funzioni WP con argomenti previsti. Non è necessario verificare se esiste davvero CPT. Se stai testando l'esistenza di CPT, stai testando il comportamento di WordPress, non il comportamento del plug-in ...
Bello .. ma è una PITA!
Sì, se devi deridere manualmente tutte le funzioni di WordPress, è davvero una seccatura. Un consiglio generale che posso dare è di usare il minor numero possibile di funzioni WP: non devi riscrivere WordPress, ma astratto funzioni WP che usi in classi personalizzate, in modo che possano essere derise e testate facilmente.
Ad esempio, per quanto riguarda l'esempio sopra, puoi scrivere una classe che registra i tipi di posta, chiamando register_post_type
"init" con argomenti specifici. Con questa astrazione devi ancora testare quella classe, ma in altre parti del tuo codice che registrano i tipi di post puoi usare quella classe, deridendola nei test (quindi supponendo che funzioni).
La cosa fantastica è che, se scrivi una classe che estrae la registrazione CPT, puoi creare un repository separato per esso, e grazie a strumenti moderni come Composer lo incorpora in tutti i progetti in cui ne hai bisogno: prova una volta, usa ovunque . E se trovi mai un bug in esso, puoi risolverlo in un posto e con un semplice anche composer update
tutti i progetti in cui viene utilizzato vengono corretti.
Per la seconda volta: scrivere codice testabile in isolamento significa scrivere codice migliore.
Ma prima o poi devo usare le funzioni WP da qualche parte ...
Ovviamente. Non dovresti mai agire in parallelo con il core, non ha senso. Puoi scrivere classi che racchiudono funzioni WP, ma anche quelle classi devono essere testate. Il metodo "manuale" sopra descritto può essere usato per compiti molto semplici, ma quando una classe contiene molte funzioni WP può essere una seccatura.
Fortunatamente laggiù ci sono persone buone che scrivono cose buone. 10up , una delle più grandi agenzie del WP, mantiene un'ottima libreria per le persone che vogliono testare i plugin nel modo giusto. Lo è WP_Mock
.
Ti permette di deridere funzioni WP e ganci . Supponendo che tu abbia caricato nei tuoi test (vedi il file Leggimi repo) lo stesso test che ho scritto sopra diventa:
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();
}
}
Semplice, no? Questa risposta non è un tutorial per WP_Mock
, quindi leggi il readme del repository per maggiori informazioni, ma l'esempio sopra dovrebbe essere abbastanza chiaro, credo.
Inoltre, non è necessario scrivere alcun beffardo add_action
o register_post_type
da solo o mantenere variabili globali.
E le classi WP?
WP ha anche alcune classi e se WordPress non viene caricato quando si eseguono i test, è necessario prenderli in giro.
È molto più facile delle funzioni di derisione, PHPUnit ha un sistema incorporato per deridere oggetti, ma qui voglio suggerirti derisione . È una libreria molto potente e molto facile da usare. Inoltre, è una dipendenza diWP_Mock
, quindi se ce l'hai hai anche la beffa.
Ma che dire WP_UnitTestCase
?
La suite di test di WordPress è stata creata per testare il core di WordPress e, se si desidera contribuire al core, è fondamentale, ma l'utilizzo per i plug-in ti fa solo provare non in isolamento.
Metti gli occhi sul mondo WP: ci sono molti moderni framework PHP e CMS là fuori e nessuno di loro suggerisce di testare plugin / moduli / estensioni (o come si chiamano) usando il codice del framework.
Se ti mancano le fabbriche, una caratteristica utile della suite, devi sapere che ci sono cose fantastiche laggiù.
Gotcha e aspetti negativi
C'è un caso in cui manca il flusso di lavoro che ho suggerito qui: test del database personalizzato .
In effetti, se si utilizzano le tabelle di WordPress standard e funzioni di scrivere lì (al livello più basso $wpdb
metodi) non è mai necessario in realtà i dati di scrittura o di prova se i dati sono in realtà nel database, tanto per essere sicuro che i metodi corretti vengono chiamati con argomenti corretti.
Tuttavia, puoi scrivere plug-in con tabelle e funzioni personalizzate che creano query da scrivere lì e testare se quelle query funzionano è tua responsabilità.
In questi casi, la suite di test di WordPress può aiutarti molto e in alcuni casi potrebbe essere necessario caricare WordPress per eseguire funzioni come dbDelta
.
(Non c'è bisogno di dire di usare un db diverso per i test, non è vero?)
Fortunatamente PHPUnit ti consente di organizzare i tuoi test in "suite" che possono essere eseguite separatamente, in modo da poter scrivere una suite per test di database personalizzati in cui carichi l'ambiente WordPress (o parte di esso) lasciando tutto il resto dei tuoi test senza WordPress .
Assicurati di scrivere solo le classi che astraggono il maggior numero possibile di operazioni di database, in modo che tutte le altre classi di plugin ne facciano uso, in modo che usando i mock puoi testare correttamente la maggior parte delle classi senza occuparsi del database.
Per la terza volta, scrivere codice facilmente testabile in isolamento significa scrivere codice migliore.
phpunit
, riesci a vedere i test falliti o superati? Hai installatobin/install-wp-tests.sh
?