Nov 10

Efectividad de los test unitarios

Vía pensamientos ágiles, descubro una idea interesante y un herramienta para probar lo efectivos que son nuestros test unitarios.

La herramienta modifica en tiempo de ejecución nuestro código, cambiando condiciones por la negada, cambiando valores de constantes, operadores aritméticos, etc y se trata de ver si nuestros test fallan después de haberse modificado nuestro código. Si el test no falla, posiblemente es que no es un test suficientemente efectivo.

La herramienta que hace estas cosas es jumble.

Nov 03

Testeando GUIs

Llevo tiempo convencido de que el mismo código, dependiendo de cómo se haga, puede ser fácil de testear o imposible. Un comentario de Roberto M. Oliva y un ojo a su blog "The Gold Bug" y en concreto a un post de "mas sobre MVC" me han llevado a dos artículos interesantes que vienen a reafirmarme en mi idea inicial.

Las interfaces de usuario son esas cosas que tradicionalmente son difíciles de testear. En ese par de artículos proponen dos formas de hacerlas, basadas en MVC en el que la parte que queda sin testear se hace lo más pequeña posible o, al menos, el código que queda sin testear es código básico en el que se vea rápido si está mal, código sin demasiados riesgos de fallo.

Uno de ellos es Supervising Controller, En este "patrón" se deja en el controlador la lógica más compleja de la vista. A la vista se le deja su funcionalidad más simple y toda la complicación se lleva al controlador. De esta forma, como el controlador es más fácil de testear que la vista, podemos realizar el test de esa funcionalidad compleja. Además, si es necesario, un pequeño "mock object"(1) de la vista nos puede ayudar a testear el controlador.

El otro es Passive View. En este patrón la vista es totalmente tonta y estúpida. No ve ni siquiera al modelo de datos. El controlador es el encargado de hacerlo todo. Cuando el usuario pulsa algo en la GUI, se entera el controlador. Este hace absolutamente todo y luego le dice a la vista qué tiene que mostrar. Nuevamente el test del controlador debería ser más fácil que el de la vista y nuevamente podríamos apoyarnos en mock objects de la vista.

Está mal decirlo, pero estoy deseando que llegue el Lunes para hacer algo de esto en el trabajo… ¡¡Lástima que me espere un estúpido documento word para hacer!!

(1) mock object. Si tenemos que testear una clase A que usa una clase B -en este caso el controlador usa la vista-, podemos reemplazar la clase B por otra con la misma interface y hecha a nuestra medida para el test. Esta clase a medida es un "mock object" que reemplaza a B y nos ayuda a testear a A, ya que podemos enterarnos a qué métodos de B llama y con qué valores, ayudándonos a decidir si A realiza bien su trabajo.

Oct 28

A favor de los test unitarios

Hace unos días me dediqué a implementar en java una funcionalidad para un proyecto. Me puse a ello haciendo los test unitarios en paralelo al código que iba desarrollando, incluso un poco antes, de forma que pasaba el test, veía que fallaba y me ponía a arreglar.

Para hacer el código, me basé en una librería que estaba desarrollando otro compañero. Dicha librería estaba muy avanzada, pero no acabada, por lo que tenía algunos bugs e incluso código faltantes. Durante mi desarrollo y mis test, encontré algunos de esos fallos y faltantes, así que avisé a mi compañero, pero corregí o completé yo el código, por supuesto, haciendo adicionalmente otros test unitarios.

Por fin terminé la funcionalidad. Con los test encontré algunos fallos en mi propio código que también corregí y lo metí todo en CVS.

Al cuarto de hora de meterlo me aparece otro compañero y me dice … "revisa esos test que acabas de meter, que fallan….". ¡Vaya!, me quedé extrañado. Acababa de pasarlos y funcionaban todos. Los vuelvo a pasar en mi directorio de trabajo … y funcionan. Reviso que no me he olvidado de meter nada en CVS y todo correcto. Hago update, salen unos fuentes, paso el test … y falla. Revisando, revisando, compruebo que otro compañero más -el tercero-, ha tenido un despiste en una clase común y mi test ha fallado. Hablo con este compañero, que tiene el cambio reciente y en dos minutos queda solucionado el problema.

En total, algo menos de una semana codificando y otra semana larga que "perdí" haciendo los test. Queda además un "chivato" por si mi primer compañero decide más adelante cambiar su librería de forma que afecte a mi código.

Todo un poco aburrido hasta aquí y nada que se salga de lo normal, pero …. ¿qué podría haber pasado si no hubiera hecho test y hubiera hecho simplemente una prueba manual de mi código?. Veamos el posible futuro que posiblemente ya no es posible -espero-.

Si no hubiera hecho tests automáticos de prueba, sino simplemente unas pruebas manuales de mi código, habría visto igualmente los fallos y faltantes de la librería de mi primer compañero. Habría igualmente corregido y completado su código, pero sin hacer test unitarios. También habría encontrado mis propios errores al codificar. Una vez terminado todo lo habría metido en CVS.

Un cuarto de hora después NO habría venido mi segundo compañero a decir que los test fallan. El despiste de mi tercer compañero habría pasado desapercibido. Puede que él mismo se hubiera dado cuenta más adelante o no. Seamos tremendistas, supongamos que ese error no "canta" hasta bastante más adelante.

Seamos más tremendistas aun. Mi primer compañero, el de la librería, sigue su desarrollo de la librería y decide modificar o reorganizar el código que yo le hice y aunque el código sigue funcionando, ya no hace exactamente lo que yo quería que hiciera.

Mi código, que ya fue probado y no se ha probado más, ya no funciona. Hay un fallo por ahí que lo impide y una librería que se comporta algo distinto. Pero no hay tests unitarios que se pasan automáticamente en todos los compilados que lo indiquen.

El código llega al entorno de integración y pruebas. El responsable me reporta un "bug" varias semanas o meses después, cuando ya no tengo fresco el código que he hecho. Cuando consigo "turno", meto el debugger, miro y acabo encontrando el fallo que metió mi tercer compañero. Se lo comento. No se acuerda de por qué hizo ese cambio ni para qué servía. Tiene que revisar su código. Al día siguiente me viene con que no puede deshacer ese fallo, ya hay mucho código hecho basado en eso y si lo arregla, igual deja de funcionar. Meto en mi código una "ñapa" para que pase con el fallo de él. Compilo y decido recuperar la prueba manual que hice en su día.

Me ha costado encontrar el código de prueba manual que hice, porque no sé dónde demonios lo tenía. Lo paso otra vez a mano y veo que aquello no "furrula". Debugger al canto y veo, con mucho esfuerzo porque no tengo el código reciente, que la librería del primer compañero no hace lo que yo espero. Se lo comento y el, que tampoco tiene el código reciente, me dice que va a mirarlo. Al día siguiente me viene con que no va a cambiar ese código, porque está mejor así y además tiene mucho código hecho que se basa en eso. Le digo que me está haciendo la puñeta, me acabo peleando con él, no nos hablamos más y meto otra segunda ñapa en mi código para que funcione, que básicamente consiste en hacer una copia de su antigua librería en otro sitio.

Si hubiera hecho los test y mi compañero más adelante hubiera tocado la librería, el test hubiera "cantado" en el momento en el que él la toca. Hubiera sido más fácil llegar a un acuerdo cuando él está todavía haciendo el código y no tiene demasiado hecho basándose en ese cambio. Hubiera sido más receptivo a un "por favor, no me toques eso, que si no me deja de funcionar a mi".

La consecuencia de no hacer tests podría haber sido más agobio en la fase de pruebas, más tensión con los compañeros y soluciones más chapuzas en el código. Y posiblemente más tiempo perdido, ya que mi semana de hacer test es bastante menor que el tiempo que habría dedicado a : hacer unas pruebas manuales + debugger en el entorno de pruebas de un código que no tengo reciente + discusión con un compañero + recuperar el programa de pruebas manuales y volver a probar + debugger para el segundo fallo + discusión con otro compañero + tiempo de ñapear otra vez el código + tiempo que pierden otros compañeros mientras yo tengo ocupado el entorno de pruebas con el debugger.

Se que es difícil y que a veces es un poco pesado, pero viendo los fallos que ya han saltado por los test y lo que posiblemente hubiera pasado más adelante -excepto la pelea con el compañero, que en realidad estamos bien avenidos y no creo que la sangre hubiera llegado al rio-, creo que merece la pena. Siempre es mejor detectar un fallo y corregirlo con tu compañero en la tranquilidad de tu mesa de trabajo, cuando ambos teneis el código reciente y todavía no habeis hecho demasiado código basado en el fallo, que corregirlo en un entorno de pruebas, con la presión de un hito con el cliente cercano, con código del que ya no te acuerdas y sobre el que ya has construido muchas cosas y por tanto no puedes tocar con facilidad.

Y aunque siempre hay excepciones, también estoy convencido que un mismo código puede ser totalmente imposible de testear o se pueden hacer los test en una tarde dependiendo de cómo esté hecho el código.

Sep 07

SQLite y test unitarios

Al instalar WAMP en mi ordenador descubrí SQLite. Es una pequeña base de datos que no necesita nada de configuración y que escribe los datos en un fichero. Supongo que está pensada como base de datos simple para que puedan utilizar las aplicaciones, sin necesidad de todo el montaje necesario de un MySQL o similar.

Sin embargo, creo que hemos encontrado una posible aplicación para ella. Muchas veces, en los test unitarios es necesario tener una base de datos, por lo que el test no se puede correr si no está la base de datos disponible o si falla por algún motivo las conexiones con ella. Con SQLite parece que tenemos la posibilidad de tener ficheros con datos de base de datos, en el mismo sitio de los test, y sería a esos ficheros a los que nos "conectamos" para hacer el test. También es posible que la misma clase de test cree dicho fichero con ciertos datos, tablas o lo que sea.

Existe un driver de SQLite para java, por lo que una vez establecida la Connection, el resto del código es más o menos independiente de la base de datos a utilizar.

La posible pega es que SQLite está basada en C, por lo que es necesario tener su versión para el sistema operativo que se utilice. Esto quiere decir que en cualquier sitio donde se quiera pasar el test, se debe tener las librerías de esta base de datos.

De todas formas, es un pequeño descubrimiento que puede resultar útil para los test unitarios.

Jun 13

Programar para el test unitario

Me decido a escribir este post viendo los comentarios del post anterior y porque los test unitarios también es otra cosa en la que tengo interés. También tenemos que ir poniéndolos.

Estoy de acuerdo en que es muy difícil hacer determinados test -e incluso unos pocos bien hechos-, pero también es cierto que hacer el código de cierta forma ayuda a hacerlos.

Por ejemplo, si tengo una clase que instancia una clase de sockets, lee un mensaje de ella, actúa en consecuencia y como resultado envía otro mensaje con, no podré hacer un test fácilmente, salvo que en el test construya el servidor del socket y lo lance. Y quizás no pueda hacerlo, porque el servidor igual debe correr en otro ordenador y no puedo lanzarlo desde mi clase de test en remoto. Sin embargo, si tengo mi clase de socket que implementa una interface y luego se la paso a la clase que quiero, esa clase es más fácil de probar. Desde el test puedo simular la clase de socket con otra hecha a mi medida -creo que se conoce en este mundillo como mock-object- y que implemente la interface. Esta clase simulada de socket puede devolver un mensaje a medida cuando la clase de prueba intente leer y esta clase simulada recibirá los que la clase de prueba intente enviar, por lo que sí puedo comprobar como se comporta la clase sin necesidad de tener el socket abierto.

Para testear la clase de socket, sí puedo instanciar dos sockets en la clase de test de forma que se comuniquen entre ellos. Claro, siempre que la clase de socket sea suficientemente configurable como para indicarle que el servidor es "localhost".

Con base de datos pasa algo parecido. Si la clase consulta o inserta directamente, es difícil probar salvo que tengas la base de datos. Si haces una clase con interface que es la encargada de realizar las inserciones y consultas, es posible pasarle un mock-object de la clase de base de datos a la clase bajo prueba, con lo que se podría hacer alguna prueba sin necesidad de tener la base de datos y teniendo los datos controlados -es el mismo mock-object el que devuelve los datos a nuestra medida o el que recibe lo que la clase bajo prueba intenta insertar.

En fin, no digo que sea fácil y que no requiera trabajo, pero muchas veces el cómo se hace el código ayuda, dificulta e incluso hace imposible el hacer los test.

Apr 19

Principios básicos de la realización de pruebas

Me encanta lo que he leído aquí sobre los principios básicos de realización de pruebas, sobre todo lo de que hay que probar también que el código no hace lo que tiene no tiene que hacer.

Todo esto me recuerda además que tengo que seguir haciendo test de JUnit del código, porque cuando hay tiempo se van haciendo, pero cuando te entra la pereza o tienes prisas, pasas de ello -como es mi caso ahora mismo-. Pero luego la verdad es que cuando tocas código antiguo, sobre todo si es de una librería que utilizas mucho, lo tocas con más tranquilidad si están esos test hechos. En más de una ocasión nos han avisado antes de meter la pata en una de esas librerías.

Mar 23

Test Continuo

Igual que tenemos integración continua, que básicamente consiste en compilar con frecuencia todo el proyecto desde cero y que hay herramientas que lo hacen automáticmente todos los días, como Cruise Control, también -acabo de descubrir- existe el concepto de test continuo.

Si tenemos unas mínimas buenas costumbres de programación, haremos test unitarios para nuestras clases. Herramientas como Cruise Control y maven se encargan de pasar esos test cada vez que compilamos y nos dicen si hemos o no metido la pata. Pero … ¿por qué no ir más allá?

Acabo de descubrir un plug-in para eclipse -que todavía no he probado, pero lo haré pronto- llamado Continuous Testing Plugin. Tiene pinta de que tú lo instalas en Eclipse, le dices cuales son las clases de test y él, según vas tocando código, va pasando los test de la que eclipse recompila automáticamente. Esto hace que si estropeas un test tocando código, te enteres inmediatamente.

Este mismo Lunes lo pruebo en el trabajo, aunque me da un poco de miedo que ralentice mucho eclipse.

Dec 19

Maven y Junit4

Bueno, a lo que venía la entrada anterior sobre JUnit4 es porque lo he probado también con maven … y no ha funcionado.

Según leo en este artículo, allá por Agosto JUnit4 era muy moderno y maven no lo soportaba. Ahora, en Diciembre, puede que pase lo mismo, o al menos, a mi no me ha funcionado. Se ejecutan los test, quizás porque el nombre que le puse era de testNoSeQue() como en versiones anteriores de JUnit, pero no se ejecuta el método setUp(), al que no he llamado setUp(), sino que simplemente le puse el @org.junit.Before antes.

La persona del artículo parece que se hizo un plugin rápido para soportar esto, que no está todo lo completo que debiera y con sus limitaciones. Bueno, lo he probado así, en plan rápido, y tampoco me ha funcionado.

Todavía tengo que hacer algunas pruebas, como además de poner las anotaciones, nombrar a los métodos como se llamaban antiguamente, setUp() y demás. También en JUnit4 hay una especie de adaptador para que la clase de test se vea como si fuera de un JUnit anterior, simplemente poniendo algo asi en la clase de test

public static junit.framework.Test suite() {
return new junit.framework.JUnit4TestAdapter(SimpleTest.class);
}

Ya contaré qué tal ha ido el tema.

Dec 19

Junit4

Acabo de hacer un pequeño test tonto con JUnit4 para probarlo. El test es este

import static org.junit.Assert.*;
import org.junit.*;

public class UnTest
{
// Se inicializa a false, pare comprobar que se llama al metodo inicializa()
private boolean valor=false;

@org.junit.Before
public void inicializa()
{
valor=true;
}

@Test
public void laPrueba()
{
// Este test falla si no se ha llamado a inicializa()
assertEquals(valor,true);
}

public static void main (String [] args)
{
org.junit.runner.JUnitCore.main(”UnTest”);
}
}

Lo primero que me llama la atención es la nueva forma de marcar los métodos. En las versiones anteriores, JUnit sabía lo que era cada método por el nombre del método. Por ejemplo, el método que debía ejecutarse antes de iniciar los test y para darnos oportunidad de inicializar las cosas debía llamarse setUp(). Los métodos de test debían empezar por testLoQueSea(). Además la clase debía heredar de alguna de las clases de Test de JUnit.

Ahora no. Se usan las anotaciones de java (los @no.se.que que se pone delante de los métodos). Marcando un método como @org.junit.Before, JUnit sabe que debe ejecutar ese método antes de los test. Etiquetando un método como @Test, JUnit sabe que ese es un método de Test. Las clases de test ahora no tienen que heredar de nadie.

Otra cosa que me ha llamado la atención, aunque seguramente es de java 5, es el import static del principio. El método assertEquals() pertenece a la clase org.junit.Assert, así que imagino que ese import static tan curioso es para importar los métodos estáticos de esa clase y poder usarlos sin más.