domingo, 6 de mayo de 2012

Tests unitarios en PHP

Los tests en un proyecto sirven para poder "asegurarnos" de si la aplicación está funcionando correctamente pese a haber echo una modificación/refactorización. Esto nos permite evitar regresiones en el código o si las tenemos detectarlas fácilmente y encontrar rápidamente una solución.

En este articulo hablaremos sobre que son los tests unitarios y veremos un ejemplo sencillo de como funcionan.

Tests unitarios

Los tests unitarios, como su propio nombre indica, se basan en comprobar si una unidad de código funciona de la forma que nosotros esperamos. Normalmente la unidad de código se considera la clase, por lo que los tests unitarios se dedican a probar a fondo una sola clase de forma que sepamos que esa clase funciona como esperamos.

Un ejemplo de test unitario podría ser:
<?php
 class Calculadora{
  public function suma( $a, $b )
  {
   return $a + $b;
  }
 }

 $calculadora = new Calculadora();
 if( $calculadora->suma(1,2) === 3 )
 {
  echo 'Test1. Resultado correcto.';
 }
 else
 {
  echo 'Test1. Resultado incorrecto.';
 }
?>

Como puede verse en el ejemplo, únicamente prueba la clase Calculadora y prueba que dadas unas entradas se produce la salida esperada. Es muy importante que el test no tenga ningún tipo de lógica y los resultados esperados no sean calculados, ya que un test que realiza las mismas operaciones que lo que está probando no es un test.

Esto sería un ejemplo de un test incorrecto:
<?php
 class Calculadora{
  public function suma( $a, $b )
  {
   return $a + $b;
  }
 }

 $calculadora = new Calculadora();
 for ( $a = 0; $a < 10; ++$a )
 {
  for ( $b = 0; $b < 10; ++$b )
  {
   if ( $calculadora->suma( $a, $b ) === $a + $b )
   {
    echo "Test $a+$b correcto";
   }
   else
   {
    echo "Test $a+$b incorrecto";
   }
  }
 }
?>

Como puede verse el código tiene mucha lógica, teniendo dos bucles y operaciones algorítmicas. Si analizas el código te das cuenta de que este test no está probando nada, ya que el test realiza la misma operación que la función probada ( $a + $ b ). Eso significa que si la función probada está mal, también lo estará la del test y el error no se detectará. Por eso siempre que hagas un test, debes hacer los cálculos a mano y establecer que en dichas entradas el resultado debe ser el que tu has calculado.

Estos dos ejemplos, son muy sencillos y esto casi nunca se da en un entorno real. Aún pese a ser sencillos escribir el test ha sido engorroso y además se sitúa en el mismo archivo que la clase, por lo que este archivo no puede ir a producción. Para facilitarnos la tarea en la creación de tests y hacerlos la vida más sencilla, existen los frameworks para tests, que es lo que utilizaremos a partir de ahora.

Tests unitarios en PHP con PHPUnit

En PHP, el estándar para la realizar los tests es el framework PHPUnit. Este framework nos proporciona todo lo necesario para poder realizar los tests que nos dirán si nuestra aplicación está funcionando como nosotros esperamos de una forma mucho más sencilla que la vista anteriormente.

Para ver el funcionamiento de PHPUnit, nada mejor que ver un ejemplo y explicar paso a paso como funciona. Os recuerdo que este será un ejemplo sencillo y lo iremos complicando en los siguientes artículos.

Para empezar, tenemos nuestra clase calculadora, que se almacenará en un archivo llamado "calculadora.php":
<?php
 //calculadora.php
 class Calculadora{
  public function suma( $a, $b )
  {
   return $a + $b;
  }
 }
?>

Ahora que tenemos nuestra clase, vamos a probarla con PHPUnit, el código para probar esta clase sería el siguiente:
<?php
 //CalculadoraTest.php
 require_once( 'calculadora.php' );

        /**
         * Los nombres de las clases tiene que terminar por *Test y el nombre del archivo debe ser el mismo
         * que el de la clase.
         */
        class CalculadoraTest extends PHPUnit_Framework_TestCase{
                /**
                 * Objeto a probar, en nuestro caso la calculadora.
                 *
                 * @var Calculadora
                 */
                protected $obj;

                /**
                 * Este método es ejecutado antes de cada ejecución de test, de forma
                 * que todo lo que tengas que inicializar en todos los tests, lo puedes
                 * centralizar aquí.
                 */
                public function setUp()
                {
                        $this->obj = new Calculadora();
                }

                /**
                 * Este método se ejecuta después de cada test ejecutado, por lo que
                 * se dedicará a limpiar los restos de la ejecución del test.
                 */
                public function tearDown()
                {
                        $this->obj = null;
                }

                /**
                 * Test para comprobar la funcionalidad del objeto
                 */
                public function testSuma()
                {
                        $this->assertEquals( 3, $this->obj->suma( 1, 2 ), 'El resultado de 1 + 2 debe ser 3.' );
                }
        }
?>

El nombre de la clase de testing debe terminar por Test y el mismo nombre de la clase debe ser el mismo nombre del archivo. Esto lo utiliza PHPUnit para buscar las clases a ejecutar y saber que nombre tienen. Además esta clase extiende de PHPUnit_Framework_TestCase, que es la clase que nos aportará todas las herramientas necesarias para el test.

Una vez entramos dentro de la clase, vemos un par de métodos mágicos llamados setUp y tearDown. Este par de métodos son opcionales y se dedican a inicializar y limpiar respectivamente los tests. Esto nos permitirá que todas las acciones repetitivas de dentro de los tests, extraerlas y que los tests solo contengan realmente la lógica de comprobación. Además esto nos permite respetar otra regla importante de los tests y es que un test no puede ser nunca dependiente de otro, por lo que el orden de ejecución de los tests nunca debería de importar, y es por esa razón que el tearDown borra el objeto creado para el test anterior, por si este ha hecho alguna modificación al objeto que podría hacer fallar al siguiente test.

Todos los métodos que empiecen por la palabra test son los que se van a ejecutar y deben comprobar el funcionamiento de la clase deseada. En nuestro caso, el método testSuma comprueba que si sumo 1 y 2, el resultado sea 3. Como podeis ver, la forma en que se comprueba si todo a ido bien es con la función assertEquals.

Las función assertEquals lo que permite es que dado el primer parámetro, que es lo que espero como resultado y un segundo parámetro donde indico el resultado real de la operación, saber si los resultados coinciden. Además a esta función se le puede pasar un tercer parametro para que en el caso de que la comparación falle, me muestre dicho mensaje de error. Como puedes ver es mucho más sencillo usar esta simple función que la lógica if/else utilizado en nuestro primer ejemplo sin framework.

A estas comparaciones para saber si algo ha ido bien, se le llaman aserciones y assertEquals es solo una de las muchas que tiene PHPUnit para poder comprobar que un resultado ha sido correcto. En el framework tienes disponibles desde aserciones para comprobar si un número es mayor que otro, hasta comprobar si el atributo de un objeto es igual a lo que tu quieres, pasando por saber si un array tiene un cierta clave. Os aconsejo darle una mirada a la gran cantidad de aserciones de las que dispone en su manual.

Finalmente para ejecutar los tests de phpunit, es tan sencillo como ejecutar la orden:
$phpunit --colors CalculadoraTest.php

Esto nos mostrará un resultado, que será verde en caso de que todo haya ido bien y rojo en caso contrario.

Os dejo los archivos del articulo adjuntos en el mismo y en los siguientes artículos veremos más a fondo el funcionamiento del framework y como abordar casos más complejos.

Referencias

  1. PHPUnit

Descargar scripts del artículo.

No hay comentarios:

Publicar un comentario