Tutorial Jobeet con Symfony2 Día 9: Pruebas Funcionales

De WikiSalud
Saltar a: navegación, buscar
Este artículo es parte del proyecto Tutorial Jobeet con Symfony2

Las pruebas funcionales son una gran herramienta para probar tu aplicación de principio a fin: De la petición hecha por una navegador a la respuesta enviada por el servidor. Ellos prueban todas las capas de una aplicación: Rutas, Modelo, Acciones y Plantillas. Son muy similiares a lo que probablemente hayas hecho manualmente: Cada vez que añades o modificas una acción, necesitas ir al navegador y comprobar que cada cosa trabaja como se espera haciendo clicks en enlaces y comprobando elementos en la página renderizada. En otras palabras, corres un escenario correspondiente a un caso que recién has implementado.

Como el proceso es manual, es tedioso y propenso a errores. Cada vez que cambias algo en tu código, debes andar a tráves de todos los escenarios para asegurarse que no rompas nada. Eso es una locura. Los test funcionales en symfony proveen una forma para describir escenarios fácilmente. Cada escenarioa puede ser ejecutado automáticamente una y otra vez para simular la experiencia de un usuario en un navegador. Como las pruebas unitarias, te dan la confianza para codificar en paz

Los test funcionales tiene una flujo de trabajo muy específico:

  • Hacen una petición
  • Prueba la respuesta:
  • Hacen click en un enlace o envían un formulario
  • Prueban la respuesta
  • Aclaran y repiten

Contenido

Nuestro primer test funcional

Test funcionales son simples archivos PHP que tipicamente residen en el directorio Tests/Controller de tu bundle. Si tu quieres probar la página manejada por tu clase CategoryController, inicia creando un nuevo archivo CategoryControllerTest.php que extienda de la clase especial WebTestCase:

src/Ens/JobeetBundle/Teste/Controller/CategoryControllerTest.php
// src/Ens/JobeetBundle/Teste/Controller/CategoryControllerTest.php
namespace Ens\JobeetBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
 
class CategoryControllerTest extends WebTestCase
{
  public function testShow()
  {
    $client = static::createClient();
 
    $crawler = $client->request('GET', '/category/index');
    $this->assertEquals('Ens\JobeetBundle\Controller\CategoryController::showAction', $client->getRequest()->attributes->get('_controller'));
    $this->assertTrue(200 === $client->getResponse()->getStatusCode());
  }
}

Corriendo test funcionales

Así como las pruebas unitarias, pueden correrse test funcionales ejecutando el comando phpunit:

phpunit -c app/ src/Ens/JobeetBundle/Tests/Controller/CategoryControllerTest

Este test fallará debido a que la url testeada, /categoy/index, no es una url válida en Jobeet:

PHPUnit 3.6.10 by Sebastian Bergmann.
 
Configuration read from /home/dragos/work/jobeet/app/phpunit.xml.dist
 
F
 
Time: 2 seconds, Memory: 14.25Mb
 
There was 1 failure:
 
1) Ens\JobeetBundle\Tests\Controller\CategoryControllerTest::testShow
Failed asserting that false is true.

Escribiendo test funcionales

Escribir test funciones es como ejecutar un escenario en una navegador. Ya hemos escrito todos los escenarios que necesitamos para probar como parte de los casos del día 2

Primero, testearemos la página principal Jobeet editando el archivo de test JobControllerTest.php. Reemplaza el código por el siguiente:

Ofertas de trabajo expiradas no son listados

src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
namespace Ens\JobeetBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
 
class JobControllerTest extends WebTestCase
{
  public function testIndex()
  {
    $client = static::createClient();
 
    $crawler = $client->request('GET', '/');
    $this->assertEquals('Ens\JobeetBundle\Controller\JobController::indexAction', $client->getRequest()->attributes->get('_controller'));
    $this->assertTrue($crawler->filter('.jobs td.position:contains("Expired")')->count() == 0);
  }
}


Para verificar la exclusión de ofertas caducas en la página principal, verificamos el selector CSS .job td.position:contains("Expired") no coincide en ninguna parte dentro del contenido de la respuesta HTML. (Recuerda que en la fixtuers, sólo las ofertas de trabajo caducas contienen "Expired" en la posición).

Sólo N trabajos son listados por categoría

Añade el siguiente código al final de el archivo de test. Para obtener el parametos personalizado definido en app/config/parameters.yml en nuestros test funcionales, usamos el kernel.

src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// ...
 
$kernel = static::createKernel();
$kernel->boot();
$max_jobs_on_homepage = $kernel->getContainer()->getParameter('max_jobs_on_homepage');
$this->assertTrue($crawler->filter('.category_programming tr')->count() == $max_jobs_on_homepage);
</php>


Para hacer que este test trabajo necesitamos añadir la clase CSS correspondiente en cada categoría en el archivo Job/index.html.twig (Así podemos seleccionar cada categoría y contar todos las ofertas de trabajo listadas):

src/Ens/JobeetBundle/Resources/views/Job/index.html.twig
<!-- src/Ens/JobeetBundle/Resources/views/Job/index.html.twig -->
<!-- ... -->
 
    {% for category in categories %}
<div class="category_{{ category.slug }}"><!-- ... -->


Una categoría tiene una enlace a la página de categoría solo si tiene muchas ofertas de trabajo

src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// ...
 
$this->assertTrue($crawler->filter('.category_design .more_jobs')->count() == 0);
$this->assertTrue($crawler->filter('.category_programming .more_jobs')->count() == 1);
// ...


En estos test, verificamos que no hay un enlace More Jobs para la categoría diseño (.category_design .more_jobs no existe), y que hay un enlace more jobs para la categoría programming (.category_programming .more_jobs existe).

Ofertas de trabajo son ordenadas por fecha

Para probar si las ofertas son realmente ordenados por fecha, necesitamos verificar que la primer oferta listada en la página principal es el que esperamos. Esto puede ser hecho verificando que la URL contiene la llave primaria esperada. Como la llave primaria puede cambiar entre ejecuciones, necesitamos obtener el objeto Doctrine de la base de datos primero

Tengo un pequeño problema con respecto a la guía original. En JobRepository:getActiveJobs se especifica que se orden contra j.expires_at, y no contra el j.created_at que se especifica en la consulta abajo en la Prueba.
Y sin embargo, he estado suponiendo que se refiere a las fechas en los datos de prueba.
Espero que quede como una referencia para el futuro


src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// ...
 
$kernel = static::createKernel();
$kernel->boot();
$em = $kernel->getContainer()->get('doctrine.orm.entity_manager');
 
$query = $em->createQuery('SELECT j from EnsJobeetBundle:Job j LEFT JOIN j.category c WHERE c.slug = :slug AND j.expires_at > :date ORDER BY j.expires_at DESC');
$query->setParameter('slug', 'programming');
$query->setParameter('date', date('Y-m-d H:i:s', time()));
$query->setMaxResults(1);
$job = $query->getSingleResult();
 
$this->assertTrue($crawler->filter('.category_programming tr')->first()->filter(sprintf('a[href*="/%d/"]', $job->getId()))->count() == 1);
// ...


Incluso si el test trabaja como esta, necesitamos refactorizar un poco, de tal forma que obtener la primera oferta de trabajo para la categoría programming puede ser reutilizada en cualquier parte de nuestros test. No debemos mover el código a la capa Modelo ya que el código es demasiado específico. En lugar de eso, moveremos el código a la función getMostRecentProgrammingJob en nuestra clase test

src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// ...
 
public function getMostRecentProgrammingJob()
{
  $kernel = static::createKernel();
  $kernel->boot();
  $em = $kernel->getContainer()->get('doctrine.orm.entity_manager');
 
  $query = $em->createQuery('SELECT j from EnsJobeetBundle:Job j LEFT JOIN j.category c WHERE c.slug = :slug AND j.expires_at > :date ORDER BY j.expires_at DESC');
  $query->setParameter('slug', 'programming');
  $query->setParameter('date', date('Y-m-d H:i:s', time()));
  $query->setMaxResults(1);
  return $query->getSingleResult();
}
 
// ...


Ahora puedes reemplazar el codigo de test previo por el siguiente:

src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// ...
 
$this->assertTrue($crawler->filter('.category_programming tr')->first()->filter(sprintf('a[href*="/%d/"]', $this->getMostRecentProgrammingJob()->getId()))->count() == 1);
// ...


Cada oferta de trabajo en la página principal tiene enlace

Para testear los enlaces de las ofertas de trabajo en la página principla, simulamos un click en el texto Web Developer. Así que como hay muchos de ellos en la página, tenemos que pedirle explicitamente al navegador que haga click en el primero. Cada parametro de la petición es luego testeado para asegurarse que el ruteo ha sido echo su trabajo correctamente

src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// ...
 
$job = $this->getMostRecentProgrammingJob();
$link = $crawler->selectLink('Web Developer')->first()->link();
$crawler = $client->click($link);
$this->assertEquals('Ens\JobeetBundle\Controller\JobController::showAction', $client->getRequest()->attributes->get('_controller'));
$this->assertEquals($job->getCompanySlug(), $client->getRequest()->attributes->get('company'));
$this->assertEquals($job->getLocationSlug(), $client->getRequest()->attributes->get('location'));
$this->assertEquals($job->getPositionSlug(), $client->getRequest()->attributes->get('position'));
$this->assertEquals($job->getId(), $client->getRequest()->attributes->get('id'));
// ...


Aprender por el ejemplo

En esta sección, tenemos todo el código que necesitamos para testear la página job y category. Lee el código cuidadosamente y así tu puede aprender algunos trucos nuevos:

src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
// src/Ens/JobeetBundle/Test/Controller/JobControllerTest.php
 
namespace Ens\JobeetBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
 
class CategoryControllerTest extends WebTestCase
{
  public function testShow()
  {
    // get the custom parameters from app config.yml
    $kernel = static::createKernel();
    $kernel->boot();
    $max_jobs_on_homepage = $kernel->getContainer()->getParameter('max_jobs_on_homepage');
    $max_jobs_on_category = $kernel->getContainer()->getParameter('max_jobs_on_category');
 
    $client = static::createClient();
 
    // categories on homepage are clickable
    $crawler = $client->request('GET', '/');
    $link = $crawler->selectLink('Programming')->link();
    $crawler = $client->click($link);
    $this->assertEquals('Ens\JobeetBundle\Controller\CategoryController::showAction', $client->getRequest()->attributes->get('_controller'));
    $this->assertEquals('programming', $client->getRequest()->attributes->get('slug'));
 
    // categories with more than $max_jobs_on_homepage jobs also have a "more" link
    $crawler = $client->request('GET', '/');
    $link = $crawler->selectLink('22')->link();
    $crawler = $client->click($link);
    $this->assertEquals('Ens\JobeetBundle\Controller\CategoryController::showAction', $client->getRequest()->attributes->get('_controller'));
    $this->assertEquals('programming', $client->getRequest()->attributes->get('slug'));
 
    // only $max_jobs_on_category jobs are listed
    $this->assertTrue($crawler->filter('.jobs tr')->count()==$max_jobs_on_category);
    $this->assertRegExp('/32 jobs/', $crawler->filter('.pagination_desc')->text());
    $this->assertRegExp('/page 1\/2/', $crawler->filter('.pagination_desc')->text());
 
 
    $link = $crawler->selectLink('2')->link();
    $crawler = $client->click($link);
    $this->assertEquals(2, $client->getRequest()->attributes->get('page'));
    $this->assertRegExp('/page 2\/2/', $crawler->filter('.pagination_desc')->text());
  }
}

src/Ens/JobeetBundle/Test/Controller/CategoryControllerTest.php
// src/Ens/JobeetBundle/Test/Controller/CategoryControllerTest.php
 
namespace Ens\JobeetBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
 
class CategoryControllerTest extends WebTestCase
{
  public function testShow()
  {
    // get the custom parameters from app config.yml
    $kernel = static::createKernel();
    $kernel->boot();
    $max_jobs_on_homepage = $kernel->getContainer()->getParameter('max_jobs_on_homepage');
    $max_jobs_on_category = $kernel->getContainer()->getParameter('max_jobs_on_category');
 
    $client = static::createClient();
 
    // categories on homepage are clickable
    $crawler = $client->request('GET', '/');
    $link = $crawler->selectLink('Programming')->link();
    $crawler = $client->click($link);
    $this->assertEquals('Ens\JobeetBundle\Controller\CategoryController::showAction', $client->getRequest()->attributes->get('_controller'));
    $this->assertEquals('programming', $client->getRequest()->attributes->get('slug'));
 
    // categories with more than $max_jobs_on_homepage jobs also have a "more" link
    $crawler = $client->request('GET', '/');
    $link = $crawler->selectLink('22')->link();
    $crawler = $client->click($link);
    $this->assertEquals('Ens\JobeetBundle\Controller\CategoryController::showAction', $client->getRequest()->attributes->get('_controller'));
    $this->assertEquals('programming', $client->getRequest()->attributes->get('slug'));
 
    // only $max_jobs_on_category jobs are listed
    $this->assertTrue($crawler->filter('.jobs tr')->count() assertRegExp('/32 jobs/', $crawler->filter('.pagination_desc')->text());
    $this->assertRegExp('/page 1\/2/', $crawler->filter('.pagination_desc')->text());
 
    $link = $crawler->selectLink('2')->link();
    $crawler = $client->click($link);
    $this->assertEquals(2, $client->getRequest()->attributes->get('page'));
    $this->assertRegExp('/page 2\/2/', $crawler->filter('.pagination_desc')->text());
  }
}

Herramientas personales
Espacios de nombres

Variantes
Acciones
Navegación
Herramientas