sábado, 22 de octubre de 2011

Cómo implementar una prueba con PHPUnit en Symfony2, utilizando carga automática de datos.


Hola a todos:
Cuando leemos el manual de Symfony2, explica en el epígrafe “3.35.2 Probando la funcionalidad ” como acceder al núcleo del framework para ejecutar consultas reales en la base de datos y de esta forma poder hacer pruebas automáticas. El problema está que para poder hacer pruebas válidas necesitamos tener una base de datos llena con datos limpios y conocidos, que nos permita predecir el comportamiento y de esta manera poder crear las pruebas.
En symofny 1 cuando creabamos una prueba utilizando el método “loadData ” de la clase “sfDoctrineData(); ” logramos cargar datos limpios para nuestras pruebas. Lamentablemente por lo que se ve en la documentación no hay nada parecido a esto en Symfony2. Por lo tanto en la presente entrada de este blog les comento como implementar esta característica con Symfony2.
  1. Primero debemos tener instalado en nuestro proyecto el bundle “StofDoctrineExtensionsBundle” y crear los fixtures según la documentación de este bundle.

  2. Configurar una conexión a la base de datos para el entorno de prueba. Debemos utilizar una base de datos distinta a la de desarrollo pues esta base de datos se va a cargar con datos nuevos cada vez que se haga una prueba que lo necesite.

    # Doctrine Configuration, fichero config/config_test.yml
    doctrine:
        dbal:
            driver:   %database_driver%
            host:     %database_host%
            port:     %database_port%
            dbname:   base_datos_test  #aquí se pone el nombre de la base de datos de prueba
            user:     %database_user%
            password: %database_password%
            charset:  UTF8
    
        orm:
            auto_generate_proxy_classes: %kernel.debug%
            auto_mapping: true
    

  3. Luego tenemos que crear la base de datos de prueba.
    php app/console doctrine:database:create --env=test
    

  4. Creamos la estructura de la base de datos.
    php app/console doctrine:schema:create --env=test
    

  5. Cada vez que se haga un cambio en el modelo de nuestro sistema, se creen nuevas tablas o se modifique alguna de las existentes, debemos ejectuar el siguiente comando para actualizar nuestra base de datos de prueba.
    app/console doctrine:schema:update --env=test
    

  6. Luego de esto lo que queda es crear las pruebas. Veamos el siguiente ejemplo.

Supongamos que tenemos una clase “User” y queremos comprobar es si la página de listado muestra de forma correcta todos los registros. Para esto tenemos creados unos fixtures que registra 3 usuarios en la base de datos. Supongamos que para ver el listado debemos acceder a la siguiente ruta “/users” y en esta página me genera una página html como esta.
<table class="user_list_test">
  <tr>
    <td>usuario 1</td>
  </tr>
  <tr>
    <td>usuario 2</td>
  </tr>
  <tr>
    <td>usuario 3</td>
  </tr>
</table>

A la tabla le ponemos como clase “user_list_test” para poder accederla desde nuestra prueba. Luego creamos una prueba con el siguiente código.
<?php

namespace Acme\UserBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader as DataFixturesLoader;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;

class UserControllerTest extends WebTestCase
{
    /**
    * @var \Doctrine\ORM\EntityManager
    */
    private $em;
    private $container;
    private $current_kernel;
    
    public function setUp()
    {        
        $kernel = static::createKernel();
        $kernel->boot();
        
        $this->current_kernel = $kernel;        
        $this->container      = $this->current_kernel->getContainer();
        $this->em             = $this->container->get('doctrine.orm.entity_manager');
    }
    public function testLoadData()
    {
        $paths = array();
        foreach ($this->current_kernel->getBundles() as $bundle) {
            $paths[] = $bundle->getPath().'/DataFixtures/ORM';
        }
        $loader = new DataFixturesLoader($this->container);
        foreach ($paths as $path) {
            if (is_dir($path)) {
                $loader->loadFromDirectory($path);
            }
        }
        $fixtures = $loader->getFixtures();
        if (!$fixtures) {
            throw new InvalidArgumentException(
                'No hay fixtures para cargar'
            );
        }
        $purger = new ORMPurger($this->em);
        $purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
        $executor = new ORMExecutor($this->em, $purger);
        $executor->execute($fixtures);
    }
    /**
     * @depends testLoadData
     */
    public function testIndex()
    {
        $client = static::createClient();

        $crawler = $client->request('GET', '/users');

        $this->assertTrue($crawler->filter('table.user_list_test  tr')->count() == 3);
    }
}

Acontinuación una explicación del código.

El método “setUp” se encarga de inicializar la conexión con la base de datos igual a como viene explicado en el epígrafe “3.35.2 Probando la funcionalidad ” del manual de Symfony.
El método “testLoadData” es el encargado de cargar los datos de los fixtures. Este método hace el mismo procedimiento que el comando “doctrine:fixtures:load” del bundle “StofDoctrineExtensionsBundle”.
El método “testIndex” es el que realmente hace la prueba, contando la cantidad de filas (tr) que tiene la tabla con class “user_list_test” que deben ser exactamente 3.
La anotación “@depends testLoadData” le indica a PHPUnit que antes de ejecutar la prueba “testIndex” primero debe ejecutar el métdo “testLoadData” que es el encargado de cargar los datos.
Como siempre expero que este artículo sea de su interés y espero comentarios.

viernes, 14 de octubre de 2011


Cómo acceder a Githup a través de Your freedom.

Cuando se accede a internet detrás de un proxy. En muchas ocaciones este no permite las conexiones a tráves del puerto 22. Esto implica que no sea sencillo acceder a Github. En estos casos la mejor herramienta que conozco es Your Freedom que es un antiproxy que nos permite acceder libremente a internet. En esta entrada les voy a comentar cómo lograr que git se conecte a Github a través de Yourfreedom.
Para subir y bajar el código, la herramienta git lo que hace es conectarse por ssh a la ubicación donde está el repositorio. Para lograrlo debemos crear un tunel a través de Yourfreedom. Los pasos son los siguientes.
Nota: Hasta el punto 2 puede encontrar una explicación más detallada en el sitio. http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=githubFirstStepsUploadProject
  1. Generar una nueva clave con el siguiente comando. Esto debe crear una llave pública y otra privada en el directorio “/home/user/.ssh”
    ssh-keygen -t rsa -C "your_email@yourcompany.com"
  2. Luego accedemos al sitio Github "Account Settings" > "SSH Public Keys" > "Add another public key" y ponemos la llave pública.
  3. El siguiente pado es crear un tunel con el Yourfreedom. Ir a “Puertos” y adicionar un “Redireccionamiento local de puerto” El la ventana emergente poner lo siguiente.
    • Puerto local: 2224 puede ser cualquier número que se le ocurra.
    • Dirección remota: github.com
    • Puerto remoto: 22
  1. Para probar la configuración debemos probar lo siguiente.
ssh git@localhost -p 2224
  1. Luego creamos un repositorio git.
    git init
  2. Luego añadimos todos los repositorios.
    git add *
  3. Hacemos un conmit
git commit -m “Primera versión”
  1. Adicionamos el repositorio remoto ubicado en git.
    git remote add origin ssh://git@localhost:2224/mi_usuario/mi_proyecto.git
  2. Finalmente hacemos un push
git push -u origin master


Un saludo y como siempre espero comentarios.

lunes, 3 de octubre de 2011

Cómo crear un tipo de campo de formulario personalizado.


Hola a todos. Esta es mi primera entrada en este blog, el cual utilizaré para compartir con la comunidad mis experiencias con Symfony2.

El presente trabajo está dedicado a la creación de un tipo de formulario en Symfony2. En Symfony1 esta caraterística era conocida como widget. Para evitar confusión le llamaremos también aquí widget.

Esta característica de Symfony2 todavía no está documentada, por lo que esta información es obtenida a partir del análisis que he hecho del propio framawork. Con el debate se podrán encontrar errores y hacer mejoras a este pequeño tutorial.

Lo que pretendo mostrar es cómo programar un widget “autocomplete” de jQuery. Después de implementado y ejecutado en una página, debe generar un código html y javascript como el mostrado en el siguiente listado.


<label for="contactotype_name_visible" class=" required">Name</label>
<input type="hidden" id="contactotype_name" name="contactotype[name]" required="required" value="" />
<input type="text" id="contactotype_name_visible" name="contactotype_visible[name]" required="required" value="" />
<script type="text/javascript">
//<![CDATA[
$(function() {
var data_contactotype_name = [
   {
     id: 'bajo',
     label: 'Rendimiento Bajo'
   },
   {
     id: 'medio',
     label: 'Rendimiento Medio'
   },
   {
     id: 'alto',
     label: 'Rendimiento Alto'
   }
];
$( "#contactotype_name_visible" ).autocomplete({
   source: data_contactotype_name,
   select: function( event, ui ) {
     $( "#contactotype_name_visible" ).val( ui.item.label );
     $( "#contactotype_name" ).val( ui.item.id );
     return false;
   }
 });
});
//]]></script>

El primer paso es crear la clase que representará al nuevo widget. Le llamaremos ”AutocompleteType”. Esta clase la podemos implementar en un directorio con el nombre “Type” dentro de nuestro bundle.
 

<?php
namespace Acme\DemoBundle\Type; //namesapce donde está ubicado nuestro nuevo
                                                   //widget.
//Ponemos todas las dependencias de nuestra clase.
use Symfony\Component\Form\AbstractType;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\Extension\Core\ChoiceList\ArrayChoiceList;

class AutocompleteType extends AbstractType
{
   /**
   * Constructor.
   *
   * @param ContainerInterface $container A container.
   */
   public function __construct(ContainerInterface $container)
   {
     //Al constructor le paso el container.
     $this->container = $container;
   }
   /**
   * {@inheritdoc}
   */
   public function getParent(array $options)
   {
     //Con este método le estoy indicando que el componente padre es un
     //field que es el utilizado para generar todos los widget de tipo input
     return 'field';
   }

   /**
   * {@inheritdoc}
   */
   public function getName()
   {
     //Nombre que va a tener el nuevo widget, debe ser un nombre distinto a
     //los ya utilizados.
     return 'jquery_autocomplete';
   }
   /**
   * {@inheritdoc}
   */
   public function buildView(FormView $view, FormInterface $form)
   {
     //En este método le pasamos todas las variables a la vista y preparamos
     //la vista para que pueda ser utilizada.

     //buscar la explicación de esta línea más abajo.
     $this->container->get('twig.extension.form.jquery')->setTheme($view,     array('AcmeDemoBundle:Type:fields.html.twig'));
     //Se le indican al objeto view todas las variables que se le deben pasar
     //a la vista. Como este widget está compuesto por dos input html, un
     //hidden y un text, paso un id y full_name para el text
     //choices: conjunto de opciones
     //id_visible: id del widget visible, (input text)
     //full_name_visible: nombre completo del widget visible, (input text)
     $view->set('choices', $form->getAttribute('choice_list')->getChoices())
       ->set('id_visible', $view->get('id').'_visible')
       ->set('full_name_visible', sprintf('%s_visible[%s]',   $view->getParent()->get('full_name'), $view->get('name')));
   }
   public function buildForm(FormBuilder $builder, array $options)
   {
     //Este método se preparan las opciones que ha puesto el usuario para el
     //formulario.
     if ($options['choice_list'] && !$options['choice_list'] instanceof ChoiceListInterface) {
throw new FormException('The "choice_list" must be an instance of "Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface".');
     }
     if (!$options['choice_list']) {
       $options['choice_list'] = new ArrayChoiceList($options['choices']);
     }
     $builder->setAttribute('choice_list', $options['choice_list']);
   }
   /**
   * {@inheritdoc}
   */
   public function getDefaultOptions(array $options)
   {
     //En este método se especifica las opciones por defecto.
     return array(
       'choice_list' => null,
       'choices' => array(),
     );
  }
}

El framework utiliza la plnatilla “form_div_layout.html.twig” ubicada en “vendor/symfony/src/Symfony/Bridge/Twig/Resources/views/Form” para renderear los distintos widget, cada uno tiene un bloque dedicado. Por ejemplo, para renderear un input hidden se utiliza un bloque con el nombre “hidden_widget”.
El framework funciona de la siguiente manera: primero trata de buscar un bloque con el nombre <nombre_type>_widget, si no aparece entonces busca si existe el bloque para el padre y así sucesivamente hasta que encuentre uno.
En nuestro caso necesitamos crear un nuevo bloque para el widget y debe tener el nombre “jquery_autocomplete_widget”. También necesitamos crear un bloque para renderear la label, cuya filosofía es la misma y tendría el nombre “jquery_autocomplete_label”.
Un paso importante es indicarle al framework la plantilla donde están definidos los nuevos bloques y este es el objetivo de la siguiente línea del método “buildView”
 
$this->container->get('twig.extension.form.jquery')
  ->setTheme($view, array('AcmeDemoBundle:Type:fields.html.twig'));
 


El método “setTheme” de la clase “Symfony\Bridge\Twig\Extension\FormExtension” se utiliza para adicionarle nuevos temas a la hora de renderear los formularios. El servicio para “FormExtension” es creado como no público, por lo tanto se necesita crear un alias para poder acceder a él desde nuestro bundle

<service id="twig.extension.form.jquery" alias="twig.extension.form" />


El siguiente paso es crear los bloques para renderear la label y el widget en la plantilla “AcmeDemoBundle:Type:fields.html.twig”
Para renderear la label sería:
 
{% block jquery_autocomplete_label %}
{% spaceless %}
    {% set attr = attr|merge({'for': id_visible}) %}{#Le indicamos el id del#}
                                                    {#componente html al cual#}
                                                    {#debe apuntar la label#}
    {{ block('generic_label') }}
{% endspaceless %}
{% endblock jquery_autocomplete_label %}
 

Para renderear la widge sería:

{% block jquery_autocomplete_widget %}
    {% set type = type|default('hidden') %}
    {{ block('field_widget') }} {#Genero un campo oculto que será el que#}
                                {#almacene el dato que se envía al servidor#}
    {% set id_hidden  = id %} {#almaceno el valor del campo oculto#}
    {% set id         = id_visible %}{#le asigno al id el valor del id_visible#}
                                     {#para poder renderear el campo input#}
    {% set full_name  = full_name_visible %} {#hago lo mismo con el full_name#}
    {% set type = 'text' %} {#indico que el tipo que voy a renderear#}
                            {#es de tipo text#}
    {{ block('field_widget') }} {#rendereo el campo de tipo text#}
    {#código javascript para hacer el widget#}     
    <script type="text/javascript">
    //<![CDATA[
    $(function() {
        var data_{{ id_hidden }} = [
            {% for choice, label in choices %}
             {
                 id:    '{{ choice }}',
                 label: '{{ label }}'
             }{% if not loop.last %},{% endif %}
            {% endfor %}
        ];
        $( "#{{ id_visible }}" ).autocomplete({
            source: data_{{ id_hidden }},
            select: function( event, ui ) {
                $( "#{{ id_visible }}" ).val( ui.item.label );
                $( "#{{ id_hidden }}" ).val( ui.item.id );

                return false;
            }
        });
    });
    //]]>
    </script>    
{% endblock jquery_autocomplete_widget %}
 


Finalmente lo que queda es crear un servicio para el nuevo widget y que este sea cargado por el sistema de formulario de symfony.
 
<service id="form.type.jquery.autocomplete" class="Acme\DemoBundle\Type\AutocompleteType">
  <tag name="form.type" alias="jquery_autocomplete" />
    <argument type="service" id="service_container" />
</service>
 

Hasta aquí es el proceso de creación de un widget. Ahora lo que queda es utilizarlo dentro de un formulario.
 
namespace Acme\DemoBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

class FormPruebaType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('name', 'jquery_autocomplete', array(
                'choices' => array(
                    'bajo'  => 'Rendimiento Bajo',
                    'medio' => 'Rendimiento Medio',
                    'alto'  => 'Rendimiento Alto'
                 )
            ));
    }

    public function getName()
    {
        return 'form_prueba_type';
    }
}

Espero que les sea útil y gustaría comentarios. Disculpen el formato y los tipos de letras utilizados.