Dinámica para enseñanza de pruebas automatizadas y especificación con pruebas

La dinámica que voy a describir aquí fue diseñada por Gabi Falcone para una clase de Algo3@Fiuba. El objetivo de la misma es presentar las pruebas automatizadas y su uso como herramienta de especificación.

Contexto: en el curso tenemos alrededor de 50 alumnos. El aula de clase no tiene computadoras, pero algunos alumnos traen sus portátiles.

Preparación

Previo a la clase se le pide a alumnos resolver un problema de programación que implica crear algunas clases. En necesario contar con al menos dos problemas de complejidad similar. Dependiendo de la cantidad de alumnos puede resultar conveniente contar con más problemas. En nuestro caso trabajamos con 3. Se le pide a cada alumno resolver solo uno de los problemas. Para esto nosotros solemos utilizar una heurística basada en el número de padrón:

  • padrones terminados en 0,1,2,3 => problema 1
  • padrones terminados en  4,5,6 => problema 2
  • padrones terminados en  7,8,9 => problema 3

La clase comienza con una explicación conceptual de pruebas unitarias automatizadas y del framework xUnit. En nuestro caso explicamos SUnit pues trabajamos con Smalltalk. Luego de la explicación se pone manos a la obra con la dinámica de trabajo interactiva.

La dinámica se hace en grupos donde cada grupo tiene una computadora. El primer paso entonces es armar los grupos. Se le pide a todos que salgan del aula, armamos tres filas con la gente que había resuelto cada variante del problema pedido inicialmente. A continuación en cada fila se le pide a los que tengan computadora que pasen al frente y elijan 2 o 3 compañeros de la fila que no tengan computadora. Así quedan armados los grupos. A continuación se les pide volver a entrar al aula y que se ubiquen en las mesas según los grupos armados.

Primera etapa

A cada grupo se le pide que escriba un conjunto de pruebas unitarias para el problema que les tocó resolver. Como todos los alumnos de un mismo grupo ya han resulto el mismo problema, la escritura de las pruebas resulta bastante directa.  Al mismo tiempo como ya tiene el código que resuelve el problema, los alumnos pueden ir corriendo los tests a medida que los van escribiendo. Explícitamente se les indica que roten el conductor (la persona que está al teclado) con el fin de que todos tengan el teclado un rato. En concreto cada prueba debe ser codeada por una persona distinta.
Al cabo de 30 minutos deberían tener una cantidad relevante de pruebas.

Segunda etapa

Cada grupo entrega el conjunto de pruebas creadas (sólo el código de las pruebas, no el código que hace pasar las pruebas) a otro grupo que haya estado trabajando sobre un problema distinto. Ahora cada grupo debe resolver un problema nuevo pero en este caso tiene un conjunto de pruebas como especificación de lo que debe resolver y al mismo tiempo puede ir corriendo esas pruebas para verificar la solución que va generando. Una vez más se pide explícitamente que vayan rotando el conductor.
Al cabo de unos 30 minutos, deben tener la mayoría de las pruebas pasando.

Cierre

Aquí se pide a los alumnos dejar a un lado las computadoras para hacer una puesta en común de la actividad. Se plantean algunas preguntas para disparar la discusión:

  • ¿Cómo les resultó la experiencia en general?
  • ¿Cómo les resultó el resolver un problema a partir de una especificación en forma de pruebas?
  • ¿Cómo les resultó escribir pruebas para un problema ya resuelto? ¿Qué utilidad le ven a esto?

Nota: tener presente que durante esta actividad no se hace TDD sino que simplemente se utilizan pruebas como especificación lo cual es solo una parte de TDD. Notar que aquí el trabajo comienza con todo un conjunto de pruebas ya escrito, mientras que en TDD las pruebas se van escribiendo de a una por vez.

Explicación básica de Maven

Maven es una herramienta de build, posiblemente la más utilizada en la actualidad en el mundo Java.

Una herramienta de build es un herramienta que permite «buildear» un proyecto. «Buildear» (ejecutar un proceso de build) implica ejecutar un conjunto de tareas las cuales pueden variar dependiendo de cada proyecto. En el caso más básico el proceso de build implica tan solo compilar. En casos más avanzados el build implica ejecucar algunas otras tareas como ser: resolución de dependencias (previo a la compilación), verificación de estándares de codificación, ejecución de pruebas automatizadas, generación de paquetes binarios, etc.

En algunos casos las herramientas de build pueden utilizarse con distintos lenguajes de programación, pero en general cada lenguaje de programación tiene alguna herramienta de build «preferida». Entre las herramientas de build más populares en la actualidad se encuentran:

  • Maven, Ant y Gradle (Java y derivados)
  • MSBuild y NAnt (C#)
  • Grunt y Gulp (JavaScript)
  • Rake (Ruby)

Algunas herramientas de Build funcionan con un conjunto de tareas predefinidas (que pueden extenderse), mientras que otras dependen completamente de la definición que haga el usuario.

Todas estas herramientas de build toman como entrada un archivo que describe generalidades del proyecto a buildear (cosas como nombre del proyecto, autor, versión, etc) y la lista de tareas a ejecutar.

Maven es una de las herramientas de build que ya trae un conjunto de tareas predeterminadas. Más concretamente Maven define lo que llama un ciclo de vida, el cual incluye diversas fases y tareas asociadas a cada una. Al mismo tiempo Maven define un serie de convenciones respecto de la estructura de carpetas del proyecto. Entonces para usar Maven deberemos:

  1. Crear una estructura de directorios acorde a lo definido por Maven (en realidad la estructura exacta depende del tipo de arquetipo utilizado, pero ello es un tema avanzado)
  2. Incluir en la raíz del directorio un archivo llamado pom.xml que describa nuestro proyecto
  3. Ejecutar Maven indicando la fase deseada. Aquí debemos tener presente que cuando indicamos una fase, Maven ejecutará la fase indicada pero previamente ejecutará todas las fases anteriores. O sea, cuando uno le indica a Maven una fase X, lo que le está diciendo es: quiero ejecutar el ciclo de vida completo hasta la fase X

 

Notas del AOC 2016

Intenso. Había tanta gente con tanta energía que simplemente las charlas se extendían horas y horas. En general la actividad «oficial» arrancaba alrededor de las 9 am, pero ya desde las 7 am había gente activa: algunos haciendo meditación, otros ejercicio físico, otros simplemente tomando mate y disfrutando del paisaje. Por la noche la actividad se estiraba más allá de media noche. Personalmente nunca me acosté antes de las 2.30.

Al igual que el año pasado, el evento fue en formato open space con el agregado de algunos keynotes. Me resultó muy interesante la variedad de los keynotes. El primer keynote fue de @FerClaverino quien nos deleitó con historias y magia (literalmente hizo varios trucos/ilusiones). El segundo fue de Hiro quien usando un formato más tradicional nos habló sobre estrategias para mejora. El tercero fue el de Tulio Calderón quien nos habló sobre los desafíos para el desarrollo de alta tecnología en Argentina. Finalmente el cuarto keynote fue el de Juan Daza, quien parado a la orilla de rió con la montaña de fondo nos hizo reflexionar sobre redes, transversalidad y evolución. Todos los keynotes me parecieron excelentes: expositores claros, presentaciones bien preparadas y temáticas interesantes.

keynote_juan_daza
Keynote de Juan Daza

En este AOC Hubo más participantes que el año anterior y con perfil más diverso lo cual tuvo un impacto directo en las temáticas de las sesiones. El grueso de las sesiones estuvo alrededor de cuestiones de gestión, hubo muy pocas sesiones técnicas y una cantidad interesante de sesiones de temáticas «desconectadas» ( katas de guitarra, un taller de fotografía, bitcoins, artes marciales, etc).

También hubo diversos grupos autoorganizados que llevaron adelante diversas iniciativas «artísticas». Juan Zapico coordinó un grupo que participantes que filmaban escenas espontáneas del evento y las editaban para compartirlas en un único video de unos 10/15 minutos al final de cada día. Pablitux coordinó un conjuntos de participantes con habilidades musicales para componer una canción para del evento, una especie de «Himno del Agile Open Camp». Finalmente yo repetí la experiencia del año anterior y coordiné la escritura de un libro sobre herramientas ágiles (más detalle de esto último en un futuro post).

Otra particularidad de este año fue la gran cantidad de participantes extranjeros. Más allá de argentinos, hubo gente de Chile, Uruguay, Perú, Colombia, Ecuador, Venezuela y Costa Rica

Me cuesta decir si esta edición 2016 estuvo mejor que la 2015, creo que fue distinta, no solo por el lugar sino principalmente porque los participantes fueron distintos y es justamente la gente el factor determinante en eventos tan «inmersivos».

Para cerrar doy las gracias al grupo organizador, disfruté muchísimo y espero que se repita.

 

caminata
Inicio de actividad de trecking14

Micro-servicios: por donde empezar

Una simple búsqueda en Google del término «microservices» nos arroja unos 710.000 resultados. Entre los tres primeros están: un artículo de Martin Fowler, la correspondiente página de wikipedia y el sitio microservices.io.

Si uno simplemente quiere tener una idea de qué son los microservicios cualquiera de estos tres recursos podría ser suficiente. Pero si uno quiere ir un poco más allá de las definiciones y explicaciones introductorias, mi recomendación es el libro Building Microservices, designing fine-grained systems de Sam Newman. Por otro lado si uno viene del mundo Java, hay un librito muy interesante escrito por Markus Eisele que se llama Modern Java EE Design Patterns: Building Scalable Architecture for Sustainable Enterprise Development.

 

The BEST exam EVER!!!!

El lunes pasado tuvimos mesa de examen en Algo3 y el examen que tomamos fue el mejor que vi en mi vida entera. El examen consistió en un ejercicio práctico, cada alumno estaba con su computadora y le entregamos un conjunto de clases que resolvían un problema dado. La consigna era simple: analice el código y mejorelo. Luego de cierto tiempo (unos 30 minutos) un docente se sentaba con cada alumno para hablar con él sobre las mejoras realizadas.

Obviamente el código entregado tenía una serie de «cochinadas» algunas muy groseras y otras menores. La expectativa era que los alumnos fueran capaces:

  1. Identificar las «cochinadas»
  2. Explicar los problemas que las mismas podrían acarrear
  3. Proponer modificaciones a la solución para remover «las cochinadas»
  4. Implementar esas propuestas en código

A mi me tocó evaluar a dos alumnos y el método me pareció simplemente genial, pues con preguntas muy concretas uno podía determinar si el alumno había incorporado, o no,los conceptos centrales de la materia.

Debo felicitar a Pablo Massuh y Gabi Devoto que fueron los ideólogos de este examen.

Alta expectativa para el AOC 2016

Pasado mañana larga el AOC 2016 y personalmente creo que hay una gran expectativa. En los últimos días he hablado con varias personas que asistirán a la conferencia y he notado un entusiasmo similar al mío. Estas sensaciones están justificadas por diversas cuestiones:

El escenario es simplemente in-cre-i-ble, el evento se llevará a cabo en un complejo a en las cercanias del Lago Escondido.

La experiencia de vivir tres días completamente inmersivos con 100 personas ávidas de compartir, aprender y pasarla bien no es algo que se pueda hacer muy frecuentemente.

El contenido incluirá cuestiones tan diversas (y ¿extravagantes?) como un Workshop de Astronomía observacional, una sesión sobre Bitcoin, un Coaching Kata de Guitarra y un Taller de fotografía nocturna. Por otro lado habrá también algunas sesiones más «conservadoras» como Prototipos con Arduino, Iteration Review: la reunión olvidada y Visual Management.

Continuará…

Un ejemplo de la fragilidad de los tests de UI

Ayer me enfrenté a un claro ejemplo de la fragilidad de los tests de UI. Resulta que hace un tiempo empecé a trabajar en una aplicación sin ningún tipo de tests y cuyo diseño hacía muy difícil generar tests unitarios y de componentes/api. Fue por ello que una de las primeras cosas que hice fue generar un set de pruebas de UI para tener algo de cobertura sobre los 3 flujos más críticos de la aplicación. Luego de un deploy a testing de una nueva versión, un test de regresión comenzó a fallar. Curiosamente el nuevo deploy no incluía ningún cambio que impactara directamente en la funcionalidad que el test cubría. Para sumar confusión a la situación, cuando corría tests en mi máquina, todo andaba perfecto, el fallo sólo se producía cuando el tests era ejecutado desde el build server.

Luego de un buen rato de troubleshooting, descubrí que habíamos tenido un cambio menor en un hoja de estilo, ese cambio hacía que cuando los tests se corrían en una máquina con una determinada resolución, dos elementos quedarán superpuestos haciendo que uno de ellos no fuera clickeable.

ui-fragil

Micro-servicios: ¿un nuevo buzzword?

Puede que si o puede que no. Algunas impresiones:

  • Hay gente que viene construyendo micro-servicios desde bastante tiempo antes de que el término se ponga de moda
  • Como suele ocurrir con toda moda en algún momento, hay gente que se está subiendo a esta iniciativa a pesar de que su contexto no lo requiere

Pero… ¿que es realmente esto de los micro-servicios? Es básicamente un nombre para identificar un conjunto de prácticas a nivel de diseño e implementación con foco en dos cuestiones:

  • Posibilidad de responder rápidamente a las necesidades del negocio
  • Soluciones robustas y escalables

El primer punto está íntimamente relacionado con la agilidad y la entrega continua y tiene un impacto directo en la forma de trabajo de los equipos/organizaciones a punto tal que resulta común que las organizaciones modifiquen sus estructuras de equipos al moverse hacía el mundo de los micro-servicios.

El segundo punto tiene una impronta mucho más técnica y a nivel implementación suele implicar cuestiones como: cloud computing, virtualización y dockerización.

¿Y a que se debe el nombre «micro-servicios»?

Hace unos 10 años hubo una fuerte «ola de servicios» en el contexto de SOA (Service Oriented Architecture). La implementación de iniciativas SOA implicó en la mayoría de los casos el uso de componentes de arquitectura «pesados» cuya administración/instalación solía tener un grado importante de complejidad. Al mismo tiempo muchas de las soluciones generadas en los contextos SOA no se caracterizaban por ser muy testeables (automatizamente).

Los micro-servicios deben su nombre en gran medida  a la intención de diferenciarse de lo que fueron las iniciativas SOA. Pues si bien hay puntos en común entre ambas iniciativas también hay importantes diferencias. Entre esas diferencias yo me inclino por los siguientes objetivos en el diseño de los micro-servicios:

  • Testeabilidad
  • Despliegue independiente y automatizado
  • Resiliencia
  • Escalabilidad horizontal

Continuará…

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á…

Prácticas DevOps: unificación de ambientes

Una de las primeras recomendaciones que suelo hacer a quienes me contratan para ayudarlos con cuestiones de Continuous Delivery/Automazación de ambientes es la unificación/normalización de infraestructura. Lamentablemente suelo encontrarme con proyectos donde cada servidor/ambiente es una historia distinta: el servidor de producción corre una versión de sistema operativo distinta a la del servidor del producción, tomcat y apache instalados de distinta forma en distintas ubicaciones en cada uno de los ambientes. Situaciones de este tipo tienen dos problemas:

1. Existe cierto riesgo de que algo probado en testing luego no se comporte de la misma forma en producción debido a que los ambientes son distintos (y no me refiero a cuestiones de escala)

2. No es posible utilizar los mismo scripts de deployment/administración/monitoreo ya que muchas veces la ubicación de las aplicaciones/utilitarios es distinta en cada ambiente.

Ante situaciones así mi propuesta suele ser unificar ambientes/servidores siguiendo la heurística:

  1. Elegir un sistema operativo (y versión particular) en los posible que cuente con soporte de largo plazo. Ejemplo Ubuntu 14.04
  2. Automatizar el provisioning de base que tendran todos los servidores: configuración de usuarios, networking, etc. (es lo que seria automatización capa 1)
  3. Para cada componente (web server, base de datos, etc, etc) determinar la forma de instalarlo y generar los scripts correspondientes. (es lo que seria automatización capa 2)

De esta forma, cuando uno pretende avanzar sobre la automatización de despliegues, todo resulta mucho más simple ya que está claro el sistema operativo de base y la forma en que fue instalado cada componente. Al mismo tiempo, si todos los servers/ambientes se generan con los scripts de los pasos (2 y 3) entonces es muy factible poder reutilizar scripts de deploy entre distintos proyectos bajando de manera importante el esfuerzo de automatización.