Si desea saber por qué las pruebas son beneficiosas, este no es el artículo para usted. A lo largo de este tutorial, asumiré que ya entiendes las ventajas y que estás esperando aprender cómo escribir y organizar tus exámenes en Laravel 4..
La versión 4 de Laravel ofrece importantes mejoras en relación con las pruebas, en comparación con su versión anterior. Este es el primer artículo de una serie que cubrirá cómo escribir pruebas para aplicaciones Laravel 4. Comenzaremos la serie discutiendo la prueba del modelo..
A menos que esté ejecutando consultas en bruto en su base de datos, Laravel le permite a su aplicación permanecer en una base de datos independiente. Con un simple cambio de controlador, su aplicación ahora puede trabajar con otros DBMS (MySQL, PostgreSQL, SQLite, etc.). Entre las opciones predeterminadas, SQLite ofrece una característica peculiar, pero muy útil: bases de datos en memoria.
Con Sqlite, podemos establecer la conexión de la base de datos a :memoria:
, lo que acelerará drásticamente nuestras pruebas, debido a que la base de datos no existe en el disco duro. Además, la base de datos de producción / desarrollo nunca se rellenará con datos de prueba sobrantes, porque la conexión, :memoria:
, Siempre comienza con una base de datos vacía..
En resumen: una base de datos en memoria permite realizar pruebas rápidas y limpias.
Dentro de app / config / testing
directorio, crear un nuevo archivo, llamado database.php
, y rellénalo con el siguiente contenido:
// app / config / testing / database.php 'sqlite', 'connections' => array ('sqlite' => array ('driver' => 'sqlite', 'database' => ': memory:', 'prefix' => "),));
El hecho de que database.php
se coloca dentro de la configuración pruebas
directorio significa que estas configuraciones solo se utilizarán cuando se esté en un entorno de prueba (que Laravel establece automáticamente). Como tal, cuando se accede a su aplicación normalmente, la base de datos en memoria no se utilizará.
Dado que la base de datos en memoria siempre está vacía cuando se realiza una conexión, es importante emigrar La base de datos antes de cada prueba. Para ello, abre. app / tests / TestCase.php
y agrega el siguiente método al final de la clase:
/ ** * Migra la base de datos y configura la aplicación de correo para 'simular'. * Esto hará que las pruebas se ejecuten rápidamente. * * / private function prepareForTests () Artisan :: call ('migrate'); Mail :: pretend (true);
Nota la
preparar()
El método es ejecutado por PHPUnit antes de cada prueba..
Este método preparará la base de datos y cambiará el estado de Laravel Remitente
clase a pretender
. De esta manera, el remitente no enviará ningún correo electrónico real al ejecutar pruebas. En su lugar, registrará los mensajes "enviados"..
Finalizar app / tests / TestCase.php
, llamada prepareForTests ()
dentro de la unidad PHP preparar()
Método, que se ejecutará antes de cada prueba..
No olvides el
parent :: setUp ()
, como estamos sobrescribiendo el método de la clase padre.
/ ** * Preparación predeterminada para cada prueba * * / public function setUp () parent :: setUp (); // ¡No olvides esto! $ this-> prepareForTests ();
En este punto, app / tests / TestCase.php
Debe verse como el siguiente código. Recuérdalo crearAplicación
Se crea automáticamente por Laravel. No tienes que preocuparte por eso.
// app / tests / TestCase.php prepareForTests (); / ** * Crea la aplicación. * * @return Symfony \ Component \ HttpKernel \ HttpKernelInterface * / public function createApplication () $ unitTesting = true; $ testEnvironment = 'testing'; el retorno requiere __DIR __. '/… /… /start.php'; / ** * Migra la base de datos y configura la aplicación de correo para 'simular'. * Esto hará que las pruebas se ejecuten rápidamente. * / private function prepareForTests () Artisan :: call ('migrate'); Mail :: pretend (true);
Ahora, para escribir nuestras pruebas, simplemente extender Caso de prueba
, y la base de datos se inicializará y migrará antes de cada prueba.
Es correcto decir que, en este artículo, no seguiremos la TDD proceso. El problema aquí es didáctico, con el objetivo de demostrar cómo se pueden escribir las pruebas. Debido a esto, elegí revelar los modelos en cuestión primero, y luego sus pruebas relacionadas. Creo que esta es una mejor manera de ilustrar este tutorial..
El contexto de esta aplicación de demostración es un simple blog / CMS, que contiene usuarios (autenticación), publicaciones y páginas estáticas (que se muestran en el menú).
Tenga en cuenta que el modelo amplía la clase, Ardent, en lugar de Eloquent. Ardent es un paquete que facilita la validación al guardar el modelo (consulte la $ reglas
propiedad).
A continuación, tenemos la $ fábrica estática pública
array, que aprovecha el paquete FactoryMuff, para ayudar en la creación de objetos durante las pruebas.
Ambos Ardentx y FactoryMuff Están disponibles a través de Packagist y Compositor..
En nuestro Enviar
modelo, tenemos una relación con el Usuario
modelo, a través de la magia autor
método.
Finalmente, tenemos un método simple que devuelve la fecha, formateada como "día mes año".
// app / models / Post.php 'required', // Post tittle 'slug' => 'required | alpha_dash', // Url Post 'content' => 'required', // Post content (Markdown) 'author_id' => 'required | numeric' // Identificación del autor); / ** * Matriz utilizada por FactoryMuff para crear objetos de prueba * / public static $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text', 'author_id '=>' factory | User ', // Será el ID de un usuario existente.); / ** * Pertenece al usuario * / public function author () return $ this-> belongsTo ('User', 'author_id'); / ** * Obtener fecha de publicación formateada * * @return string * / public function postedAt () $ date_obj = $ this-> created_at; if (is_string ($ this-> created_at)) $ date_obj = DateTime :: createFromFormat ('Y-m-d H: i: s', $ date_obj); devuelve $ date_obj-> format ('d / m / Y');
Para mantener las cosas organizadas, he colocado la clase con el Enviar
pruebas modelo en app / tests / models / PostTest.php
. Pasaremos por todas las pruebas, una sección a la vez..
// app / tests / models / PostTest.phpExtendemos el
Caso de prueba
clase, que es un requisito para la prueba PHPUnit en Laravel. Además, no olvides nuestroPreparar Pruebas
Método que se ejecutará antes de cada prueba..función pública test_relation_with_author () // Crear una instancia, rellenar con valores, guardar y devolver $ post = FactoryMuff :: create ('Post'); // Gracias a FactoryMuff, este $ post tiene un autor $ this-> assertEquals ($ post-> author_id, $ post-> author-> id);Esta prueba es una "opcional". Estamos probando que la relación "
Enviar
pertenece aUsuario
". El propósito aquí es principalmente demostrar la funcionalidad de FactoryMuff.Una vez el
Enviar
clase tiene la$ fábrica
matriz estática que contiene'author_id' => 'factory | User'
(note el código fuente del modelo, que se muestra arriba) FactoryMuff crea una nueva instanciaUsuario
llena sus atributos, guarda en la base de datos y finalmente devuelve su id a laautor_id
atributo en elEnviar
.Para que esto sea posible, la
Usuario
modelo debe tener un$ fábrica
matriz que describe sus campos también.Observe cómo puede acceder al
Usuario
relación a través de$ post-> autor
. Como ejemplo, podemos acceder a la$ post-> autor-> nombre de usuario
, o cualquier otro atributo de usuario existente.El paquete FactoryMuff permite la creación rápida de instancias de objetos consistentes con el propósito de realizar pruebas, al mismo tiempo que respeta y crea las relaciones necesarias. En este caso, cuando creamos un
Enviar
conFactoryMuff :: create ('Publicar')
laUsuario
También será preparado y puesto a disposición..función pública test_posted_at () // Crear una instancia, rellenar con valores, guardar y devolver $ post = FactoryMuff :: create ('Post'); // Expresión regular que representa el patrón d / m / Y $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Verdadero si preg_match encuentra el patrón $ matches = (preg_match ($ expected, $ post-> postedAt ()))? verdadero Falso; $ this-> assertTrue ($ coincide);Para finalizar, determinamos si la cadena devuelta por el
publicado en()
El método sigue el formato "día / mes / año". Para tal verificación, se usa una expresión regular para probar si el patrón\ d 2 \ / \ d 2 \ / \ d 4
("2 números" + "barra" + "2 números" + "barra" + "4 números") es encontrado.Alternativamente, podríamos usar el comparador assertRegExp de PHPUnit.
En este punto, el
app / tests / models / PostTest.php
archivo es el siguiente:// app / tests / models / PostTest.php assertEquals ($ post-> author_id, $ post-> author-> id); función pública test_posted_at () // Crear una instancia, rellenar con valores, guardar y devolver $ post = FactoryMuff :: create ('Post'); // Expresión regular que representa el patrón d / m / Y $ expected = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Verdadero si preg_match encuentra el patrón $ matches = (preg_match ($ expected, $ post-> postedAt ()))? verdadero Falso; $ this-> assertTrue ($ coincide);PD: elegí no escribir el nombre de las pruebas en CamelCase para facilitar la lectura. PSR-1 perdóname, pero
testRelationWithAuthor
no es tan legible como lo prefiero personalmente. Eres libre de usar el estilo que más prefieras, por supuesto.Modelo de página
Nuestro CMS necesita un modelo para representar páginas estáticas. Este modelo se implementa de la siguiente manera:
'required', // Título de la página 'slug' => 'required | alpha_dash', // Slug (url) 'content' => 'required', // Content (markdown) 'author_id' => 'required | numeric' , // Identificación del autor); / ** * Array utilizado por FactoryMuff * / public static $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text', 'author_id' => ' factory | User ', // Será el ID de un usuario existente.); / ** * Pertenece al usuario * / public function author () return $ this-> belongsTo ('User', 'author_id'); / ** * Representa el menú usando caché * * @return string Html para enlaces de páginas. * / public static function renderMenu () $ pages = Cache :: rememberForever ('pages_for_menu', function () return Page :: select (array ('title', 'slug')) -> get () -> toArray ();); $ result = "; foreach ($ pages as $ page) $ result. = HTML :: action ('PagesController @ show', $ page ['title'], ['slug' => $ page ['slug'] ]). | '; return $ resultado; / ** * Olvidar caché cuando se guarda * / función pública después de Guardar ($ éxito) si ($ éxito) Caché :: olvidar (' pages_for_menu '); / ** * Olvidar caché cuando eliminado * / public function delete () parent :: delete (); Cache :: forget ('pages_for_menu');Podemos observar que el método estático.,
renderMenu ()
, hace una serie de enlaces para todas las páginas existentes. Este valor se guarda en la clave de caché.,'pages_for_menu'
. De esta manera, en futuras llamadas arenderMenu ()
, No habrá necesidad de golpear la base de datos real. Esto puede proporcionar mejoras significativas en el rendimiento de nuestra aplicación..Sin embargo, si un
Página
se guarda o se elimina (afterSave ()
yborrar()
métodos), el valor de la memoria caché se borrará, lo que provocará larenderMenu ()
para reflejar el nuevo estado de la base de datos. Por lo tanto, si se cambia el nombre de una página, o si se elimina, elclave 'pages_for_menu'
se borra de la caché. (Cache :: forget ('pages_for_menu');
)NOTA: El método,
afterSave ()
, Está disponible a través del paquete Ardent. De lo contrario, sería necesario implementar elsalvar()
método para limpiar el caché y llamarpadre :: guardar ()
;Pruebas de página
En:
app / tests / models / PageTest.php
, Vamos a escribir las siguientes pruebas:assertEquals ($ page-> author_id, $ page-> author-> id);Una vez más, tenemos una prueba "opcional" para confirmar la relación. Como las relaciones son responsabilidad de
Iluminar \ Base de datos \ Elocuente
, que ya está cubierto por las propias pruebas de Laravel, no necesitamos escribir otra prueba para confirmar que este código funciona como se espera.función pública test_render_menu () $ pages = array (); para ($ i = 0; $ i < 4; $i++) $pages[] = FactoryMuff::create('Page'); $result = Page::renderMenu(); foreach ($pages as $page) // Check if each page slug(url) is present in the menu rendered. $this->assertGreaterThan (0, strpos ($ result, $ page-> slug)); // Comprueba si el caché se ha escrito $ this-> assertNotNull (Cache :: get ('pages_for_menu'));Esta es una de las pruebas más importantes para el
Página
modelo. En primer lugar, se crean cuatro páginas en elpara
lazo. A continuación, el resultado de larenderMenu ()
llamada se almacena en el$ resultado
variable. Esta variable debe contener una cadena HTML que contenga enlaces a las páginas existentes.los
para cada
el bucle comprueba si el slug (url) de cada página está presente en$ resultado
. Esto es suficiente, ya que el formato exacto del HTML no es relevante para nuestras necesidades.Finalmente, determinamos si la clave de caché,
pages_for_menu
, tiene algo almacenado. En otras palabras, hizo elrenderMenu ()
llamada en realidad guardó algún valor en el caché?función pública test_clear_cache_after_save () // Un valor de prueba se guarda en caché Cache :: put ('pages_for_menu', 'avalue', 5); // Esto debería limpiar el valor en cache $ page = FactoryMuff :: create ('Page'); $ this-> assertNull (Cache :: get ('pages_for_menu'));Esta prueba tiene como objetivo verificar si, al guardar un nuevo
Página
, la clave de caché'pages_for_menu'
se vacía losFactoryMuff :: create ('Page');
eventualmente desencadena lasalvar()
método, por lo que debería ser suficiente para la clave,'pages_for_menu'
, ser borrado.función pública test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Se guarda un valor de prueba en caché Cache :: put ('pages_for_menu', 'valor', 5); // Esto debería limpiar el valor en caché $ page-> delete (); $ this-> assertNull (Cache :: get ('pages_for_menu'));Similar a la prueba anterior, esta determina si la clave
'pages_for_menu'
se vacía correctamente después de eliminar unPágina
.Tu
PageTest.php
debe verse así:assertEquals ($ page-> author_id, $ page-> author-> id); función pública test_render_menu () $ pages = array (); para ($ i = 0; $ i < 4; $i++) $pages[] = FactoryMuff::create('Page'); $result = Page::renderMenu(); foreach ($pages as $page) // Check if each page slug(url) is present in the menu rendered. $this->assertGreaterThan (0, strpos ($ result, $ page-> slug)); // Comprueba si el caché se ha escrito $ this-> assertNotNull (Cache :: get ('pages_for_menu')); función pública test_clear_cache_after_save () // Un valor de prueba se guarda en caché Cache :: put ('pages_for_menu', 'avalue', 5); // Esto debería limpiar el valor en cache $ page = FactoryMuff :: create ('Page'); $ this-> assertNull (Cache :: get ('pages_for_menu')); función pública test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Se guarda un valor de prueba en caché Cache :: put ('pages_for_menu', 'valor', 5); // Esto debería limpiar el valor en caché $ page-> delete (); $ this-> assertNull (Cache :: get ('pages_for_menu'));Modelo de usuario
En relación con los modelos presentados anteriormente, ahora tenemos la
Usuario
. Aquí está el código para ese modelo:'string', 'email' => 'email', 'password' => '123123', 'password_confirmation' => '123123',); / ** * Tiene muchas páginas * / páginas de función pública () return $ this-> hasMany ('Page', 'author_id'); / ** * Tiene muchas publicaciones * / public function posts () return $ this-> hasMany ('Post', 'author_id');Este modelo está ausente de pruebas..
Podemos observar que, con la excepción de las relaciones (que pueden ser útiles para probar), no hay ninguna implementación de métodos aquí. ¿Qué pasa con la autenticación? Bueno, el uso del paquete Confide ya proporciona la implementación y las pruebas para esto..
Las pruebas para
Zizaco \ Confide \ ConfideUser
se encuentran en ConfideUserTest.php.Es importante determinar las responsabilidades de la clase antes de escribir sus exámenes. Probando la opción de "restablecer la contraseña" de un
Usuario
sería redundante Esto se debe a que la responsabilidad adecuada de esta prueba está dentro deZizaco \ Confide \ ConfideUser
; no enUsuario
.Lo mismo es cierto para las pruebas de validación de datos. Como el paquete, Ardent, maneja esta responsabilidad, no tendría mucho sentido probar la funcionalidad nuevamente.
En breve: Mantenga sus pruebas limpias y organizadas. Determine la responsabilidad apropiada de cada clase y pruebe solo cuál es estrictamente su responsabilidad.
Conclusión
El uso de una base de datos en memoria es una buena práctica para ejecutar pruebas contra una base de datos rápidamente. Gracias a la ayuda de algunos paquetes, como Ardent, FactoryMuff y Confide, puede minimizar la cantidad de código en sus modelos, mientras mantiene las pruebas limpias y objetivas..
En la secuela de este artículo, revisaremos. Controlador pruebas. Manténganse al tanto!
Aún comenzando con Laravel 4, permítanos enseñarle lo esencial!