Symfony 1.0, la guía definitiva

15.5. Otras utilidades para pruebas

Las herramientas que incluye Symfony para realizar pruebas unitarias y funcionales son suficientes para la mayoría de casos. No obstante, se muestran a continuación algunas técnicas adicionales para resolver problemas comunes con las pruebas automatizadas: ejecutar pruebas en un entorno independiente, acceder a la base de datos desde las pruebas, probar la cache y realizar pruebas de las interacciones en el lado del cliente.

15.5.1. Ejecutando las pruebas en grupos

Las tareas test-unit y test-functional ejecutan una sola prueba o un conjunto de pruebas. Sin embargo, si se ejecutan las tareas sin indicar ningún parámetro, se lanzan todas las pruebas unitarias y funcionales del directorio test/. Para evitar el riesgo de intereferencias de unas pruebas a otras, cada prueba se ejecuta en un entorno de ejecución independiente. Además, cuando se ejecutan todas las pruebas, el resultado que se muestra no es el mismo que el que genera cada prueba de forma independiente, ya que en este caso la salida estaría formada por miles de líneas. Lo que se hace es generar una salida resumida especialmente preparada. Por este motivo, la ejecución de un gran número de pruebas utiliza un test harness, que es un framework de pruebas con algunas características especiales. El test harness depende de un componente del framework Lime llamado lime_harness. Como se muestra en el listado 15-31, la salida producida indica el estado de las pruebas archivo por archivo y al final se muestra un resumen de todas las pruebas que se han pasado y el número total de pruebas.

Listado 15-31 - Ejecutando todas las pruebas mediante el test harness

> symfony test-all

unit/miFuncionTest.php.................ok
unit/miSegundaFuncionTest.php..........ok
unit/foo/barTest.php...................not ok

Failed Test                     Stat  Total   Fail  List of Failed
------------------------------------------------------------------
unit/foo/barTest.php               0      2      2  62 63
Failed 1/3 test scripts, 66.66% okay. 2/53 subtests failed, 96.22% okay.

Las pruebas se ejecutan de la misma forma que si se lanzaran una a una, solamente es la salida la que se resume para hacerla más útil. De hecho, la estadística final se centra en las pruebas que no han tenido éxito y ayuda a localizarlas.

Incluso es posible lanzar todas las pruebas de cualquier tipo mediante la tarea test-all, que también hace uso del test harness, como se muestra en el listado 15-32. Una buena práctica consiste en ejecutar esta tarea antes de realizar el paso a producción del nuevo código, ya que asegura que no se ha introducido ningún nuevo error desde la versión anterior.

Listado 15-32 - Ejecutando todas las pruebas de un proyecto

> symfony test-all

15.5.2. Acceso a la base de datos

Normalmente, las pruebas unitarias necesitan acceder a la base de datos. Cuando se llama al método sfTestBrowser::get() por primera vez, se inicializa una conexión con la base de datos. No obstante, si se necesita acceder a la base de datos antes de utilizar sfTestBrowser, se debe inicializar el objeto sfDabataseManager a mano, como muestra el listado 15-33.

Listado 15-33 - Inicializando la base de datos en una prueba

$databaseManager = new sfDatabaseManager();
$databaseManager->initialize();

// Opcionalmente, se puede obtener la conexión con la base de datos
$con = Propel::getConnection();

Antes de comenzar las pruebas, se suele cargar la base de datos con datos de prueba, también llamados fixtures. El objeto sfPropelData permite realizar esta carga. No solamente es posible utilizar este objeto para cargar datos a partir de un archivo (como con la tarea propel-load-data) sino que también es posible hacerlo desde un array, como muestra el listado 15-34.

Listado 15-34 - Cargando datos en la base de datos desde una prueba

$data = new sfPropelData();

// Cargar datos desde un archivo
$data->loadData(sfConfig::get('sf_data_dir').'/fixtures/test_data.yml');

// Cargar datos desde un array
$fixtures = array(
  'Article' => array(
    'article_1' => array(
      'title'      => 'foo title',
      'body'       => 'bar body',
      'created_at' => time(),
    ),
    'article_2'    => array(
      'title'      => 'foo foo title',
      'body'       => 'bar bar body',
      'created_at' => time(),
    ),
  ),
);
$data->loadDataFromArray($fixtures);

Una vez cargados los datos, se pueden utilizar los objetos Propel necesarios como en cualquier aplicación normal. Las pruebas unitarias deben incluir los archivos correspondientes a esos objetos (se puede utilizar el método sfCore::initSimpleAutoload() para automatizar la carga, como se explicó en la sección anterior "Stubs, Fixtures y carga automática de clases"). Los objetos de Propel se cargan automáticamente en las pruebas funcionales.

15.5.3. Probando la cache

Cuando se habilita la cache para una aplicación, las pruebas funcionales se encargan de verificar que las acciones guardadas en la cache se comportan como deberían.

En primer lugar, se habilita la cache para el entorno de pruebas (en el archivo settings.yml). Una vez habilitada, se puede utilizar el método isCached() del objeto sfTestBrowser para comprobar si una página se ha obtenido directamente de la cache o ha sido generada en ese momento. El listado 15-35 muestra cómo utilizar este método.

Listado 15-35 - Probando la cache con el método isCached()

<?php

include(dirname(__FILE__).'/../../bootstrap/functional.php');

// Create a new test browser
$b = new sfTestBrowser();
$b->initialize();

$b->get('/mymodule');
$b->isCached(true);       // Comprueba si la respuesta viene de la cache
$b->isCached(true, true); // Comprueba si la respuesta de la cache incluye el layoutlayout
$b->isCached(false);      // Comprueba que la respuesta no venga de la cache

Nota No es necesario borrar la cache antes de realizar la prueba funcional, ya que el proceso de arranque utilizado por la prueba se encarga de hacerlo automáticamente.

15.5.4. Probando las interacciones en el lado del cliente

El principal inconveniente de las técnicas descritas anteriormente es que no pueden simular el comportamiento de JavaScript. Si se definen interacciones muy complejas, como por ejemplo interacciones con Ajax, es necesario reproducir de forma exacta los movimientos del ratón y las pulsaciones de teclado que realiza el usuario y ejecutar los scripts de JavaScript. Normalmente, estas pruebas se hacen a mano, pero cuestan mucho tiempo y son propensas a cometer errores.

La solución a estos problemas se llama Selenium (http://www.openqa.org/selenium/), que consiste en un framework de pruebas escrito completamente en JavaScript. Selenium permite realizar una serie de acciones en la página de la misma forma que las haría un usuario normal. La ventaja de Selenium sobre el objeto sfBrowser es que Selenium es capaz de ejecutar todo el código JavaScript de la página, incluidas las interacciones creadas con Ajax.

Symfony no incluye Selenium por defecto. Para instalarlo, se crea un directorio llamado selenium/ en el directorio web/ del proyecto y se descomprime el contenido del archivo descargado desde http://www.openqa.org/selenium-core/download.action. Como Selenium se basa en JavaScript y la mayoría de navegadores tienen unas restricciones de seguridad muy estrictas, es importante ejecutar Selenium desde el mismo servidor y el mismo puerto que el que utiliza la propia aplicación.

Nota Debe ponerse especial cuidado en no subir el directorio selenium/ al servidor de producción, ya que estaría disponible para cualquier usuario que acceda a la raíz del servidor desde un navegador web.

Las pruebas de Selenium se escriben en HTML y se guardan en el directorio web/selenium/tests/. El listado 15-36 muestra un ejemplo de prueba funcional en la que se carga la página principal, se pulsa el enlace "pinchame" y se busca el texto "Hola Mundo" en el contenido de la respuesta. Para acceder a la aplicación en el entorno test, se debe utilizar el controlador frontal llamado miaplicacion_test.php.

Listado 15-36 - Un ejemplo de prueba de Selenium, en web/selenium/test/testIndex.html

<!DOCTYPE html PUBLIC " *W3C*DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <meta content="text/html; charset=UTF-8" http-equiv="content-type">
  <title>Index tests</title>
</head>
<body>
<table cellspacing="0">
<tbody>
  <tr><td colspan="3">First step</td></tr>
  <tr><td>open</td>              <td>/miaplicacion_test.php/</td> <td>&nbsp;</td></tr>
  <tr><td>clickAndWait</td>      <td>link=pinchame</td>    <td>&nbsp;</td></tr>
  <tr><td>assertTextPresent</td> <td>Hola Mundo</td>    <td>&nbsp;</td></tr>
</tbody>
</table>
</body>
</html>

Cada caso de prueba consiste en una página HTML con una tabla de 3 columnas: comando, destino y valor. No obstante, no todos los comandos indican un valor. En caso de que no se utilice un valor, es recomendable incluir el valor &nbsp; en esa columna (para que la tabla se vea mejor). El sitio web de Selenium dispone de la lista completa de comandos que se pueden utilizar.

También es necesario añadir esta prueba al conjunto completo de pruebas, insertando una nueva línea en la tabla del archivo TestSuite.html del mismo directorio. El listado 15-37 muestra cómo hacerlo.

Listado 15-37 - Añadiendo un archivo de pruebas al conjunto de pruebas, en web/selenium/test/TestSuite.html

...
<tr><td><a href='./testIndex.html'>Mi primera prueba</a></td></tr>
...

Para ejecutar la prueba, solamente es necesario acceder a la página:

http://miaplicacion.ejemplo.com/selenium/index.html

Si se selecciona la "Main Test Suite" y se pulsa sobre el botón de ejecutar todas las pruebas, el navegador reproducirá automáticamente todos los pasos que se han indicado.

Nota Como las pruebas de Selenium se ejecutan en el propio navegador, permiten descubrir las inconsistencias entre navegadores. Si se construye la prueba para un solo navegador, se puede lanzar esa prueba sobre todos los navegadores y comprobar su funcionamiento.

Como las pruebas de Selenium se crean con HTML, acaba siendo muy aburrido escribir todo ese código HTML. Afortunadamente, existe una extensión de Selenium para Firefox (http://seleniumrecorder.mozdev.org/) que permite grabar todos los movimientos y acciones realizadas sobre una página y guardarlos como una prueba. Mientras se graba una sesión de navegación, se pueden añadir pruebas de tipo asertos pulsando el botón derecho sobre la ventana del navegador y seleccionando la opción apropiada del menún "Append Selenium Command".

Una vez realizados todos los movimientos y añadidos todos los comandos, se pueden guardar en un archivo HTML para añadirlo al conjunto de pruebas. La extensión de Firefox incluso permite ejecutar las pruebas de Selenium que se han creado con la extensión.

Nota No debe olvidarse reinicializar los datos de prueba antes de lanzar cada prueba de Selenium.