Test unitarios e integración con PHPUnit/CIUnit sobre CodeIgniter.

Índice

Introducción

En mi experiencia profesional observo que en muchos casos existen importantes vacios de conocimientos en relación a PHP (a fecha de hoy vamos por la release de version: 5.4.8).

Muchos desarrolladores equivocadamente etiquetan a PHP como un lenguaje de programación sin frameworks estables, en donde los desarrollos suelen ser código poco estructurado y poco mantenible. Pues bien, se equivocan. Existen buenos frameworks y se puede programar con mucha calidad.

Cierto es que en muchos casos cuando veo código PHP en libros y proyectos de código abierto me dan ganas de llorar al ver HTML mezclado con SQL, escasa modularidad, inexistancia de patrones como el MVC, bajo uso del paradigna de programación orientación a objetos, nada de pruebas unitarias, ... ¡¡Cuidado con aprender PHP de ciertas fuentes!!

Mi conocimiento en PHP y su ecosistema es bastante más reducido a mis conocimientos en .NET o Java, pero quería realizar un tutorial donde pudiera aportar un poco más de claridad tanto a los desarrolladores de otros lenguajes de programación como a los de PHP más juniors.

Aunque sea más partidario de otros lenguajes (ver conclusiones), resalto de nuevo que se puede programar bien usando PHP y de eso trata un poco este tutorial que espero os guste.

La Importancia de las Pruebas

Existen infinidad de documentos que puedes consultar en Internet, por lo que no voy a ser muy extenso en este apartado.

La cuestión es que la calidad no es negociable, un sistema informático debe funcionar en base a los requisitos marcados y además este debe de ser facilmente mantenible y ampliable. De aquí, que cuando un equipo de desarrollo necesita realizar modificaciones o ampliaciones sobre la aplicacion estos deben de realizarse sin tener que rezar un padre nuestro por no saber que romperá de lo ya realizado cuando realize las modificaciones.

Todos sabemos que el coste económico, tanto teórico (nos lo enseñan en la universidad) como el real (lo que vemos en el dia a dia) suele ser alto. Esto en gran medida, se debe a la ausencia de test que hagan que la probabilidad de romper algo en cada versión sea mínima.

Además si nunca has programado en base a tests, puedes pensar que el coste de desarrollo se incrementa, pues bien mentira.. es más rápido hacer un test, que tener que hacer un deploy e ir navegando por la interfaz (en caso de aplicaciones web) para poder invocar la funcionalidad que estás desarrollando.

Hasta donde llego, el rey en el mundo en PHP para la realización de tests unitarios es PHPUnit (con extensiones de cobertura, mockeo, etc), el cual no voy a extenderme más allá de que es muy potente, a la altura que librerías del mundo Java (JUnit, Mockito, etc).

El Framework PHP CodeIgniter

Al igual que dije en el apartado anterior, existe a golpe de consulta una excelente documentación sobre CodeIgniter por lo que no me voy a extender más de lo necesario en esta sección.

CodeIgniter es un excelente Framework para el desarrollo de aplicaciones Web con PHP basado en el patrón MVC, cuya funcionalidad voy a resumir a continuación:

  • Extremadamente fácil de aprender con muy buena documentación.
  • Te lo descargas, lo descomprimes y en pocos minutos tienes una estructura funcional lista para tu proyecto.
  • No es intrusivo ni te tienes que acoplar a el como ocurre en otros frameworks PHP.
  • Separación clara de los controladores, vistas y el modelo.
  • Excelente sistema de caché tanto a nivel de acceso a base de datos como a nivel de URL (sencillo de comprender y habilitar).
  • Completas y amplias funciones de utilidades (helpers) listas para ser usadas (archivos, encriptacion, acceso a datos, ...)
  • Fácilmente modificable (además no está restringido con licencias poco permisivas).
  • Soporte para la internazionalicación de mensajes.
  • Buen soporte de composición de pantallas a base de plantillas.
  • Posibilidad de realizar lógica de negocio antes/después de que un controlador o clase sea invocada (hooks de CodeIgniter)
  • Sistema de trazas (no es muy potente pero bueno hay está :-) )

Carencias???? Su sistema de testing y por eso este tutorial.

Aplicación de ejemplo

Para aprender me voy a apoyar en una sencilla aplicación web en donde se listan tutoriales categorizados por etiquetas (tags). La aplicación será construida con el framework para PHP CodeIgniter y realizaremos las pruebas usando PHPUnit y su "puente" CIUnit para usar PHPUnit desde CodeIgniter.

Según avancemos en el desarrollo, podremos lanzar los tests (desde línea de comandos) que verifican que todo funciona correctamente y no hemos roto nada en el avance y las refactorizaciones.

En la siguiente captura de pantalla podemos ver como lanzar phpunit para ejecutar todos los tests o sólo los de los modelos, helpers, librerías..:

En CodeIgniter las clases de test deben heredar de CIUnit_TestCase y usar en el nombre el sufijo "Test". Además los nombres de los métodos deben tener el prefijo "test".

Cómo podrás ver en el código fuente, una buena práctica a la hora de hacer test unitarios es crear el test de una clase siguiento la misma estructura de directorios.

Los tests que se encargan de verificar que las clases que acceden a los datos funcionan usarán el siguiente esquema de base de datos así como los datos del juego de datos que se describen a continuación.

Para cada test se inicializarán y vaciarán las tablas de manera que los tests sean independientes.

El esquema de base de datos debe existir (no he buscado mucho por que no he tenido la necesidad). No he encontrado la forma de que se cree automáticicamente el esquema para cada test.

SQL DDL de la base de datos con la que trabaja la aplicación

Juego de datos para los tests

tests/fixtures/tutoriales_fixt.xml

1:
    id: 1
    alias: backbone-requirejs
    titulo: Creacion de un Widget JavaScript usando Backbone.js y Require.js
    descripcion: En este tutorial crearemos un Widget JavaScript usando Backbone.js y Require.js.
    visitas: 299
    estado: 1
    fecha: 2012-10-23
2:
    id: 2
    alias: spring-mvc-api-rest-oauth2
    titulo: Creacion de un API REST protegido por OAuth2
    descripcion: En este tutorial veremos como construir un API REST protegido por OAuth2.
    visitas: 77
    estado: 1
    fecha: 2012-07-25
            

tests/fixtures/tags_fixt.xml

1:
    id: 1
    caption: Android
2:
    id: 7
    caption: BackboneJS
3:
    id: 4
    caption: HTML5
4:
    id: 2
    caption: Java
5:
    id: 6
    caption: JavaScript
6:
    id: 3
    caption: JEE
7:
    id: 8
    caption: RequireJS
8:
    id: 5
    caption: Spring
            

tests/fixtures/tutoriales_tags_fixt.xml

1:
    id: 1
    tutorialID: 1
    tagId: 6
2:
    id: 2
    tutorialID: 1
    tagId: 7
3:
    id: 3
    tutorialID: 1
    tagId: 8
4:
    id: 4
    tutorialID: 2
    tagId: 2
5:
    id: 5
    tutorialID: 2
    tagId: 3
6:
    id: 6
    tutorialID: 2
    tagId: 5
            

Los Helpers

Los helpers en CodeIgniter son clases con funciones de utilidad no ligadas al paradigma de la programación orientada a objetos.

helpers/matematicas_helper.php

tests/helpers/MatematicasHelperTest.php

Las Librerias

Los librerias en CodeIgniter son las clases (POO) que no están ligadas al acceso a datos.

libraries/es/carlosgarcia/entity/Tag.php

tests/libraries/es/carlosgarcia/entity/TagTest.php

libraries/es/carlosgarcia/entity/Tutorial.php

tests/libraries/es/carlosgarcia/entity/TutorialTest.php

libraries/es/carlosgarcia/service/AppService.php

Esta clase proporciona servicio a los controladores de la aplicación (MVC), es decir entre el controlador de CodeIgniteer tendrá entre sus atributos una referencia a AppService.

Observe que esta clase es responsable de la transaccionalidad, es decir por dentro este servicio podría tener varios repositorios de distintas clases y la operación de servicio tener que realizar varias lecturas, actualizaciones e inserciones para llevarse a cabo, de hay que la responsabilidad de la transaccionalidad recaiga en el servicio.

tests/libraries/es/carlosgarcia/service/AppServiceTest.php

En el siguiente test unitario mockearemos las dependencias para probar la funcionalidad de nuestra clase con idependencia de sus las dependencias.

En este test se usa un Mock para la clase TutorialRepository que es la que accede al repositorio de datos (en este caso, una base de datos)

tests/libraries/es/carlosgarcia/service/AppServiceIntegrationTest.php

En este caso la inicializaremos cada tabla a partir de un archivo fixture:

  • La tabla tags con el archivo tests/fixtures/tags_fixt.xml
  • La tabla tutoriales con el archivo tests/fixtures/tutoriales_fixt.xml
  • La tabla tutoriales_tags con el archivo tests/fixtures/tutoriales_tags_fixt.xml

libraries/es/carlosgarcia/exception/ResourceNotFoundException.php

Operaciones con etiquetas de los tutoriales.

Las clases del modelo

CodeIgniter separa semánticamente las clases que no acceden a datos (librerias) con las que si lo hacen (modelos).

models/es/carlosgarcia/repository/TagsRepository.php

tests/models/es/carlosgarcia/repository/TagsRepositoryTest.php

models/es/carlosgarcia/repository/TutorialRepository.php

Operaciones con tutoriales.

Cobertura de los tests

Mediante el plugin PHP_CodeCoverage podremos obtener un magnifico informe de la cobertura de nuestros tests.

Existen multitud de artículos de como instalarlo y ejecutarlo.. en resumen se hace de la siguiente forma. (googlea para más información)

  • Instalación del plugin: sudo pear install pear.phpunit.de/PHP_CodeCoverage
  • Ejecución: phpunit --coverage-html ./report

Capturas de pantalla de los informes de salida de PHP_CodeCoverage

Captura de pantalla 1 de 2

Captura de pantalla 2 de 2

Referencias

Conclusiones

Cómo hemos visto en este tutorial, se puede programar mejor que lo que se ve en ciertos libros/tutoriales en donde todo se mezcla y no hay pruebas unitarias que mejoren la calidad.

A pesar de todo y en mi sesgada opinión personal sobre PHP, hecho en falta cosas para mi importantes, como:

  • La sobrecarga de métodos de una clase (POO), es decir dos métodos con el mismo nombre y diferente número de parámetros.
  • Una gestión de excepciones más madura en donde:
    • Exista el bloque finally {}
    • En donde los bloques catch {} apliquen en el flujo de captura de la excepción la herencia de clases.
    • Los métodos puedan indicar en su firma que excepciones lanza obligando al programador a capturarlas siempre.
  • A pesar de lo que digan algunas tendencias actuales, a mi me gustan los lenguajes tipados, en donde reciba errores a nivel de edición del código como en la compilación.
    Se puede tipar los métodos de los objetos pero a una forma limitada.
  • El concepto de ciclo de vida que por ejemplo tiene las aplicaciones JEE y .NET.
  • El gran ecosistema que rodea a Java: Workflow, frameworks de seguridad, motores de reglas, librerías gráficas e informes ..

Pero bueno, pienso que el éxito o fracaso de un proyecto depende mucho más de otros factores que el lenguaje de programación usado..
Además se puede programar muy bien en PHP y muy mal en Java, .NET o cualquier otro.

Bueno, eso es todo, un saludo.
Carlos García.

Categorías del artículo

Comentarios de los lectores