ßeta perpetua

El blog personal de Juan Manuel Barroso

En testing lo primero es F.I.R.S.T

No Comments »

El testing es un pilar fundamental del desarrollo de software pues es lo único que nos garantiza que dicho software funciona. Pero no es suficiente con hacer testing, hay que hacerlo bien. Si hay algo peor que no tener pruebas, es tener pruebas mal hechas.

Malas pruebas nos dan la falsa seguridad de que las cosas funcionan, nos hacen mantener un código que no aporta valor y si están mal diseñadas, y tenemos que mantenerlas, son un auténtico dolor de cabeza.

Creo que los principios que plantea Robert C. Martin en su libro Clean Code son perfectos para comenzar. Me voy a centrar en los principios F.I.R.S.T añadiendo algunos elementos con los cuales me he tropezado en proyectos heredados, con código legacy que obligan a desviarse del camino ideal, y sobre todo desde un punto de vista de testing de integración.

Es conveniente recordar que el código de los test es tan importante como el de producción, el esfuerzo por mantener un código limpio, legible, sin duplicidad debe hacerse sin distinguir el objetivo del código que estamos escribiendo.

Las siglas F.I.R.S.T son 5 reglas que significan: Fast, Independent, Repeatable, Self-validating y Timely.

Fast: Los test deberían ser rápidos. Baterias de test lentos nos rompen el flujo de trabajo y generan indisposición a  ejecutarlos. Si no los ejecutamos no podremos detectar fallos con frecuencia, ni a tiempo.
A esta regla me gustaría añadirle un aspecto más. Es cierto que debemos intentar que los test sean rápidos en su ejecución pero en ocasiones no podemos evitar que un gran número de pruebas de integración/funcionales en sistemas complejos tarden un quantum de tiempo mayor al deseado. En estos casos debemos considerar la importancia de que la acción de lanzar los test se pueda hacer de manera sencilla y que se pueda llevar a cabo rápidamente.
Algunas ideas en este punto son la creación de suites de prueba que permitan desde un único punto ejecutar todos los test relacionados, o contar con una herramienta como Jenkins en la que a golpe de click se puedan lanzar todos los tests para en un instante posterior consultar los resultados.
De esta manera conseguimos que no sea tedioso lanzar las pruebas, e incluso que se puedan ejecutar en background mientras nosotros seguimos trabajando.
En sistemas compuestos por diferentes módulos, o equipos divididos por dominios de la aplicación, la inclusión de las baterías de pruebas de integración de los diferentes módulos de nuestro sistema en Jenkins puede ser un gran acierto, porque hacen que todo el equipo tenga acceso a las pruebas de todos los módulos, y que se pueda comprobar el impacto de nuestros cambios en los módulos relacionados sin necesidad de tener un conocimiento profundo de todos esos módulos.

Independent: Los test no deberían depender entre ellos, ningún test debería ser una pre-condición de otro test. Partiendo de esta regla tenemos un sistema en el que podemos lanzar cualquier test de manera independiente y en cualquier orden.
Si nos encontramos nuevamente con test de integración/funcionales podemos extender esta regla a otros recursos que pudieran necesitar los test. Cada test debería preparar de manera independiente las pre-condiciones, y debería evitar dar por sentado que los recursos que necesitan estarán disponibles en el sistema. Si el test tiene como pre-requisito que exista un registro en BBDD se debería crear ese registro en el momento previo de lanzar el test, si el test necesita que exista un documento previo en el gestor documental se debería subir ese documento en el momento previo de lanzar el test, y cuando sea realmente imposible crear esa pre-condición deberíamos controlarla generando errores que fácilmente nos informen de que el problema es la pre-condición, y que no de lugar a pensar que la funcionalidad que comprueba el test se ha roto.
Con esto buscamos que nuestro tests se independicen del entorno y de las circunstancias. Podemos comprobar la funcionalidad  en desarrollo, en producción, con una nueva BBDD sin registros o incluso no vernos afectados si alguien borra los documentos del gestor documental.

Repeteable: El test debería ser repetible en cualquier entorno y para cualquier desarrollador del equipo. Este apartado guarda relación con la regla anterior, cada test debería preparar su entorno para que todos los recursos estén controlados en el propio test.
El punto más común de fallo es que el test pasa en el ordenador de la persona que lo desarrolló pero no al resto de miembros. Los motivos pueden ser varios, desde configuración hasta recursos no sincronizados correctamente en la herramienta de control de versiones del código.
La mejor medicina para evitar este gran problema es disponer de una maquina independiente que corra un servidor de integración como Jenkins para que ejecute los test de manera automática. Todos los test deberán poder ser lanzados por Jenkins cada vez que se realiza un cambio, y a poder ser crear señales visuales que mantengan al equipo informado ;-) .

Self-Validating: Los test deben tener una salida booleana, pasan o no pasan. Pruebas que dejen su resultado en una cadena de texto que debemos revisar, o tests sin asserts deben evitarse.
Me gustaría extender este apartado con otra mala costumbre que me he encontrado muchas veces, y que probablemente alguna vez padecí, que es que el test no valida lo que está probando. Ejemplos bastante significativos de esto pueden ser los tests de búsquedas que no comprueban que los elementos recibidos cumplen los requisitos de la búsqueda o test sin asserts que sólo fallan cuando el código genera una preciosa excepción.
Es importante que tengamos claro el objetivo del test para crear las validaciones acorde a dicho objetivo.

Timely: Los tests deben ser creados antes que le código de producción, cuando hacemos TDD esto está implicito, pero tambien debemos hacerlo con el resto de tipos de tests (funcionales, integración). Cuando un usuario nos reporta un bug, o cuando queremos cambiar el comportamiento de una funcionalidad debemos reflejar ese bug o ese nuevo comportamiento en uno o varios tests, de esta manera sabremos realmente cuando la funcionalidad esta terminada o cuando el bug está solucionado. Además que es mucho más sencillo escribir el test sin pensar en una implementación que ya hemos codificado.

Pueden haber excepciones que nos obliguen a romper alguna de las reglas, estas excepciones deberían estar documentadas y sobre todo no debemos permitir que se generalicen.

Si tenemos un test de un caso muy particular no deberíamos mezclarlo con el resto del tests, para estas cosas suelo usar un test case denominado SandBoxTests que se excluye de los test automatizados, o usar la notación de JUnit @Ignore con una descripción que explique la circunstancias especiales que me llevaron a escribir el test, por ejemplo que el usuario Julio no puede subir el fichero comic.pdf pero si cualquier otro.

 

Contexto de una aplicación en JBOSS

2 Comments »

El contexto raíz de una aplicación web determina las URLs que serán delegadas por el servidor a nuestra aplicación, esto quiere decir que si tenemos un contexto raiz myapplication el servidor delegará a nuestra aplicación todas las peticiones del tipo myapplication/*.

El contexto raíz se carga durante el despliegue, y dependiendo del tipo de artefacto (EAR,  WAR) puede encontrarse en lugares diferentes.

Cuando estamos trabajando con un EAR el contexto de la aplicación debe ser especificado en el fichero application.xml, específicamente en la etiqueta <context-root/> del módulo correspondiente:

<application xmlns="http://java.sun.com/xml/ns/j2ee" version="1.4"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com /xml/ns/j2ee
                             http://java.sun.com/xml/ns/j2ee/application_1_4.xsd">
    <display-name>MyApplication</display-name>
 
    <module>
        <web>
            <web-uri>myApplication.war</web-uri>
            <context-root>myNewContext</context-root>
        </web>
    </module>
 
    <module>
        <ejb>my-library.jar</ejb>
    </module>
 
</application>

Si nuestra aplicación va a ser desplegada en un .war directamente, debemos modificar el fichero jboss-web.xml de la carpeta WEB-INF:

<jboss-web>
    <context-root>myNewContext</context-root>
</jboss-web>

De esta manera las peticiones a myNewContext/* serán delegadas a nuestra aplicación.

Si no especificamos un contexto al WAR, se usará el mismo nombre del fichero .war. Por ejemplo, para el desplegable otra-aplicacion.war se usará el contexto otra-aplicacion.

Esto puede ser útil cuando necesitemos publicar nuestra aplicación en una ruta del tipo: producto/consolas/myAplicacion.

Un caso real donde tuve que cambiar el contexto de una aplicación, fue desplegando un aplicación Grails que delegaba la parte de seguridad en SpringSecurity y sólo podía ser accesible desde una redirección de Apache. El problema era que el plugin no era capaz de redirigir desde la página de login a la primera página de la aplicación. Como solución inmediata cambie el contexto de la aplicación para que coincidiera con la URL de la redirección y problema solucionado.
Probablemente el plugin podía ser configurado para solucionar el problema, pero en aquel momento la aplicación debía estar en producción sin retrasos.

Probado con JBOSS 5 y Grails 1.3.7