Continuous Delivery como una cinta transportadora

Hace un par de semanas en un reunión de trabajo mi colega Mariano explicó la práctica de Continuous Delivery como una analogía con una cinta transportadora y me pareció simplemente excelente.

Siendo estrictos con las definiciones Continuous Delivery implica Continuous Integration y Trunk-Based Development. Entonces:

  • El equipo hace commits pequeños, si hace TDD, posiblemente un commit por cada nueva prueba en verde.
  • Un funcionalidad/story requiere de varios commits.
  • Si el equipo trabaja simultáneamente en más de una funcionalidad es común que una funcionalidad se complete mientras otra está aún en desarrollo.
  • Si efectivamente hacen continuous delivery es posible que a penas se complete una funcionalidad se la quiera poner producción. Esto a su vez implicaría llevar a producción también las funcionalidades aún no completas.

De esta forma el proceso de continuous delivery es una cinta transportadora donde cada commit es como un paquete que uno pone en la cinta y la cinta lo lleva a producción.

Esto tiene un impacto muy grande en la forma del trabajo del equipo que obliga a tomar precauciones adicionales como puede ser el uso de feature toggles, de manera que si una funcionalidad no completa llega a producción, la misma pueda ser “toggleada/apagada”.

Medición de cobertura en .Net Core con Coverlet

Luego de cumplir con los primeros hitos de negocio y teniendo un equipo que empieza a estabilizarse me puse a hacer algunas pruebas para medir la cobertura de nuestro proyecto.

En primera instancia atiné a utilizar OpenCover, una herramienta que había utilizado en proyectos anteriores, pero me encontré que solo corre en Windows. Nuestra infraestructura de build corre en Linux y yo particularmente trabajo en MacOS. Con lo cual OpenCover quedó descartado.

Luego de Googlear un poco dí con Coverlet que según la documentación es multiplataforma. Investigando un poco más encontré este artículo de Scott Hanselman y con eso me bastó para hacer una prueba. A continuación voy a compartir algunos descubrimiento que hice aprendiendo a utilizar esta herramienta.

En primer lugar tenemos que saber que hay tres formas de utilizar esta Coverlet:

  1. Como una extensión de dotnet-cli
  2. Como un collector integrado al motor de ejecución de VSTest
  3. Como una tarea de MSBuild

Yo decidí ir por esta última estrategia. Para ello el primer paso es agregar el paquete coverlet.msbuild a cada uno de los proyectos de tests. Una vez agregado este paquete simplemente tenemos que agregar el parámetro de cobertura al momento de la ejecución de los tests

dotnet add package coverlet.msbuild
dotnet test /p:CollectCoverage=true

Si tenemos un solo proyecto de tests que cubre todos los proyectos/assemblies de nuestra solución, con esto ya habrá sido suficiente. Al ejecutar los comandos anteriores obtendremos una salida como la que se muestra en la siguiente figura.

Pero si nuestra solución, como es muy habitual, tiene varios proyectos de tests nos vamos a encontrar que la medición que hace Coverlet es “parcial”/”desagregada”. Ocurre que cada proyecto de tests es ejecutado ejecutado independientemente haciendo que la medición de cobertura también sea independiente lo cual a su vez hace que tengamos varios reportes de cobertura. Esto se puede observar en la siguiente figura.

El primer reporte corresponde al proyecto de tests de Domain y nos indica que precisamente Domain tiene una cobertura de ~79%.
El segundo reporte corresponde al proyecto de tests de ApiServices y como ApiServices depende de Domain, la medición de cobertura considera ambos proyectos. Pero dado que los tests de ApiServices apenas tocan el código de Domain, la cobertura informada sobre Domain es mínima (~6%). Entonces lo que deberíamos hacer para obtener el valor correcto de cobertura es mezclar el reporte de cobertura generado por el proyecto de tests de Domain y el proyecto de tests de ApiServices. Aquí también coverlet nos da varias opciones. En mi caso, lo que hice fue ejecutar explícitamente cada proyecto de test por separado, escribiendo los resultados en un archivo y en la misma ejecución indicándole a Coverlet que realice el merge con el archivo de cobertura de la ejecución anterior.

dotnet test Obi.Domain.Tests/Obi.Domain.Tests.csproj /p:CollectCoverage=true /p:CoverletOutput=../coverage.json

dotnet test Obi.ApiServices.Tests/Obi.ApiServices.Tests.csproj /p:CollectCoverage=true /p:CoverletOutput=../coverage.json /p:MergeWith=../coverage.json

De esta forma el reporte se genera correctamente.

Una situación habitual cuando medimos la cobertura es no incluir en el cálculo algunas archivos. En nuestro caso ocurre que consumimos servicios SOAP, y utilizamos una herramienta que nos genera un conjunto de clases proxy para interactuar con SOAP. Dichas clases son almacenadas en un archivo Reference.cs que queremos excluir del análisis de cobertura. Para esto debemos incluir un parámetro adicional para Coverlet en la ejecución de los test /p:ExcludeByFile=”**/Reference.cs”.

Bien, con todo lo descripto hasta el momento hemos logrado medir el % de cobertura de nuestro código. Lo siguiente que suele hacerse es ver cuales son las partes de código sin cobertura. Si bien coverlet tiene la capacidad de detectar esto, no provee un mecanismo cómodo para visualizarlo y por ello debemos utilizar otra herramienta complementaria. Pero eso será parte de otro post.

Sobre la secuencia de tests al hacer TDD

Este es uno de los temas clave al hacer TDD. La elección de la secuencia de test puede hacer que el ciclo de TDD resulte muy fluido o que sea una pesadilla. Más aún, tengo la sospecha que la elección inapropiada de la secuencia de tests debe ser una de las principales razones de abandono de TDD. Personalmente suelo aplicar la heurística conocida como “TDD Guided by Zombies” propuesta por James Grenning, pero incluso usando esa heurística hay situaciones en la que mi secuencia de tests no me resulta lo suficientemente fluida. Quiero compartir un caso concreto para repasar distintas heurísticas.

Ayer por la tarde estábamos con otros 3 colegas en un sesión de mobbing trabajando sobre una funcionalidad que consistía en filtrar una lista de objetos Cuenta en base a 4 condiciones. En caso de no encontrar ningún objeto que cumpla con las 4 condiciones en forma simultánea debíamos generar un error pues eso representaba una situación excepcional de negocio.

Un colega propuso comenzar por los casos de excepción y luego los casos positivos. Lo que me gusta de esta heurística es que inicialmente permite avanzar rápido, el código del método generado comienza con una secuencia de validaciones que van respondiendo a los distintos casos de excepción. Lo que no gusta de esta secuencia es que durante varios ciclos el código no resuelve el problema para nadie, o sea, el método en cuestión solo valida y lanza excepciones pero no genera una lista filtrada. Hay que esperar casi hasta el final de la secuencia para obtener una lista filtrada. Una posible secuencia de tests con esta estrategia sería:

DeberiaLanzarExcepcionSiNoTieneCuentasEnPesos
DeberiaLanzarExcepcionSiNoTieneCuentasConTarjetaAsociada
DeberiaLanzarExcepcionSiNoTieneCuentasTitular
DeberiaLanzarExcepcionSiNoTieneCuentasActivas
DeberiaFiltrarLasCuentasEnPesosConTarjetaTitularidadActivas
...(otros casos positivos)...

Personalmente yo prefiero comenzar con casos positivos y dejar las situaciones de excepción para el final. En este sentido un camino posible sería arrancar literalmente al revés del ejemplo anterior, comenzando con el caso positivo que contempla todas las condiciones. El problema de esta estrategia es que genera un paso muy grande, o sea, implica escribir en un solo paso el algoritmo completo que considera las 4 condiciones.

Otra opción en línea con la idea de transitar el camino de casos positivos y siguiendo la heurística de Zombies, es arrancar con una condición positiva a la vez. De esta forma, ya de entrada estamos generando una lista filtrada, que al menos para algunos casos será una lista completamente válida. A partir de eso podemos ir generar nuevos casos positivos refinando el filtro y agregando los casos de excepción luego de tener todo el filtro completo. De esta forma cada paso del ciclo se mantiene pequeño y el algoritmo de filtrado se construye incrementalmente. La secuencia resultante podría ser:

DeberiaFiltrarSoloCuentasEnPesos
DeberiaFiltrarSoloCuentasConTarjeAsociada
DeberiaFiltrarSoloCuentasTitular
DeberiaFiltrarSoloCuentasActivas
.... (otros casos incluyendo los de excepcion)....

Obviamente que también está la posibilidad de tomar una secuencia que alterne casos positivos y negativos. De hecho si uno siguiera a raja tabla la heurística Zombie el primer caso (caso Zero) sería negativo y los siguientes positivos (caso One y casos Many).

Bueno, esto esto por ahora pero creo que este tema de la elección de la secuencia de tests aún da para mucha charla.

The Pragmatic Programmer

La primera edición de este libro fue publicada en 1999 pero yo no lo conocí hasta ~2005. Y no lo leí hasta 2009 cuando lo encontré la biblioteca de la empresa en la que trabajaba en aquella época. El año pasado se publicó la edición 20 aniversario y no dudé en comprarlo. Ayer terminé de leerlo.

Mas allá de la elegancia de la edición tapa dura, a nivel contenido el libro no tiene desperdicio y creo que es una lectura obligada para todo developer. Cubre temas muy variados que van desde principios de diseño, estimación, hasta ética profesional pasando por programación concurrente, técnicas de testing y recomendaciones de organizacional personal.

Complementariamente a la usual división en capítulos, el libro está organizado en “temas” (topics) que son subdivisiones de los capítulos y la extensión de cada uno es tal que su lectura puede realizarse en 30 minutos (lo cual a mi personalmente me facilita mucho la lectura). Asimismo al final de cada tema se listan los temas relacionados y se ofrece una serie de ejercicios, algunos de reflexión, otros de programación, etc.

Los autores son Dave Thomas y Andrew Hunt, ambos autores del Agile Manifesto. Tal vez por eso muchas de las cuestiones tratadas en el libro están abordadas desde una perspectiva ágil.

Esta edición 20 aniversario es bastante distinta a la edición original de 1999. La esencia de libro es la misma pero el contenido ha sido totalmente actualizado. De hecho los propios autores reconocen que hay partes del libro que fueron completamente reescritas.

Notes from my session @XPConf 2020

Last Friday I delivered my session “BDD your solution from git init to Kubernetes” in the context of the XP Conf 2020. This was my forth participation in the XP Conference, but as you can image this time I didn’t travel anywhere, the conference turned to an online format.

In my experience most of the BDD/TDD sessions/trainings are based on “toy examples” focused just on coding and leaving apart the whole CI/CD cycle. One of the goals of my session was to share with the participants a hands-on, real-world experience applying BDD/TDD in the context of the CI/CD pipeline. For this I setup a Kubernetes cluster on Digital Ocean and we used the Gitlab platform to implement the pipeline.

I am very satisfied with the flow and the result of the session. The evaluation of the participants was (average) 4.6 / 5.

The code is available on GitLab and slides are available here.

Algunas reflexiones luego de cinco meses con .Net Core

En enero comencé a trabajar un en proyecto para el sector financiero utilizando .Net Core y me parece que este es un buen momento para compartir algunas sensaciones y hallazgos.

Antes que nada debo decir que hasta 2009 mi carrera profesional estuvo muy vinculada a tecnología Microsoft en general y a .Net en particular. En 2009 di un vuelco en mi carrera y empecé a involucrarme más de cerca con otras tecnologías. Fue así que hice varios proyectos con Java y Ruby y algunos otros más escasos con NodeJS, Python y C++. Este transitar por variadas tecnologías me resultó muy enriquecedor y revelador. Entre otras cosas descubrí que es posible desarrollar software sin estar completamente atado a un IDE particular y a la infraestructura. Tradicionalmente en .Net uno “vivía” atado a Visual Studio y al Internet Information Server.

Ahora sí, hablemos de .Net Core. Quiero referirme en particular a tres puntos: Experiencia del desarrollador, Stack de herramientas e Infraestructura.

Experiencia del desarrollador

El primer punto a destacar aquí es el desarrollo multiplataforma, .Net core está disponible para Windows, Linux y MacOS (justamente en mi equipo tenemos gente trabajando en las 3 plataformas). [1]

El otro punto destacable es la posibilidad trabajar con .Net Core desde la línea de comandos a partir de un CLI que ofrece funcionalidades de scaffolding, ejecución de tests y posibilidades de extensión. Hay que decir que si bien es posible hacer muchas operaciones desde la línea de comando, no todas están lo suficiente documentadas y como dicen algunos miembros de mi equipo: “hay que aprender los conjuros”.

Estos dos puntos hacen que la experiencia de desarrollar con .Net Core sea mucho más parecida a la experiencia del desarrollar con NodeJS, Ruby o Python.

Finalmente hay que destacar las nuevas interfaces de Asp.Net Core que posibilitan el desarrollo de aplicaciones más testeables y el uso de TDD de punta a punta.

Stack de herramientas

Si bien siempre me gusto C# como lenguaje el stack de herramientas me parecía muy limitado, sobre todo luego de trabajar en Ruby. Tradicionalmente el desarrollo a nivel profesional con .Net obligaba al uso de Visual Studio. Hoy en día con .Net Core existen varias alternativas al tradicional Visual Studio, muchas de ellas desarrolladas/soportadas por la iniciativa Omnisharp y entre las que se destacan varios “IDEs livianos” como VSCode, Sublime y Atom. El propio Microsoft viene dando mucho impulso a VSCode el cual tiene una particularidad que lo destaca por encima del resto de los IDEs de su categoría: la capacidad de debugging. Dado que .Net Core aún no ofrece un debugger de línea de comandos el hecho de que el IDE ofrezca un debugger es un punto relevante.

Personalmente intenté trabajar con VSCode y no me resultó cómodo, pero debo destacar que varios miembros de mi equipo (los que viven en Linux) lo vienen utilizando y están conformes.

La herramienta que yo vengo utilizando es Rider, el IDE multiplataforma para desarrollo .Net que perteneciente a la familia de IDEs de JetBrains. Este hecho es una de las cosas que me inclinó a usar Rider ya que en los últimos años yo venía utilizando IntelliJ (Java) y RubyMine (Ruby). Las tres herramientas son muy parecidas y a mi parecer excelentes.

Infraestructura

El hecho de ser multiplataforma despegó completamente a .Net Core (o más precisamente a Asp.net Core) del Internet Information Server. Aquí es donde aparece Kestrel, el web server multiplaforma de Asp.Net Core. Una particularidad de Kestrel es que puede ser instanciado vía código dentro de cualquier aplicación .Net Core. Esto mejora mucho la experiencia desarrollo, testeabilidad de las aplicaciones web y el empaquetamiento/distribución de aplicaciones Asp.Net Core. En relación a este último punto hay mucha información y recursos respecto a distribuir aplicación Asp.Net Core con Docker, de hecho hay imágenes Docker de Net Core y Asp.Net oficiales provistas por Microsoft.

En nuestro proyecto estamos usando Docker y nuestra aplicación corre Dockerizada dentro de Kubernetes.

[1] Hay que mencionar que previo a .Net Core hubo varios iniciativas (Mono, Xamarin, etc) para proveer algunas de las caraterísticas que menciono en este artículo, pero a mi parecer dichas iniciativas no lograron no llegaron al mainstream, aunque sin duda fueron motivadores del nacimiento de .Net Core.

Experimento: Taller de Desarrollo “a la gorra”

Hace tiempo que tengo ganas de hacer un experimento con el valor de mis talleres, quiero que el dinero que pagan los asistentes tenga relación con el valor que perciben. Finalmente he decidido hacerlo.

El próximo sábado 6 de Junio de 14 a 17 hs. estaré dando en modalidad online un nuevo taller llamado: “BDD your solution from git init to Kubernetes” (los detalles de contenido están más abajo).

El valor a abonar por los participantes se determina a partir de:

  1. El costo de la infraestructura que utilizaremos en durante el taller (cluster Kubernetes + Zoom Pro)
  2. El costo del esfuerzo de preparación y dictado
  3. La cantidad de participantes
  4. El valor que perciba cada participante

La suma de 1 y 2 da un monto que se dividirá entre la cantidad de participantes, generando un pago sugerido para cada participante. Asimismo cada participante deberá abonar a priori un porcentaje proporcional a (1) para asegurar su vacante. Luego de finalizado el taller cada participante pagará la diferencia entre el pago a priori y el pago sugerido, pudiendo incluso decidir pagar una cifra diferente (mayor o menor) de acuerdo al valor que considere que el taller le aportó.

Los interesados en participar pueden completar este formulario y les enviaré información más detallada.

Behaviour-Driven Development is a key agile technique to ensure the developed software behaves according to user expectations. In this workshop we will start by reviewing BDD concepts and we will see how to develop an application from scratch by applying BDD and other agile techniques like Hexagonal Architecture and Walking Skeleton. We will explore in detail the BDD/TDD/CI cycle and we will see how to integrate it in a Continuous Delivery Pipeline to take our code from the source control system to the cloud. We will use BDD to drive the coding of the application and also the coding of the infrastructure. We will be using tools like Cucumber, GitLab-CI, Docker and Kubernetes among others. This is a hands-on workshop, participants will work on their machines and also using a cloud environment.

Definición de la estrategia de pruebas

Existen muchas clasificaciones de tests, casi tantas como autores. De caja blanca, de caja negra, unitarias, de integración, de aceptación, funcionales, de sistema, de carga, etc, etc.

Hay para todos los gustos y colores. Más aún, algunos tipos de tests tienen un significado distintos para distintas personas. Un caso usual de esto son los test end-2-end que típicamente:

  • para los desarrolladores implican tests de una funcionalidad que atraviesan todas las capas de la aplicación, desde la pantalla hasta la base de datos
  • para los testers implican tests que cubren un flujo de negocio que integra varias funcionalidades

Esta situación obliga a que todo equipo deba en primer lugar ponerse de acuerdo en la terminología a utilizar. A partir de esto hay que definir la estrategia de pruebas.

Definir la estrategia de pruebas implica definir:

> Tipos de tests que se realizarán
> Cuantos de esos tests se realizarán
> Quien los realizará
> Cuando los realizará
> Qué herramienta se utilizará
> Y cual será la arquitectura de prueba que dará soporte a todos los tests

La siguiente figura resumen parcialmente la estrategia de pruebas de mi proyecto actual.

Los que indica esta imagen se complementa con las siguientes decisiones:

  • El desarrollo se hace una forma test first, donde los tests guían el desarrollo. Todo empieza “tironeado” por una pruebas de aceptación (story tests) especificada en Gherkin a partir de una charla entre: usuario + tester + developer.
  • Los tests de aceptación no se automatizan al comenzar el desarrollo sino a posterior y al mismo tiempo no todos los escenarios son automatizados.
  • El resto de los tests se escriben a priori del código y de a uno por vez. Una vez implementados se ejecutan constantemente en el build server.
  • Salvo los tests de aceptación, todo el resto de los tests de la figura son escritos por los developers.
  • Para los tests de aceptación usamos Gherkin+Specflow.
  • Para los tests de frontend utilizamos Jasmine+Karma.
  • Para los tests de backend utilizamos NUnit.
  • Para mockear utilizamos Moq y Wiremock.

Adicionalmente a los tests de la figura hacemos pruebas de performance (jmeter) cuando tenemos cambios relevantes en la arquitectura y pruebas exploratorias de ejecución manual antes de cada salida a producción. Tenemos pendiente agregar algunas pruebas de seguridad.

Finalmente respecto de cuántos tests de cada tipo hacer idealmente apuntamos a tener un triángulo con pocos tests de aceptación end-2-end para los flujos centrales en la punta y muchos tests unitarios en la base para lograr cobertura de flujos alternativos. En el medio de la pirámide quedan los tests de componentes/integración.

Materiales de la sesión TDD al banquillo

El viernes pasado estrené esta charla en el meetup online de Software Crafters Chile. La sesión duró poco más de 1 hora y yo quedé muy conforme. Creo que la exposición estuvo bien y la gente se mostró muy participativa. A partir de algunas preguntas/comentarios de los participantes se me ocurrieron algunos updates a la charla para una futura presentación.

Los slides que utilicé están disponibles aquí, allí mismo están las referencias a los papers formales sobre TDD. Como mencioné en la sesión, el paper que me resulta más relevante es el de Fucci, Erdogmus y colegas: “A Dissection of the Test-Driven Development Process: Does It Really Matter to Test-First or to Test-Last?

Para quienes quieran estudiar TDD mis recomendaciones son:

Agradezco a @JosephCastroR y demás organizadores del meetup por la invitación.

Previsibilidad mata velocidad

Completamos la séptima iteración entregando menos de lo que habíamos planificado y eso generó cierto debate en la planning de la octava iteración, llevando incluso a que algunos miembros del equipo quieran replantear la forma de estimación.

Hasta el momento hacíamos lo siguiente:

  • Repasar el backlog candidato determinado por el Product Owner, asegurando que entendíamos cada uno de los ítems
  • Estimar “a ojo” los item que consideramos podríamos llegar a completar en la iteración por comenzar
  • Asignar puntos (1, 2 o 3) a cada uno de los items, principalmente para estar seguros que todos los miembros del equipo teníamos una idea similar de las implicancias/alcance de cada ítem. Importante: estos puntos no los utilizamos para determinar la cantidad de trabajo de la iteración, sino simplemente como un mecanismo de doble validación interna.

Con este mecanismo en la iteración 7 planificamos unos 23 items pero completamos unos 18.

Antes de continuar debo decir que:

  • En la gran mayoría de equipos que he estado involucrado “con problemas de estimación”, resultó que en realidad era mucho más grave el problema de ejecución que el de estimación
  • Para equipos comprometidos, auto-organizados y trabajando en iteraciones cortas y con un esquema de continuous delivery no creo que valga la pena estimar “formalmente” con puntos/horas ni nada por el estilo más allá de la sensación del equipo

Dicho todo esto, mientras parte del equipo debatía sobre los ajustes al método de estimación/planificación, me puse a repasar el histórico de iteraciones en el Jira y confirmé mi sospecha viendo unos de los reportes provistos por la herramienta:

El nombre que la herramienta da a este gráfico es “Reporte de Velocidad” el mismo muestra en gris el plan del equipo al inicio de cada iteración y en verde lo efectivamente completado al final de la iteración. Se supone que a partir de este gráfico uno podría deducir una velocidad/capacidad del equipo que puede resultar útil a la hora de planificar.

Analizando este caso particular uno podría decir que este equipo puede entregar alrededor de 18 puntos por iteración. Sin embargo, ese no es el dato más importante de este gráfico para mí.

Lo que me resulta más relevante de este gráfico es la predecibilidad. Puede verse que independiente del número X planificado, este equipo entrega consistentemente con un desvió promedio de ~18 %, siendo este desvío en ocasiones positivo (se entrega más de lo planificado) y en ocasiones negativo (se entrega menos de lo planificado). Más aún, si eliminamos del cálculo la iteración 4 que fue particularmente corta en términos de días laborales (por feriados) y que tuvo una variación de equipo (se sumo una persona), el desvío promedio cae por debajo del 15%. Personalmente creo que es un grado de predecibilidad muy bueno, sobre todo si consideramos que el equipo tiene la capacidad (tanto a nivel proceso como de infraestructura) de entrega software potencialmente todos los días.

En general prefiero no hablar de la velocidad de un equipo pues creo que puede generar falsas expectativas por la sola terminología. Alguien fácilmente podría tentarse de pedirle al equipo que aumente su velocidad generando así presiones para el equipo. Y ni hablar de quienes pretenden comparar la velocidad de distintos equipos. Es por esto que prefiero no hablar de velocidad como una característica del equipo. Prefiero caracterizar al equipo por cuan predecible es.