martes, 13 de marzo de 2012

Crear Models y Fixtures para Tests

Una de mis frustraciones con CakePHP es que la documentación para crear Test Unitarios no cubre bien todas las posibilidades. Encontrar algunas soluciones concretas requiere bastante investigación en el manual, API, código del core y en blogs dispersos.

Uno de estos puntos oscuros (para mí) es el Test de Behaviors. Los Behaviors, como sabrás, son extensiones a los Models. Normalmente para crear los tests necesitarás un modelo al que asociarlos y el problema es que, por lo general, este modelo debería ser independiente y específicamente creado para la ocasión, o lo bastante genérico como para no introducir factores indeseados en el proceso de test.

¿Cómo crear esos modelos? Pues esto es lo que voy a intentar contar en este artículo. Esto lo he aprendido estudiando el código de tests de Cake y, aunque no tengo claro que sea todo lo completo que desearía, al menos he conseguido que vaya funcionando para mis propósitos.

Models

Para empezar, los Model para Tests extienden la clase CakeTestModel (que a su vez desciende de Model y se ajusta para utilizar la configuración de base de datos de test por defecto),
Pero nuestros Models no deberían acceder a una base de datos "física", a fin de no introducir elementos de distorsión de los resultados del test.

Esto se consigue definiendo el esquema o estructura de datos en el propio modelo y especificando que no se use una tabla. Aquí tienes un ejemplo:

class Basic extends CakeTestModel {
    var $useTable = false;
    var $name = 'Basic';
    var $_schema = array(
        'id'=> array('type' => 'string', 'null' => '', 'default' => '1', 'length' => '36', 'key'=>'primary'),
        'title'=> array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'),
        'key'=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'),
    );
}


El lenguaje de esquema de CakePHP es bastante sencillo y te permite definir los tipos de campos que necesites. En este caso, necesitaba dos campos de texto, aparte del campo id.

Pero, ¿dónde defino este modelo?

Pues se me ocurren dos ubicaciones. Si no piensas reutilizarlo, podrías definirlo en el mismo archivo que el test.

Pero si crees que puedes utilizarlo en tests de otros elementos de tu aplicación (otros Behaviors, Controllers, etc) es buena idea guardarlo en un archivo separado e incluirlo cuando sea necesario.
En mi caso, he creado un archivo models.php en la carpeta tests de la aplicación. En este archivo colecciono los modelos creados para tests. Para incluirlo, no tengo más que usar este código:

require_once(TESTS . DS . 'models.php');

Y luego crear una instancia del modelo cuando la necesite con los métodos habituales, ya sea mediante new Model o ClassRegistry.


Los datos

En la mayoría de los tests necesitaré datos en los modelos, para lo cual tendré que crear un archivo de fixtures.

En teoría debería bastar con utilizar la propiedad CakeTestFixture->import y especificar que vamos a usar el modelo creado, pero yo no he conseguido hacerlo funcionar así, por lo que he tenido que especificar los campos del modelo en el archivo de fixtures (basta con copiar y pegar el contenido de Model->_schema). Puede que sea debido al hecho de no utilizar una tabla en la base de datos, sin embargo, no deja de ser un engorro. La parte buena es que estos modelos no tienen que cambiar mucho y realmente no da tanto trabajo.

Incluimos también los registros (records) que necesitemos para empezar. Este es un ejemplo de TEST/fixtures/basic_fixture.php


class BasicFixture extends CakeTestFixture {
    var $name = 'Basic';
    var $fields = array(
        'id'=> array('type' => 'string', 'null' => '', 'default' => '1', 'length' => '36', 'key'=>'primary'),
        'title'=> array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'),
        'key'=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'),
    );
   var $records = array(
        array(
            'id' => 1,
            'title' => 'Lorem ipsum dolor sit amet',
            'key' => 'lorem_ipsum_dolor_sit_amet',
        ),
    );
}
?>


En el test declaramos que vamos a usar como fixtures app.basic y así estamos listos para trabajar. Aquí tienes un ejemplo de un test que utiliza el modelo Basic.

Conclusiones

Finalmente, tener modelos para usarlos específicamente en tests unitarios es bastante fácil. Son autocontenidos, por lo que no necesitas tener una base de datos y los puedes reutilizar dentro de un mismo proyecto o en otros.