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.

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.

 

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.