Sep 20

He leído “Test Driven: TDD and Acceptance TDD for Java Developers”

tdd acceptance testBueno, realmente exagero un poco cuando digo "he leído…", me he dejado sin leer los últimos capítulos.

La parte central del libro me ha encantado, pero la primera parte y la última me han resultado muy pesadas e inútiles, hasta el punto de dejar de leerlo en esa última parte.

Los primeros capítulos nos cuenta principalmente las ventajas de TDD, no se extiende mucho en qué es o cómo se hace TDD, sino que se extiende mucho (muchísimos) en sus ventajas. Estas ventajas son más o menos conocidas por todos (código con menos fallos, confianza en que no estropeamos nada a la hora de hacer refactoring por lo que nos cuesta menos hacerlo, etc, etc). Por ello, varios capítulos dedicados a las ventajas me parece excesivo e incluso repetitivo, ya que una y otra vez comenta las mismas ventajas.

Afortunadamente el grueso de capítulos centrales me ha parecido una maravilla. No por TDD en sí mismo, sino porque se dedica para cada tipo de proyecto diffícilmente testeable (base de datos, jsp, swing, etc) a mostrarnos los distintos frameworks con los que podemos trabajar (jdbc, hibernate, spring, jsf, tapestry, wicket, …) como las librerías útiles para hacer test automáticos en esos frameworks (mockito, clases de spring que son mock objects de interfaces java complejas, jspunit, fit, etc, etc). Lo mejor de todo esto es que no da por supuesto que conocemos cada framework o librería, sino que nos da un resumen de cada uno de ellos, qué es, para qué sirve y cómo se usa. Así que esta parte, más que para hacer test automáticos, sirve realmente para conocer de qué van todas esas siglas que oímos de continuo y a veces no sabemos qué son (jsf, spring mvc, tapestry, jsf, wicket …) y es una primera guía para saber por dónde empezar a usarlas.

Dentro de este grupo de capítulos nos habla de los test de aceptación, que son test automáticos de más alto nivel donde idealmente se considera el sistema como caja negra y se testea desde fuera automáticamente. Idealmente estos test deben estar escritos por el cliente más que por los desarrolladores, puesto que el cliente es el que sabe lo que quiere y si el test es o no suficiente. Así que, aparte de discutir en qué casos se puede/debe testear desde fuera el sistema como caja negra, o cuando se puede/debe testear justo por debajo de la interfaz de usuario, nos introduce en herramientas como fit o fitnesse.

En la última parte, la que he dejado de leer, nos muestra los problemas que podemos tener con nuestros compañeros de trabajo si intentamos convencerlos de que usen TDD, y cómo identificar esos problemas y cómo abordarlos. Pero para mí, programador principalmente, lo de las relaciones humanas no es un libro que me entretenga. Y para mí, cabeza cuadriculada, semejante texto me parece demasiado "etéreo" y evidente. Oír por enésima vez las ya consabidas frases estilo "para que tus compañeros hagan TDD, dales ejemplo haciéndolo tú" o "si te dicen que sí sin entusiasmo igual te están diciendo que no", no me parece que ayuden demasiado a pelearte con los problemas día a día. Este tipo de problemas son problemas que puedes resolver si tu forma de ser es la adecuada para ello, y los resolverás o no independientemente de que hayas leído este libro. Hay quien de forma innata es un lider y que yo sepa, no existe quien de forma innata es un anti-lider y se convierte en lider con un cursillo.

Hablando de cursillos, me ha llamado la atención (creo que tiene toda la razón), este post sobre el peligro de las certificaciones, tan de moda hoy en día http://www.javiergarzas.com/2012/09/problemas-testing.html

Y volviendo al libro otra vez, una frase traducida más o menos libremente que me ha llamado la atención "Los buenos programadores sufren un tipo especial del síndrome de déficit de atención, consistente en poner todo su empeño en usar una herramienta nueva, para abandonarla pocos meses después  y poner nuevamente todo su empeño en otra herramienta más nueva". Real como la vida misma.

Aug 13

Configurando Hibernate para test unitarios.

Cuando hacemos test de JUnit o la herramienta que sea, una de las cosas difíciles de probar son las transacciones con base de datos. En nuestro entorno de desarrollo necesitaríamos una base de datos igual que la de producción y deberíamos borrar todos los datos, meter los datos previos al test para tener la base de datos en un estado conocido, hacer el test y luego revisar el contenido de la base de datos. Esto, aparte de complejo, puede ser muy lento con una conexión a una base de datos real.

Una de las soluciones es utilizar una base de datos en memoria (como HSQLDB, H2, etc). Estas bases de datos suelen ser un jar que no necesita instalación, así que basta con añadirlo a nuestro classpath en los test. Al ser en memoria, los test serán también muy rápidos. Sin embargo, sigue habiendo dos problemas:

  • En cada test habría que crear todas las tablas desde cero.
  • Las SQL de estas bases de datos pueden no ser las mismas que la de nuestra base de datos de producción (MySQL, Oracle, etc).

Para ayudarnos a solucionar estos problemas podemos usar Hibernate (u otra herramienta similar). Si en el fichero de configuración de Hibernate, los de mapeo de clases (o anotaciones) y nuestro código no usamos nada específico de una base de datos (debería ser lo normal salvo casos excepcionales), Hibernate nos hará independientes de la base de datos que usemos (de hecho, ese es uno de sus objetivos). Por otro lado, hibernate tiene un parámetro de configuración hibernate.hbm2ddl.auto en el que le podemos indicar que cree desde cero todas las tablas de la base de datos. Así que Hibernate nos resuelve, en principio, los dos problemas mencionados.

La primera idea que se nos ocurre es tener dos ficheros de configuración de Hibernate (hibernate.cfg.xml), uno para producción con la base de datos real y otro para test. Pero somos buenos programadores y no nos gusta tener cosas repetidas (principio DRY), posiblemente esos ficheros sean prácticamente iguales salvo los parámetros de conexión a la base de datos y el mencionado parámetro hibernate.hbm2ddl.auto, que en nuestro base de datos de test valdrá "create" y en la de producción puede valer algo como "verify".

La solución es simple, basta tener un único fichero de configuración para la base de datos de producción, con toda la parámetrica real. Para los test cargamos ese fichero … y modificamos en código sólo lo que nos interese. Se puede hacer de esta forma

Configuration configuration = new Configuration().configure();
configuration.setProperty("hibernate.connection.url", "jdbc:h2:mem:databaseName");

configuration.setProperty("hibernate.hbm2ddl.auto","create");
SessionFactory sessionFactory = configuration.buildSessionFactory();
 

El método configure() de Configuration leerá el fichero por defecto de configuración (suponemos que es el de producción) y luego, con el método setProperty() podemos "machacar" todas las propiedades que queramos de ese fichero. En el ejemplo sólo hemos cambiado la url de la base de datos y hibernate.hbm2dll.auto, aunque posiblemente deberíamos cambiar también el nombre de la clase del Driver de base de datos, usuario, password, etc.

Ahora sólo nos quedaría poner este código en algún sitio al que nuestras clases de test puedan acceder. Nuestras clases de test obtendrían el sessionFactory usando este código, mientras que el código de producción lo obtendría de la forma habitual, posiblemente a través de un UtilHibernate.java.

Por supuesto, no nos libra nadie de llenar la base de datos en cada test con unos datos conocidos y analizar luego los resultados.

Nos queda un detalle, que es borrar los datos después de cada test. Utilizando la base de datos en memoria como hemos hecho en el código anterior, quedará "viva" y con datos mientras esté arrancada la máquina virtual java. Al ejecutar una batería de test (con mvn test, por ejemplo), la máquina virtual no muere hasta que acaban todos los test, por lo que los datos que unos test van dejando en base de datos quedan para los siguientes test, aunque estén en memoria. Debemos vaciar esos datos después de cada test.

Una opción "pesada" es ir borrando los datos o hacer un "drop" de las tablas, en el orden adecuado para evitar problemas de "contraints". Pero otro método rápido y sencillo consiste en simplemente cerrar el SessionFactory. El siguiente test tendrá que crear un SessionFactory nuevo (con el código de arriba) y así se volverán a crear todas las tablas desde cero. Es decir, nuestras clases de test tendrán los métodos setUp() y tearDown() (o @Before y @After en JUnit 4) de la siguiente forma

public class TestUno extends TestCase {
   private SessionFactory = sessionFactory;

   @Override
   public void setUp() {
      sessionFactory = // Obtener session factory con el codigo anterior
   }

   @Override
   public void tearDown() {
      sessionFactory.close();
   }
   …
}

 

Simplemente un detalle. De esta manera realmente no se borran los datos al terminar en test. En realidad se borran al empezar el siguiente test, ya que el "create" de hibernate.hbm2dll.auto hace exactamente eso, borrar para crear desde cero.

 

Feb 22

Más sobre los test automáticos

 

Comenté ayer que estaba jugando con fest-swing y otras herramientas para test automáticos de pruebas de las interfaces gráficas de usuario. Pues bien, hoy he seguido un poco y he conseguido resolver la mayor parte de los problemas que se me presentaron. Eran, por supuesto, culpa mía, de no conocer la herramienta y de la mala costumbre de jugar el ensayo y error en vez de leerse la documentación.

Por un lado, en vez de bajarme fest-swing de la página de fest-swing, usé el repositorio maven que hay para ello, dejando que fuera maven el que se bajara el jar de fest-swing y todas sus dependencias. Entiendo que esto no tiene mucho que ver (más bien nada) con los problemas que se me presentaron.

El segundo punto, y este sí es importante, es que si hacemos el ejemplo tonto de test que viene con fest-swing no hay ningún problema, sale bien a la primera. Pero si ejecutamos una batería de test con fest-swing, es muy importante reiniciar los recursos que usa fest-swing. Esto se traduce que en el método tearDown() (que JUnit ejecuta después de cada test), debemos hacer la llamada correspondiente

private FrameFixture window; // Clase de Fest-Swing con la ventana principal.

public void tearDown() {
   window.cleanUp();  // reiniciar recursos
}

Y esto ha solucionado gran parte de los problemas que tenía. Ahora las ventanas de test sí salen cuando se ejecutan desde maven y no hacen tantas cosas raras cuando se ejecutan desde eclipse.

Otro tema importante es que aunque Fest-Swing se encarga de acceder a los componentes en el hilo de tratamiento de eventos de swing, es posible que el orden de estos eventos no sea el que esperamos. Si nuestro test manda visualizar una ventana, he encontrado útil esperar a que dicha ventana esté realmente visible antes de proseguir con el test. Esto se puede conseguir con la llamada a window.requireVisible() de fest-swing. Esta llamada hace fallar el test si la ventana no está visible, pero espera un tiempo prudencial antes de cantar el fallo. De esta forma, la llamada se queda bloqueada hasta que la ventana realmente está visible, o hasta que pasa un tiempo excesivo de espera (configurable, por supuesto).

Lo que no he conseguido es que desde eclipse los test se ejecuten dando resultados siempre. Sí es cierto que con todo esto parece que falla menos, pero siguen saliéndome barras grises o negras (en vez de verdes o rojas) de vez en cuando.

Una vez conseguido que todo funcione más o menos correctamente, lo he metido en Subversion y he esperado a que el sistema de integración continua (Hudson) compilara y ejecutara los test. A pesar de las advertencias en la documentación de fest-swing de que puede haber problemas con la integración continua, todo ha ido sobre ruedas y sin problemas.

Otra característica que me ha parecido interesante de fest-swing, aunque no la he probado, es que si un test falla, fest-swing puede capturar y almacenar la pantalla en el momento que se produce el fallo. De esta forma, viendo la foto de nuestra interfaz gráfica de usuario con el fallo, podemos ver si realmente hay algo incorrecto o el fallo se ha producido por otra circunstancia. Por ejemplo, que la ventana de que "windows se ha actualizado" haya salido justo encima de nuestra interfaz justo cuando la estamos testeando.

Así que de momento fest-swing queda incorporado en mi trabajo como herramienta para hacer test de interfaces gráficas de usuario. Sólo me falta ir convenciendo a mis "compis" para que también la usen.

Comenté también en el post anterior que quería probar Abbott. Bien, después del éxito con fest-swing, no lo he hecho. Pero leyendo la documentación de Abbott, he visto una característica también interesante. Abbott, ¿cómo no?, viene con Costello, una aplicación que permite ejecutar nuestra interfaz de usuario de forma normal (a través de su método main()) y actuar manualmente sobre ella. Costello se encargará de capturar toda nuestra interacción con ella (a modo de grabación), de forma que luego Costello puede reproducirla una y otra vez sobre nuestra aplicación, testeando que los resultados son los mismos. Es una forma interesante de hacer los test, sin necesidad de programar demasiado. A pesar de que usaremos fest-swing, probaré Abbott y Costello en algún momento, para ver si se puede testear el sistema completo en el entorno de pruebas.

Feb 20

Test automáticos de interfaces gráficas de usuario

 

Por fin, después de una larga temporada adaptando una y otra vez el mismo software ya hecho a distintos proyectos, ha llegado el momento de empezar a hacer algo nuevo. Vamos a pasar a java unas viejas interfaces gráficas de usuario que teníamos en C++. Por supuesto y con mis ganas de aplicar (y aprender) las buenas costumbres de programación, voy a intentar hacer la parte que me toca siguiendo TDD. Pero, amigo Sancho, con la iglesia hemos topado. Una de las cosas que tradicionalmente se reconoce que son difíciles de probar automáticamente son, precisamente, las interfaces gráficas de usuario.

En una aplicación java SWING, los test básicamente consisten en coger la ventana e ir buscando en los componentes que contienen, recursivamente, hasta que se encuentra el que se busca. Una vez encontrado, actuamos sobre él, haciendo click, metiendo un texto, leyendo su contenido o lo que sea que necesitemos hacer para realizar el test automáticamente. También la clase java.awt.Robot nos permite simular eventos de ratón y teclado sobre los componentes. Pero ni buscar componentes por las ventanas, ni usar la clase Robot es precisamente una tarea cómoda. Por fortuna, hay librerías que nos ayudan a hacer estas tareas y en definitiva, a realizar los test automáticos. Así que a probar esas librerías toca.

La primera que he probado ha sido FEST-Swing, quizás la más conocida para aplicaciones de escritorio con java. La librería está muy bien y contiene características muy interesantes. Las que más me han llamado la atención:

  • Según recomendación de java, todos los accesos a ventanas deben hacerse en el hilo de refresco de ventanas y tratamiento de eventos que nos proporciona java (EDT Event dispatch Thread). Pues bien, una simple configuración de FEST en el test hace que salte si accedemos a los componentes SWING fuera de ese hilo.
  • Precisamente ese hilo es un poco rollo. Nuestro test no se ejecuta en ese thread EDT y si el código que testeamos mete algo en un componente SWING usando el thread EDT (como se recomienda), nuestro test debe esperar a que el thread EDT termine antes de verificar el contenido del componente. Pues bien, FEST nos hace esto totalmente transparente. Cuando con FEST accedemos a un componente para ver su contenido, FEST espera que terminen los hilos EDT.
  • Aunque al principio parece raro, es realmente sencillo de usar. Podemos buscar en general cualquier componente con un método simple, indicando qué tipo de componente buscamos (JTextFiel, JLabel, etc), por su nombre (si hemos tenido la precaución de ponérselo con setName()) o haciéndonos un filtro a medida (un Matcher)

Pero no es oro todo lo que reluce. Me he encontrado con dos problemas que me van a dificultar seriamente su uso, más un pequeño problemilla.

  • El pequeño problemilla es que si ejecuto un test desde eclipse, a veces parece que no se ejecuta el test. Cuando ejecutamos un test en eclipse, al final sale una barra verde o roja, indicando si el test pasa o no. Pues resulta que, de forma aleatoria, el test termina y dicha barrita queda en negro o en gris, como si eclipse no detectara que el test ha terminado. Bueno, no es grave, puesto que no voy a ejecutar habitualmente los test con eclipse y lo peor que puede pasar es que tenga que darle dos o tres veces hasta obtener la barra verde o roja.
  • Un problema más serio lo he tenido con maven. Cuando ejecuto el test en eclipse, FEST visualiza las ventanas bajo test, se hace el test y pasa el test o falla (o se queda en gris/negro). Ejecutando el mismo test desde maven, ni siquiera sale la ventana y el test falla sistemáticamente. Investigaré en este tema, pero si no consigo solucionarlo, descartaré el uso de FEST en el entorno que trabajo.
  • Y un segundo posible problema es la integración continua. Hudson se encarga de compilar y pasar los test de nuestros proyectos todas las noches. FEST requiere que la ventana se visualice y por tanto, requiere que Hudson corra en un terminal abierto en sesión, sin salvapantallas ni nada que impida la correcta visualización de la ventana. En la documentación de FEST indican varias formas para solucionar problemas con la integración continua. Me pondré con ellas si consigo pasar el punto anterior, lo de ejecutar los test con maven, ya que si hudson ejecuta maven y maven no muestra las ventanas, me da igual que haya o no pantalla disponible.

Después de esto, fui a buscar otra herramienta y me topé con uispec4j. Mucho más simple que FEST de uso (aunque menos potente) y puede cumplir para test no excesivamente ambiciosos. A diferencia de FEST, no requiere que las ventanas se hagan visibles. Esto seguramente dé menos problemas a la hora de integración continua, pero posiblemente haga que los test sean menos reales. Tampoco controla el acceso a los componentes a través del thread EDT, por lo que las esperas por dicho thread debemos codificarlas en nuestro test. Así que nada, me puse a probarla y ¿cómo no?, me encontré con un par de problemillas.

  • Al igual que FEST, al ejecutar los test con eclipse, a veces la barra no queda ni verde ni roja, sino gris claro o negro. Esto ya hace pensar que no es problema ni de FEST ni de uispect4j, sino quizás un test mal hecho por mi parte o a algún problema con mi eclipse. Tengo que probar en casa.
  • Al ejecutar los test, me salta una fea excepción. Buscando en google, veo que hay un problema cuando juntas wndows xp, java 6 y uispec4j. ¡¡ qué casualidad !! ¡¡ justo mi configuración !!.

En fin, sigo peleándome a ver si consigo que alguna de estas herramientas me funcione correctamente. También me gustaría echar un ojo a abbott, herramienta similar aunque quizás menos conocida.

Por cierto, quiero dejar claro que no estoy diciendo que FEST o uispec4j estén mal o no funcionen correctamente. Simplemente estoy contando lo que me ha pasado trabajando dos o tres días con ellas mientras hacía el código real de mi aplicación. Pueden ser fallos de las librerías, de mi configuración concreta, de mi código que haga cosas raras o de que estoy pagando la novatada haciendo mis primeros test de este tipo con este tipo de herramientas.

 

Jun 30

JUnit y Log4j

 

Cuando hacemos nuestras clases de test de JUnit, puede ser bastante normal testear clases que usan log4j. Si en nuestros test queemos que se muestre la traza de esas clases, debemos configurar log4j. Lo normal es hacerlo en el método setUp() del test (el que se ejecuta justo antes de cada uno de los test) de una forma similar a esta

public void setUp() {
   BasicConfigurator.configure();
}

Quizás, en vez de en el setUp() lo hagamos en el constructor de la clase de test.

Pero esto tiene una pequeña cosa que debemos saber. Cada vez que llamamos al BasicConfigurator.configure(), se añade un ConsoleAppender al log4j. Por ello, cada vez que se ejecuta setUp() (una vez por cada test), se añade un appender nuevo… y la traza empieza a salir repetida, ya que cada appender creado saca el mensaje por consola. Si lo hacemos en el constructor y tenemos varias clases de test, tenemos el mismo problema.

Para evitar esto, lo mas sencillo es llamar a BasicConfigurator.configure() en el método setUp() (y no en el constructor), pero poner tambien un método tearDown(), que JUnit llama automáticamente al final de cada test, y ahí borrar los appenders añadidos

public void tearDown() {
   LogManager.shutdown();
}

De esta forma, por cada test, primero se añadirá el appender y después se borrará. Cada test tendrá un único appender y la salida de log será única.

Mar 21

Jugando con TDD, MVP, PF, EasyMock y Mockito

 

Normalmente no codifico, lo he dejado por imposible. Un día de trabajo mio normal consiste en atender una cola de gente que viene a preguntarme cosas, algunos pocos sobre java, algunos más sobre las herramientas que usamos (maven, archiva, redmine, etc) y muchos (jefes sobre todo) sobre incidencias de los proyectos, cómo va el trabajo, etc. En esas condiciones, es muy difícil concentrarse cinco minutos seguidos y mucho menos diez, por lo que hace tiempo que decidí no meterme en serio a codificar.

Sin embargo, el viernes de la semana pasada fue un día excepcionalmente tranquilo. Todo el mundo sentado en su sitio, a sus cosas y sin que nadie viniera a decirme nada. Cuando pasan estas cosas, suelo aburrirme un poco, no empiezo a hacer nada en serio por temor a las interrupciones que seguro que están al caer. Pero ese viernes la tranquilidad se estaba prolongando demasiado, había un módulo nuevo para hacer desde cero y yo acababa de leerme algo sobre TDD, así que hablé con la persona que tenía asignada ese módulo y no le molestó que lo empezara yo (aun a sabiendas de que lo dejaré a medias y le tocará seguir a él). Así que así me he tirado casi una semana, codificando y jugando al TDD.

Las primeras historias fueron más o menos sencillas. Hice mis test, luego mi código, refactoricé un poco y vuelta a empezar. Pero llegó un momento en que el tema se me complicó más de la cuenta. Llegó una historia de usuario en la que había involucrada una parte de interface de usuario con Swing que no era trivial de probar con un test automático. Y me puse a investigar en internet.

Primero encontré la posibilidad de ponerse con los métodos de java getComponents() y similares para tratar de buscar el componente java (el JTextField, el JButton o lo que sea) que necesitas para tu test y a partir de ahí usar el método setText(), getText() o doClick() para simular la interacción con el usurio. Aunque eso sí te puede sacar del apuro en un caso puntual, no parece que sea la mejor forma de hacerlo por sistema. El test se llena de código rebuscado con bucles y recursiones hasta localizar el componente que te interesa y que por supuesto, tienes que haber dado previamente un nombre con el método setName() para distinguirlo de otros componentes del mismo tipo.

Seguí buscando y encontré que todo ese proceso se facilita con librerías como FEST-Swing, que de alguna forma facilitan y eliminan todo el código raro que mencioné en el apartado anterior. No llegué a probarlo porque no encontré un repositorio maven que me permitiera bajarme la librería fácilmente.

Y seguí buscando y encontré una cosa interesante: El patrón MVP (Model-View-Presenter) y el PF (Presenter First). La idea de estos patrones es que una interface gráfica de usuario es muy compleja de testear automáticamente, por lo que debemos hacerla lo más tonta posible, para no tener que testearla. Es decir, un panel de nuestra interface gráfica de usuario únicamente debe tener métodos set y get para mostrar datos y recogerlos. No debe tener actionListeners para los botones ni ningún tipo de inteligencia. Toda la inteligencia de esa interface se hace en otra clase, llamada Presenter. Esta clase debe recibir dos interfaces en el constructor, una de la interface gráfica de usuario y otra correspondiente al modelo de datos. La clase Presenter debe hacer ella misma de listener de los botones y encargarse de interactuar con el modelo y la interface de usuario. Haciéndolo así, es fácil testear toda esta lógica, ya que podemos instanciar en nuestro test el Presenter y pasarle dos mock-objects, el del modelo y el de la interface del usuario.

Me gustó la idea, así que rápidamente me puse a modificar mi código para usar un Presenter y quitarle el máximo posible de código (todo) a los componentes swing. Por supuesto, siguiendo TDD, primero comencé a hacer test y modificar los ya hechos.

Pero lleguó el momento de usar los mock-objects. Por un lado, podía hacérmelos a mano, pero por otro lado hace tiempo que conozco y nunca he usado la librería EasyMock. Supuestamente (y en la realidad) facilita la creación de los mock-objects, haciéndolo automáticamente. Viendo la documentación me pareció fácil de usar y encontré además la librería en los repositorios maven, así que me puse con ello. Sin embargo, me ha decepcionado un poco. Los EasyMock están bien para determinadas cosas, pero les veo dos pegas que no los hacen ideales en todas las circunstancias:

  • La filosofía de un EasyMock en el test es crear el mock-object, decirle a qué métodos se van a llamar durante el test y con qué parámetros, luego llamar a su método replay() para indicarle que empieza el test de verdad y hacer el test. Si durante el test no se sigue la secuencia de llamadas indicada anteriormente, el test falla. ¿Cual es la pega que le veo a esto?. Pues sobre todo para cosas como TDD, en que las clases se refactorizan y van evolucionando según se van desarrollando nuevas historias. Es muy fácil que sin modificar el funcionamiento de la clase, la secuencia de llamadas cambie, porque se hagan más llamadas, o se reemplacen algunas por otras o lo que sea. Esto obliga a ir rehaciendo los test, aunque la funcionalidad no cambie, simplemente porque cambia el orden o el número de llamadas que se hacen.
  • Los EasyMock, aunque no lo puedo asegurar porque no lo he mirado en profundidad, no permiten que un mock-object genere algún tipo de eventos. En el caso concreto del patrón MVP y PF mencionados antes, un Presenter añade un ActionListener a un botón de la interface de usuario. Si estoy testeando el Presenter y le paso un mock de la interface de usuario, no puedo simular un click en el botón o hacer la llamada al actionListener. Unicamente puedo ver que el Presenter efectivamente se suscribe llamando al addActionListener() correspondiente.

Tratando de solucionar algunas de estas pegas (la primera sobre todo), hay otra librería, Mockito, que se basa en EasyMock, pero trata de darle una API más sencilla y versátil. Tengo pendiente echarle un ojo, pero creo que la segunda pega no me la va a solucionar. Supongo que para cosas como los listener, no me quedará más remedio que hacerme los mock-object a medida … y a mano.

Lo que me da realmente pena de todo esto, es la persona que va a seguir con este código, cuando a mí empiecen a agobiarme con otros temas. Veamos que cara pone cuando empiece a ver Presenters y Mockitos. O lo tira todo a la basura, o aprovecha para aprender una nueva forma de hacer las cosas y así, al menos, tendré alguna opinión más sobre el tema, aparte de la mía propia. Esto es, nuevamente, una sopa de piedras o un capitán araña. Ya veremos en qué acaba.

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.