Build de 10 minutos y categorización de tests

Cuando el equipo actual tomó a su cargo el sistema este tenía una alto grado de inestabilidad y prácticamente no tenía tests automatizados. Gradualmente se fueron agregando tests que ayudaron a mejorar a la estabilidad del sistema. En un punto se llegó a tener unos ~2600 tests de componentes/unitarios cuya ejecución excedía los 30 minutos. Todos estos tests eran ejecutados como parte del build de integración continua. A esto se sumaba el hecho de que equipo hacía feature-branches, los cuales también eran buildeados automáticamete, aumentando la carga del build server. Llegado un punto, cada vez que un developer hacía un commit (+push) tenía que esperar, en el mejor de los casos, unos 40 minutos para obtener feedback. Podía llevar a pasar que el build server estuviera ocupado y entonces el build quedaba encolado estirando aún más el tiempo de espera.

Ante esta situación hicimos dos cosas. En primer lugar instalamos un segundo agente del Build Server, para bajar el tiempo tiempo de espera en cola. En segundo lugar categorizamos los tests y ajustamos la configuración del build server. En este segundo punto quiero profundizar.

Es un práctica muy difundida y aceptaba, el tener un build de integración/feedback continua que no exceda los 10 minutos (algunas referencias para profundizar aquí y aquí). En línea con esto decidimos categorizar los ~2600+ tests, identificando aquellos que cubrían las partes críticas de la iteración. Identificamos unos ~600 tests fundamentales cuyo tiempo de ejecución resultó en unos 7 minutos. Categorizamos esos tests como “core”. Al mismo tiempo ajustamos el build server para que se ejecuten en forma continua (ante cada commit+push) los tests “core” y de los “current” (pertenecientes a la iteración actual), de manera de poder tener un feedback “rápido”. Por otro lado, creamos otra tarea que se ejecuta al fin del día para correr el set completo de tests.

 

Anuncios

.Net: infraestructura de desarrollo & test

Quiero compartir la infraestructura que estamos usando en el proyecto que estoy trabajando en este momento.

Como repositorio de código estamos usando Git, más específicamente GitLab (aunque a pedido del cliente en breve migraremos a BitBucket).  Dado que estamos usando una arquitectura “micro-servicios-like” tenemos un repositorio por cada micro-servicio, más un repositorio general para configuración, más un conjunto de repositorios adicionales para algunas librerías de uso común y finalmente un repositorio para los scripts de operaciones.

Estamos usando Nexus como repositorio de binarios NuGet y Maven, ya que tenemos componentes .Net y Java.

El servidor de integración continua y orquestador de deployments es Jenkins 2. El proceso de Build lo tenemos armado con MSBuild de manera que Jenkins simplemente obtiene el código de Git, dispara MSBuild y genera reportes y notificaciones basados en el resultado del proceso.

Los deployments los hacemos con MSDeploy y los disparamos desde Jenkins.

De esta forma, los componentes comunes compartidos entre distintos micro-servicios están publicados en nuestro NuGet/Nexus y quien necesita utilizarlos simplemente agrega una referencia a nivel binario usando NuGet. Esto nos permite evitar almacenar binarios en Git y al mismo tiempo que nos habilita a que cada parte de la arquitectura pueda ser versionada en forma independiente.

Para la gestión del backlog utilizamos Jira que a su vez lo tenemos integrado con Hiptest para el manejo de los casos de prueba. Personalmente nunca antes había trabajado con Hiptest y algo que me gusta mucho es que se integra con SpecFlow para la definición de los casos de prueba y el registro centralizado del resultado de la ejecución de las pruebas reportado por SpecFlow.

Para comunicarnos tenemos una lista de correo, un Slack y Skype/Hangout para las video conferencias.

pipeline_net

Delivery Pipeline con Jenkins 2

Si bien vengo trabajando con Jenkins2 desde que se publicó el release candidate, no había echado mano a la nueva funcionalidad nativa de pipelines. Sinceramente la miré por arriba apenas instalé Jenkins2, no me convenció y decidí seguir con la combinación de plugins que venia usando con Jenkins 1.6.

El lunes pasado me actualicé a Jenkins 2.8 y decidí investigar más en profundidad la funcionalidad de pipelines nativos.  Finalmente esta mañana logré completar una primera versión de mi pipeline utilizando el nuevo soporte nativo. Al hacerlo descubrí un montón de cosas interesantes que pueden llegar a disminuir considerablemente el costo de control de configuración de Jenkins (principalmente las cuestiones versionado, actualización y backups de jobs)

jenkins2-pipeline

 

El placer de trabajar con quien uno gusta

Varios son los beneficios de trabajar de manera independiente, pero dudo que alguno sea más gratificante que el hecho de poder elegir con quien trabajar.

En este sentido ayer tuve  el gusto de compartir una mañana de trabajo con Pablito Tortorella (@pablitux). Pablo y yo estamos trabajando en un cliente, Pablo enfocado en cuestiones de adopción de Scrum y mejora organizacional y yo en cuestiones de integración continua y pruebas automatizadas. Si bien son temas distintos, tienen puntos de contacto y complementación. Ayer en particular estuvimos trabajando armando la infraestructura de prueba con un equipo que está físicamente distribuído.

Fue una mañana intensa, trabajamos todos juntos sentados frente a dos pantallas: una con el Hangout (teníamos miembros del equipo conectados en forma remota) y otra con el código del proyecto.

Logramos el objetivo que nos habíamos propuesto (tener una prueba corriendo sobre la nueva arquitectura), aprendimos, la pasamos bien y acordamos repetirlo la semana próxima.

¡Gracias Pablitux!

Integración continua, principio #3: el principio del tio Ben

Cuando el tío Ben está apunto de morir en los brazos de su sobrino Peter Parker (spiderman) le dice una frase que lo marcará para siempre:

Grandes poderes conllevan grandes responsabilidades

Amo esta frase, creo que aplica a todos los ámbitos de la vida y también al desarrollo de software. En este sentido Tobias Meyer en su libro People Scrum la utiliza para describir la situación de un equipo autoorganizado: el equipo tiene el poder de autoorganizarse y eso automáticamente aumenta su responsabilidad. Analicemos esta situación por el contrario: si a un equipo se le imponen compromisos ¿cuán responsable será ese equipo por esos compromisos impuestos?. Si en lugar de eso fuera el propio equipo el que asumiera los compromisos ¿no se sentiría ese equipo automáticamente más responsable por el cumplimiento de esos compromisos que el propio equipo eligió? Mi experiencia me ha demostrado que efectivamente es de esta forma, lo cual confirma la frase del tío Ben.

Llevando esto al contexto de integración continua creo que la frase puede incluso aplicarse a la inversa, grandes responsabilidades requieren grandes poderes. O sea, si se pretende que un equipo asuma la responsabilidad de mantener un build sano, debe entonces darse a ese equipo las herramientas (poderes) para poder tomar esa responsabilidad. En concreto ello implica, entre otras cosas, que el equipo debe tener acceso total al ambiente de integración continua. Sin excepción puedo decir que todas las implementaciones fallidas que he visto de esta práctica tenían en común que el equipo no tenía acceso completo al ambiente de integración continua.

Integración continua, principio #2: build everywhere

Particularmente la tarea de Build debe poder ejecutarse tanto en el servidor de integración continua como en la máquina del programador. Esto es fundamental para que en caso de falla del build, los programadores sean capaces de reproducir la falla en sus ambientes locales ejecutando la misma tarea que ejecutó el servidor.

Al mismo tiempo esto reduce la dependencia con el servidor de integración continua, ya que el mismo solo agrega un paso previo y uno posterior al build. En concreto:

  • El servidor de IC monitorea al repositorio de código y dispara nuevas ejecuciones en caso de detectar cambios
  • Una vez disparado el build y descargado el código fuente, el servidor de IC ejecutará la tarea de build que es justamente la que debe poder ejecutarse en la máquina de los programadores
  • Finalmente el paso adicional que ejecuta el servidor de IC es la notificación del resultado del build

Integración continua, principio #1: Independencia de IDE

Desde el punto de vista conceptual esta práctica está muy bien descripta en el artículo de Martin Fowler (#lecturaObligatoria), pero a la hora de intentar implementar la práctica, no suele alcanzar con la teoría. En este sentido un libro que me parece muy útil es Jenkins: The definitive guide de John Ferguson.

Luego de la lectura de los mencionados recursos y de haber recorrido un largo camino con esta práctica he identificado algunos principios fundamentales para la efectiva implementación de esta práctica. A partir de hoy y durante los próximos días voy a compartir una serie de post explicando cada uno de dichos principios. Si bien los principios los voy a ir numerando, su numeración no tiene correlación alguna con su importancia. Aquí va el primero.

Independencia del IDE

La generación del build no puede depender de un IDE. Es sorprendente la cantidad de programadores que no son capaces siquiera de compilar su aplicación sin usar un IDE. Esta es una situación que veo en muchos ambiente Java (alta dependencia de Eclipse) y .Net (alta dependencia de Visual Studio). En la actualidad todos los lenguaje de programación cuentan con alguna herramienta de build e incluso si no existe una herramienta específica para algún lenguaje, siempre es posible utilizar alguna herramienta genérica como el noble make.