Prueba aislada
Al desarrollar un complemento, la mejor manera de probarlo es sin cargar el entorno de WordPress.
Si escribe código que se puede probar fácilmente sin WordPress, su código se vuelve mejor .
Todos los componentes que se prueban por unidad deben probarse de forma aislada : cuando prueba una clase, solo tiene que probar esa clase específica, suponiendo que el resto del código esté funcionando perfectamente.
Esta es la razón por la cual las pruebas unitarias se denominan "unidad".
Como beneficio adicional, sin cargar el núcleo, su prueba se ejecutará mucho más rápido.
Evitar ganchos en constructor
Un consejo que puedo darle es evitar poner ganchos en los constructores. Esa es una de las cosas que hará que su código sea comprobable de forma aislada.
Veamos el código de prueba en OP:
class CustomPostTypes extends WP_UnitTestCase {
function test_custom_post_type_creation() {
$this->assertTrue( post_type_exists( 'foo' ) );
}
}
Y supongamos que esta prueba falla . ¿Quién es el culpable ?
- ¿No se agregó el gancho o no fue correcto?
- ¿El método que registra el tipo de publicación no se llamó en absoluto o con argumentos incorrectos?
- Hay un error en WordPress?
¿Cómo se puede mejorar?
Supongamos que su código de clase es:
class RegisterCustomPostType {
function init() {
add_action( 'init', array( $this, 'register_post_type' ) );
}
public function register_post_type() {
register_post_type( 'foo' );
}
}
(Nota: me referiré a esta versión de la clase para el resto de la respuesta)
La forma en que escribí esta clase le permite crear instancias de la clase sin llamar add_action
.
En la clase anterior hay 2 cosas para ser probadas:
- el método
init
realmente llama add_action
pasarle argumentos apropiados
- el método
register_post_type
realmente llama a la register_post_type
función
No dije que debe verificar si el tipo de publicación existe: si agrega la acción adecuada y si llama register_post_type
, debe existir el tipo de publicación personalizada : si no existe, es un problema de WordPress.
Recuerde: cuando prueba su complemento, debe probar su código, no el código de WordPress. En sus pruebas, debe asumir que WordPress (al igual que cualquier otra biblioteca externa que use) funciona bien. Ese es el significado de la prueba unitaria .
Pero ... en la práctica?
Si WordPress no está cargado, si intenta llamar a los métodos de clase anteriores, obtendrá un error fatal, por lo que debe burlarse de las funciones.
El método "manual"
Claro que puede escribir su biblioteca de burla o burlarse "manualmente" de cada método. Es posible. Te diré cómo hacerlo, pero luego te mostraré un método más fácil.
Si WordPress no se carga mientras se ejecutan las pruebas, significa que puede redefinir sus funciones, por ejemplo, add_action
o register_post_type
.
Supongamos que tiene un archivo, cargado desde su archivo bootstrap, donde tiene:
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();
}
Reescribí las funciones para simplemente agregar un elemento a una matriz global cada vez que se llaman.
Ahora debe crear (si aún no tiene uno) su propia clase de caso de prueba base que se extienda PHPUnit_Framework_TestCase
: eso le permite configurar fácilmente sus pruebas.
Puede ser algo como:
class Custom_TestCase extends \PHPUnit_Framework_TestCase {
public function setUp() {
$GLOBALS['counter'] = array();
}
}
De esta manera, antes de cada prueba, el contador global se reinicia.
Y ahora su código de prueba (me refiero a la clase reescrita que publiqué anteriormente):
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' ) );
}
}
Debes tener en cuenta:
- Pude llamar a los dos métodos por separado y WordPress no está cargado en absoluto. De esta manera, si una prueba falla, sé exactamente quién es el culpable.
- Como dije, aquí pruebo que las clases llaman a las funciones de WP con los argumentos esperados. No hay necesidad de probar si CPT realmente existe. Si está probando la existencia de CPT, está probando el comportamiento de WordPress, no el comportamiento de su complemento ...
Bonito ... pero es una PITA!
Sí, si tiene que burlarse manualmente de todas las funciones de WordPress, es realmente un dolor. Algunos consejos generales que puedo dar es usar la menor cantidad posible de funciones de WP: no es necesario reescribir WordPress, sino funciones abstractas de WP que se usan en clases personalizadas, de modo que se puedan burlar y probar fácilmente.
Por ejemplo, con respecto al ejemplo anterior, puede escribir una clase que registre tipos de publicaciones, invocando register_post_type
'init' con argumentos dados. Con esta abstracción, aún necesita probar esa clase, pero en otros lugares de su código que registran tipos de publicaciones, puede hacer uso de esa clase, burlándose de ella en las pruebas (suponiendo que funcione).
Lo increíble es que, si escribe una clase que resuma el registro de CPT, puede crear un repositorio separado para él, y gracias a las herramientas modernas como Composer, incrustarlo en todos los proyectos donde lo necesite: probar una vez, usar en todas partes . Y si alguna vez encuentra un error en él, puede solucionarlo en un solo lugar y con un simple composer update
todos los proyectos donde se utiliza también se solucionan.
Por segunda vez: escribir código que se pueda probar de forma aislada significa escribir un código mejor.
Pero tarde o temprano necesito usar las funciones de WP en alguna parte ...
Por supuesto. Nunca debes actuar en paralelo al núcleo, no tiene sentido. Puede escribir clases que envuelvan las funciones de WP, pero esas clases también deben probarse. El método "manual" descrito anteriormente puede usarse para tareas muy simples, pero cuando una clase contiene muchas funciones de WP puede ser una molestia.
Afortunadamente, allí hay buenas personas que escriben cosas buenas. 10up , una de las mayores agencias de WP, mantiene una gran biblioteca para las personas que desean probar los complementos de la manera correcta. Es WP_Mock
.
Le permite burlarse de las funciones de WP y los ganchos . Suponiendo que haya cargado en sus pruebas (vea el archivo readme del repositorio), la misma prueba que escribí anteriormente se convierte en:
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, ¿no es así? Esta respuesta no es un tutorial para WP_Mock
, así que lea el archivo readme del repositorio para obtener más información, pero el ejemplo anterior debería ser bastante claro, creo.
Además, no necesita escribir ninguna burla add_action
o register_post_type
usted mismo, ni mantener ninguna variable global.
¿Y las clases de WP?
WP también tiene algunas clases, y si WordPress no se carga cuando ejecuta pruebas, debe burlarse de ellas.
Eso es mucho más fácil que burlarse de funciones, PHPUnit tiene un sistema integrado para burlarse de objetos, pero aquí quiero sugerirle burla . Es una biblioteca muy poderosa y muy fácil de usar. Además, es una dependencia de WP_Mock
, por lo que si lo tiene, también tiene burla.
¿Pero que pasa WP_UnitTestCase
?
El conjunto de pruebas de WordPress se creó para probar el núcleo de WordPress , y si desea contribuir al núcleo es fundamental, pero usarlo para complementos solo hace que la prueba no sea aislada.
Eche un vistazo al mundo de WP: hay muchos frameworks PHP modernos y CMS por ahí y ninguno de ellos sugiere probar plugins / módulos / extensiones (o como se llamen) usando el código de marco.
Si echa de menos las fábricas, una característica útil de la suite, debe saber que hay cosas increíbles allí.
Gotchas y desventajas
Hay un caso en el que falta el flujo de trabajo que sugerí aquí: pruebas de bases de datos personalizadas .
De hecho, si utiliza tablas de WordPress y funciones estándar para escribir allí (al nivel más bajo $wpdb
métodos) que no será necesario en realidad datos de escritura o de prueba si los datos es en realidad la base de datos, sólo asegúrese de que los métodos adecuados se llaman con argumentos adecuados.
Sin embargo, puede escribir complementos con tablas y funciones personalizadas que crean consultas para escribir allí, y probar si esas consultas funcionan es su responsabilidad.
En esos casos, el conjunto de pruebas de WordPress puede ayudarlo mucho, y puede ser necesario cargar WordPress en algunos casos para ejecutar funciones como dbDelta
.
(No hay necesidad de decir que use una base de datos diferente para las pruebas, ¿no es así?)
Afortunadamente, PHPUnit le permite organizar sus pruebas en "suites" que se pueden ejecutar por separado, por lo que puede escribir una suite para pruebas de bases de datos personalizadas donde cargue el entorno de WordPress (o parte de él) dejando todo el resto de sus pruebas sin WordPress .
Solo asegúrese de escribir clases que resuman tantas operaciones de base de datos como sea posible, de manera que todas las demás clases de complementos las utilicen, de modo que utilizando simulacros pueda probar adecuadamente la mayoría de las clases sin tener que lidiar con la base de datos.
Por tercera vez, escribir código fácilmente comprobable de forma aislada significa escribir un código mejor.
phpunit
, ¿puede ver pruebas fallidas o pasadas? ¿Instalastebin/install-wp-tests.sh
?