Automatización de test en proyecto «brown-field»

Hace poco más de un mes comencé a trabajar en un proyecto de desarrollo de una aplicación existente (brown-field project). La aplicación en cuestión era un monolito construido con una antigua versión de Grails y luego actualizado a Grails 2.5 y con no más de 30 pruebas unitarias. La visión del proyecto tenia 2 objetivos bien claros (sin ningún orden particular):

  • Realizar una rearquitectura de la aplicación para llevarla a un esquema de microservicios
  • Agregar un conjunto de funcionalidades relacionadas principalmente a cuestiones de integración con otras aplicaciones.

Una de mis primeras propuestas para el Product Owner fue comenzar generando un conjunto mínimo de pruebas end-2-end de regresión que nos dieran cierta seguridad para poder realizar modificaciones sobre la aplicación sin sonarla. El Product Owner siendo una persona de formación técnica estuvo de acuerdo con la propuesta y hacia allí fuimos. Comenzamos identificando las funcionalidades más críticas para el negocio y diseñando una arquitectura de pruebas para ellas.

Dado que la aplicación no había sido desarrollada con la «testeabilidad» en mente, resultaba muy dificil realizar pruebas que no unitarias y de API, asi que decidimos ir por un enfoque de caja negra realizando pruebas end-2-end que interactuaran con la aplicación via UI tal cual un usuario real.

Luego de un de par de pruebas de concepto llegamos a la siguiente arquitectura de tests:

test_arq

  • Gherkin: la idea de usar este DSL user-friendly no tiene un fundamento en que nos permite espeficar flujos funcionales desde una perspectiva de negocio.
  • Cucumber-JVM: como la aplicación está hecha en Grails el cual ya corre sobre JVM y los nuevos servicios se construirán en Java, nos pareio que Cucumber-JVM era la opción obvia. si es cierto que también podríamos haber usar JBehave, la realidad es que Cucumber-JVM está más difundido y por ello hay más documentación de soporte.
  • DB-Unit: lo utilizamos para cargar distintos set de datos para cada contexto de tests. Asimismo DBUnit se encarga de borrar los datos existentes antes de cargar cada dataset.
  • Selenium: era la opción default para interactuar con aplicaciones web. Para generar cierto nivel de abstracción sobre el webdriver utilizamos PageObjects.
  • JUnit: finalmente tenemos JUnit como librería de aserciones y motor de ejecución.

Continuará…

Automatización de pruebas en AS400, fin de la historia

Como mencioné tiempo atrás, en octubre comencé a trabajar en un proyecto para automatizar pruebas de una aplicación RPG/AS400. He completado mi participación en el proyecto, logramos dejar una arquitectura de pruebas funcionando con un par de casos automatizados, ahora está en manos del propio equipo de desarrollo continuar agregando nuevos casos.

El siguiente gráfico resume la arquitectura implementada:

test_as400

La arquitectura está armada de forma tal que un tester puede agregar fácilmente nuevos casos sin tener que meterse mucho en el código. Algo de código obviamente va a ser necesario escribir pero por la forma en que todo quedó armado (con template methods) es realmente poco lo que hay que codear.

Los casos de pruebas se generan con FitNesse y luego el gluecode/driver utiliza dos librerías de IBM: una para invocar funciones nativas en AS400 que se encargan de hacer el setup del ambiente con los datos iniciales y otra para la creación de las colas MQ y para leer/escribir en dichas colas.

Como la aplicación y los tests están codeados en distintas tecnología y almacenados en distintos repositorios, el servidor de integración continua (jenkins) quedó configurado para correr el set de tests ante cada commit y también en forma periódica 3 veces al día.

Estoy conforme con el resultado de nuestro trabajo, al mismo tiempo creo que fue un lindo desafío que me permitió aprender un poquito del mundo AS400. Ahora está en manos del codear más pruebas y sacar valor al trabajo realizado.

 

 

 

 

Reflexiones sobre la automatización de pruebas

La semana pasada dicté junto a @dfontde un taller de automatización de pruebas. Entre la audiencia tuvimos principalmente desarrolladores.

Obviamente uno de los temas que tratamos fue la automatización de pruebas a nivel de Interface Gráfica de Usuario (GUI). De hecho cada vez que dicto este taller la gente suele venir con la expectativa de encontrar la bala de plata para poder automatizar pruebas exclusivamente a nivel GUI. Mi posición al respecto es siempre la misma: ¿en verdad que quieres automatizar pruebas a ese nivel?.  A lo largo del taller trabajamos fuertemente sobre la cuestión de a qué nivel automatizar las pruebas y muchos suelen convencerse que puede resultarles más conveniente trabajar la automatización a nivel del API/Servicios.

Las pruebas a nivel GUI suelen ser frágiles (cambios mínimos en la GUI pueden romper las pruebas) y costosas de ejecutar (ya que requieren instanciar la aplicación completa). Pero al mismo tiempo pueden ser la única opción de automatización, ya que prueban la aplicación en modo caja negra, no importa como esté construida la aplicación pues simplemente se interactúa con ella desde afuera. Este es un punto muy relevante para el caso de aplicaciones legacy, que muchas veces han sido construidas sin características de testeabilidad. Más allá de estos argumentos que son clásicos y pueden encontrarse en la bibliografía hay algunas cuestiones adicionales que se han puesto de manifiesto en los últimos años.

  • Por un lado cada vez son más comunes las famosas SPAs (Single Page Application), muchas veces basadas en frameworks «client-side»como AngularJS. Esto implica una creciente cantidad de código JavaScript. Este tipo de aplicaciones no suele ser tan simple de automatizar a nivel GUI pues no todas las herramientas no se lo bancan bien. Pero al mismo tiempo aparece un nuevo nivel de automatización posible: pruebas unitarias de los componentes JavaScript.
  • Por otro lado, los frameworks de desarrollo / generadores de código «server-side» como Spring-Boot ya incluyen como parte de sus funcionalidades una propuesta para la codificación de pruebas automatizadas a nivel de API e incluso en algunos casos se encargan de generar el código de algunas de estas pruebas.

En mi opinión estas dos cuestiones refuerzan necesidad de repensar cualquier intento de automatización de pruebas a nivel GUI. Y quiero ser muy claro en cuanto a esto: no estoy diciendo que no se automaticen pruebas a nivel GUI, sino simplemente que no me parece una buena estrategia que el grueso de las pruebas automatizadas sea a nivel GUI. Creo que hay que pensar en una estrategia de automatización más integral que incluya pruebas a distintos niveles. Esto nos permitirá tener una mejor cobertura con un esfuerzo mucho menor y repartido entre los distintos perfiles del equipo (tengamos presente que el testing no es sólo responsabilidad de los testers).

Ejercicio: interpretación de métricas de test y cobertura

Amo las métricas, creo que es un efecto colateral de la búsqueda de la mejora continua. Para mejorar es necesario medir, pero con medir no basta, hay que saber qué medir y luego hay que interpretar lo medido para poder accionar al respecto.

Hoy quiero compartir un actividad que hicimos esta semana con mis alumnos de Ingeniería de software en UNTreF. La dinámica fue simple, dado un proyecto tomamos los gráficos de evolución de tests y cobertura generados por Jenkins y los analizamos para generar hipótesis a partir de ellos. Invito a los lectores a sumarse a este ejercicio, analicen los gráficos e intenten extraer información de ellos. Comparto un poco de contexto que les puede resultar útil para el análisis: el proyecto en cuestión es un modelo (o sea pura lógica, sin UI ni base de datos), los desarrolladores son alumnos universitarios que tenían que trabajar con 2 consignas:

  1. Hacer el desarrollo usando TDD
  2. Hacer integración commiteando al repositorio central (en el mainline) luego de cada nuevo test verde.
  3. Si el build se rompe, se para la línea hasta que sea arreglado

analisis_1

Aquí va mi análisis y con mis hipótesis/conclusiones (=>):

  • En el build #10 se agregaron 10 tests => no respetaron la consigna 2
  • Pero a pesar del agregado de tests la cobertura disminuyó => posiblemente por el agregado de código de dominio sin los correspondientes tests => violación de la consigna 1
  • En los siguientes builds (#11,#12 y #13) la cantidad de test aumenta de a uno y también se evidencia un aumento en la cobertura.
  • En los builds #14 y #15 se evidencian nuevamente saltos grandes en la cantidad de tests y de la mano de ello también un aumento de la cobertura.
  • Entre los builds #15 y #18 la cantidad de tests se mantiene constante y lo mismo ocurre con la cobertura => esto podría indicar pasos de refactoring.
  • En el build #19 disminuye la cantidad de tests y con ello la cobertura => esto podría deberse a un cambio en el código de dominio que no fue acompañado completamente por la correspondiente actualización de test.
  • Finalmente la cantidad de tests se mantiene constante en los últimos builds pero la cobertura disminuye => me hace pensar en agregado de código sin los correspondientes tests.

¿algo que agregar?

Automatización de pruebas en AS400

Hace un par de semanas comencé a trabajar un proyecto con @dfontde y @CodeRaguet para automatizar pruebas de una aplicación AS400/RPG. Nunca en mi mi vida había hecho nada con esa tecnología y creo que justamente ese fue un factor clave para que decida involucrarme en el proyecto.

Nuestro primer día de trabajo, uno de los programadores de la aplicación en cuestión nos hizo un breve tour introductorio a la plataforma AS400, al lenguaje RPG y a la aplicación sobre la cual debemos trabajar.

Dadas las particularidades de la plataforma y de cómo está construida la aplicación, decidimos tomar un enfoque de caja negra. Básicamente vamos a intercambiar mensajes con la aplicación via una cola. De esta forma nuestras pruebas se reducen a correr un setup (el cual hay que codear en RPG), escribir un mensaje en una cola, leer la respuesta de otra cola y verificar dicha respuesta. Todo esto lo vamos a codear en Java, que si bien no es «el sueño del pibe», me resulta bastante más amistoso que RPG.

Hicimos una primer prueba de concepto exitosa para entender la interacción Java/PC <-> RPG/AS400. En este momento estamos analizando la herramienta de especificación de pruebas para lo cual estamos haciendo una prueba de concepto con FitNesse.

Continuará…

Cursos en la previa de Agiles 2015

Como de costumbre en la previa de Agiles 2015 habrá un conjunto de cursos organizados de forma independiente. En ese contexto estaré dictando en Montevideo mi Taller de Continuous Delivery y Prueba automatizada.

El términos generales el taller está divido en 2 partes. La primera enfocada en los conceptos centrales de la práctica de Continuous Delivery y un conjunto de técnicas y herramientas para su implementación que incluyen Jenkins, Puppet y Docker. La segunda se enfoca en la automatización de pruebas, lo cual constituye un punto fundamental en toda estrategia de Continuous Delivery. En esta segunda parte veremos herramientas tales como JUnit, Cucumber-JVM y FitNesse. Si bien el taller tiene una base conceptual independiente de toda tecnología, la parte práctica de automatización de pruebas se realiza con Java.

Los detalles de logística e inscripción están disponibles en la página de Evolución Ágil.

Selección de herramientas de prueba (parte 2)

En la primera entrega de esta serie presenté una arquitectura de prueba de referencia. En esta entrega voy a instanciar esa arquitectura de referencia en Ruby.

Comenzado por las pruebas del programador (unitarias y de componentes), tendremos que nuestro objeto de prueba serán clases programadas en Ruby. Para hacer este tipo de pruebas una de las herramientas más populares es RSpec la cual nos daría una arquitectura de pruebas conformada por:

  • Lenguaje de especificación: Lenguaje de Dominio Específico provisto por RSpec
  • Intérprete: es el propio RSpec
  • Driver: dado que estamos testeando clases Ruby, que estamos haciendo pruebas unitarias y de componentes y que RSpec está hecho en Ruby no requerimos de ningún driver particular. En todo caso nuestro driver es el mismo Ruby
  • Librería de aserciones: es el mismo RSpec
  • Motor de ejecución: RSpec

Un detalle a tener presente es que este tipo de pruebas son pruebas que hace el programador para sí mismo, ya sea para guiar el desarrollo (si está haciendo TDD) o bien para garantizar que sus clases se comportan acorde a lo esperado. Este tipo de pruebas no suelen interesar al usuario, pues son de índole técnica y al mismo tiempo mucho más granulares que las funcionalidades pedidas por el usuario.

arquitectura_developer_tests_ruby

 

 

Pasando ahora a las pruebas de aceptación funcionales, si bien podríamos utilizar el mismo set de herramientas, la realidad es que esperamos que el usuario esté involucrado y por ello usaremos un set distinto de herramientas de cara tener un lenguaje de especificación más «amistoso» para el usuario. Al mismo tiempo el objeto de nuestras pruebas ya no son clases sino la aplicación como un todo:

  • Lenguaje de especificación: Gherkin
  • Intérprete: Cucumber
  • Driver: dependerá de la forma en que pretendamos comunicarnos con la aplicación. Suponiendo que nos comunicamos via HTTP, podríamos utilizar Capybara en conjunto con WebDriver de Selenium.
  • Librería de aserciones: RSpec
  • Motor de ejecución: Cucumber

arquitectura_user_tests_ruby

 

Selección de herramientas de prueba (parte 1)

Una de las decisiones a tomar al querer hacer pruebas automatizadas es qué herramientas utilizar. Para poder tomar esta decisión resulta importante entender mínimamente la arquitectura de una infraestructura de pruebas automatizadas. El siguiente gráfico resumen de forma muy general una arquitectura de modelo para esta problemática.

arq_pruebasSi bien el gráfico puede sugerir una arquitectura de capas, la misma rara vez se cumple a nivel de implementación pero si es cierto que para elemento representa un nivel de abstracción distinto en la estructura de la solución.

En el nivel de abstracción de más alto tenemos un lenguaje de especificación que nos permite expresar la prueba.
Un ejemplo de un lenguaje posible de especificación podría ser Gherkin.

En un segundo nivel tenemos un intérprete de ese lenguaje
que nos permitirá relacionar la especificación abstracta con fragmentos de código que escribiremos nosotros y que comúnmente se denominan «glue code».
Para el caso Gherkin tenemos como intérpretes las familia de herramientas Cucumber, en particular si quisieras escribir nuestro código en Java podríamos utilizar Cucumber-JVM.

En el tercer nivel tendremos un componente que denominamos **driver** que nos permitirá interactuar con nuestra aplicación. Lo que hace el driver en concreto es implementar el protocolo de comunicación necesario para interactuar con el sistema bajo prueba.
Siguiendo con el ejemplo anterior y asumiendo que el sistema bajo prueba es de tipo web, podríamos entonces utilizar Selenium Web Driver el ofrece una implementación en Java.

El siguiente elemento de nuestra arquitectura es la librería de aserciones, la cual nos permitirá precisamente hacer aserciones sobre el resultado de las operaciones realizadas sobre el sistema bajo prueba.
En este caso es común utilizar alguna librería de la familia xUnit. En el caso de Java utilizaríamos JUnit.

Finalmente, como último ítem de esta enumeración tenemos un motor de ejecución que se encarga de instanciar y ejecutar los elementos previamente mencionados.
En este caso una alternativa muy común en el mundo Java en particular es utilizar el mismísimo JUnit.

En la segunda parte de este artículo ofreceré algunos ejemplos concretos de implementación de esta arquitectura.

Fin del taller de prueba automatizada

El jueves pasado tuvimos el quinto y último encuentro del taller de prueba automatizada que dictamos junto con Pablo Tobia en FIUBA. El taller era abierto y gratuito pero con inscripción previa. Justamente al ser totalmente gratuito implicaba cierto riesgo en lo que hace a la planificación, pues es común que mucha gente se inscriba y luego no asista. Para mitigar esto, pedimos a todos los interesados que para confirmar su vacante resolvieran un pequeño ejercicio de programación.

Inicialmente tuvimos unas 40 personas que manifestaron interés en participar del taller cuando aún no estaba definido el horario. Una vez que definimos el horario le informamos a estas ~40 personas y les pedimos que resolvieran el ejercicio para completar su inscripción. Sólo 12 de los 40 lo hicieron. Y aún así, de esos 12 solo 10 participaron del taller.

El taller consistió en 5 encuentros de  ~3 horas. Como guía del taller utilizamos los cuadrantes de testing de Marrick con lo cual el contenido quedó organizado de la siguiente forma:

  • Encuentro #1: conceptos básicos de testing y presentación del enfoque de los cuadrantes
  • Encuentro #2:
    • foco en el cuadrante #1 (support programming & technology facing),
    • test automation manifest
    • xUnit test patterns
    • Test doubles
    • Coverage
  • Encuentro #3 y #4:
    • foco en el cuadrante #2 (support programming & business facing)
    • tipos de herramientas para automatización de pruebas
    • arquitectura de las herramientas de automatización de pruebas
    • testing de aplicaciones tipo enterprise
    • SeleniumIDE, Cucumber, Fitnesse
    • Phantom, Slimer and CasperJS
    • Watir & PageObjects
  • Encuentro #5:
    • foco en los cuadrantes #3 y #4 (test que critican el producto)
    • Stress testing, JMeter
    • AB testing
    • Calidad del software más allá del testing
    • Testing en distintos tipos de proceso de desarrollo
    • El rol de tester

Tanto docentes como alumnos quedamos muy contentos con el taller y concluimos que perfectamente podria ser una materia de las carreras de informática de FIUBA (en realidad es posible que también aplique a otras carreras pero sabemos que en FIUBA estos temas no estan cubiertos por ninguna materia en la actualidad). En caso querer convertir este taller en un materia (o en un curso más amplio) deberíamos agregar temas talles como: planificación de la prueba, definición y administración de casos de prueba, herramientas de soporte, Testlink, modelos de calidad, etc.

Finalmente quiero agradecer a todos los que participaron este experimento para mi ha sido un gran placer el tiempo compartido.

Clasificación de herramientas para automatizar pruebas de funcionales

Las pruebas funcionales son las que típicamente hace un tester, que más le importa al usuario y sería ideal definir en conjunto con él, pues estas pruebas interactúan con el SUT desde la perspectiva del usuario. Son pruebas que Marrick clasifica como Business Facing.

Para este tipo de pruebas es muy relevante la forma en que las herramientas permiten expresar la prueba, ya que dado que son «pruebas de usuario», dependiendo de la forma en este planteado el proyecto podríamos querer que estas pruebas sean escritas por el usuario.
En este sentido es que las herramientas para automatizar este tipo de pruebas han adoptado distintos enfoques:

  • Record and Play: permiten «grabar» la interacción del usuario con el SUT para luego reproducirlo. Con este tipo de herramienta, básicamente se ejecuta manualmente 1 una vez cada caso de prueba mientras la herramienta va grabando la interacción de manera que la misma y una completada la grabación las pruebas pueden repetirse tantas veces como se guste de forma automática. Un ejemplo de este tipo de herramientas es SeleniumIDE.
  • Keywork-Driven: este tipo de herramientas permiten definir las pruebas utilizando un conjunto de palabras claves par estructurar la prueba. Luego es necesario escribir cierto código (glue code) para vincular la prueba (expresada con palabras claves específicas) y el SUT. En esta categoría de herramientas entra toda la familia de Cucumber.
  • Data-Driven: este tipo de herramientas permiten especificar la prueba a partir de condiciones expresadas en tablas. Cada tabla es interpretada por por cierto código (glue code) que permite la interacción con el SUT. Las herramientas de la familia Fitnesse entran en este grupo.

Si bien muchas veces las herramientas pueden utilizarse de diversas formas, en general suelen ser concebidas para ser utilizadas de una forma particular (una silla es concebida para sentarse, sin embargo en ocasiones es posible utilizarla para elevar nuestra altura como si fuera una escalera). En este sentido cada herramienta puede verse más orientada con alguno de los grupos mencionados, pero ello no quita que la podamos usar de forma distinta.