Mostrando entradas con la etiqueta DevOps. Mostrar todas las entradas
Mostrando entradas con la etiqueta DevOps. Mostrar todas las entradas

lunes, 7 de febrero de 2022

Feature flags o deployment rings. ¿Por qué no ambos?

DevOps nos permite entregar valor al negocio de un modo sostenido, obteniendo feedback de producción de un modo temprano, de modo que nuestras decisiones sean tomadas en base a datos objetivos.

 

Entregar features rápido no implica que todas estas features resulten siempre satisfactorias para nuestros usuarios o el negocio. Algunas pueden fallar (tener bugs) o pese a funcionar correctamente, no resultar útiles o cómodas a nuestros usuarios, generando rechazo.

 

En esos casos, una exposición progresiva (Progressive exposure) de estas nuevas features puede reducir el rechazo, o al menos que este se produzca en un número reducido de usuarios, en vez de en toda nuestra base de clientes.

 

El conjunto de usuarios a los que llega una nueva feature se conoce como Radius blast, por analogía con el radio de expansión de una bomba. Si decimos que incrementamos el Radius blast, estaremos haciendo que una feature llegue a más usuarios.



Feature flags y Deployment rings


Dos de las técnicas más extendidas que podemos usar para exponer nuestras features progresivamete a nuestros usuarios son el uso de feature flags y la estrategia de despligue en anillos (deployment rings).

 

Ambas cumplen el propósito, pero como no podía ser de otro modo, presentan pros y contras, que debemos conocer antes de "lanzarnos" a seleccionar una.


Deployment rings


Fueron descritos por primera vez por Jez Humble en el famosísimo libro "Continuous Delivery", donde se hablaba también de canary deployments, que podemos considerar un caso especial de despliegue en anillos.

 

Portada del libro Continuous Delivery

 

 

Se trata de disponer de varios entornos de producción, siendo cada uno de tus usuarios siempre enrutado al mismo entorno, por criterios variados (geográficos, por tipo de usuario, por antigüedad u otros).

 

Cuando se despliega una nueva feature, inicialmente solo estará disponible en el primero de los entornos (anillo), donde será visible solo para un porcentaje de usuarios (los redirigidos allí).

 

A partir de ese momento comenzaremos a monitorizar activamente el comportamiento de esta nueva feature en ese primer anillo, para pasado un tiempo razonable, decidir si la progresamos al siguiente anillo (hemos ganado suficiente confianza) o bien es necesario algún cambio en el software, y por tanto un nuevo despliegue al primer anillo.

 

Este mismo proceso lo realizaremos para cada uno de los anillos que hayamos definido. Podemos verlo en el siguiente gráfico:


Proceso de CI/CD con deployment rings


 

Como veis se han definido 3 anillos: al primero lo llaman "Canaries", el siguiente es para "Early adopters" y el último engloba al resto de usuarios. A modo de ejemplo, y para una aplicación tipo "Spotify" podríamos definirlos así:

 

  • Canaries: trabajadores de Spotify
  • Early adopters: Clientes que han indicado que quieren recibir versiones "beta"
  • Users: resto de usuarios

 

Podríamos definirlo de otro modo, con más o menos anillos y usando otros criterios como decía antes, pero lo importante es el proceso, que paso a detallar:

 

  1. Un commit llega a la rama principal
  2. Se dispara atomáticamente el pipeline de CI/CD
    1. Se compila, se pasan los tests y se empaqueta el software
    2. Se despliega en el primer anillo
  3. Se monitoriza activamente durante un tiempo definido
    1. Si se encuentra algun problema, se descarta la release y no será progresada a siguientes anillos
    2. Si no se encuentran problemas, se progresa la release al siguiente anillo. Podría ser con una acción de aprobación manual o de modo automático (cuando termina el tiempo definido)
  4. Se actúa igual en los siguientes anillos

 

Como vemos ganamos en confianza a coste de ser más lentos haciendo llegar las nuevas features a nuestros usuarios.


¿Qué herramientas podemos usar para implementar una estrategia de despliegue en anillos?

 

Sin duda mis favoritas son GitHub actions y Azure Pipelines, aunque podríamos usar otras como CircleCI, Jenkins o GitLab

 

En GitHub actions nos será muy útil usar environments (aunque solo están presentes en la versión Enterprise):

https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment


En Azure DevOps Pipelines, también nos apoyaremos en los environments, y además  disponemos de las Gates, que nos pueden ayudar a monitorizar nuestra release en un anillo para decidir si progresa al siguiente:

https://docs.microsoft.com/en-us/azure/devops/pipelines/release/approvals/

 

 

Feature flags (o feature toggles)

 

Fueron descritos por primera vez por Martin Fowler en este artículo y nos permiten desacoplar la exposición  progresiva de features del despliegue de una release. Para ello, las nuevas features van controladas con un toggle, que nos permite, en tiempo de ejecución, elegir si se encuentran activas o no, y por tanto modificar el comportamiento de nuestro software.

 


 

Es decir, podemos desplegar una release con varias features desactivadas, para más adelante activarlas poco a poco, monitorizando en todo momento su comportamiento.

 

 

¿Qué necesitamos para esto?

 

  1. Un servicio de gestión de toggles.
  2. Una query que pregunte en tiempo de ejecución el valor de un toggle
  3. Una remificación if-else (o algo más sofisticado) en nuestro código que haga que el software se comporte de distinta manera cuando el toggle esté o no activado.

 

¿Qué herramientas podemos usar?


Azure App Configuration nos ofrece una gestión simple de Feature toggles, que puede servir en algunos escenarios, pero lo más seguro es que rápidamente necesites algo más avanzado, como Xabaril Esquio (un paquete para .NET hecho por algunos amigos) o un servicio como LaunchDarkly.


¿Qué opción elegir?

 

Como siempre que se hacen este tipo de preguntas, sería temerario responder categóricamente, un "depende" siempre es más acertado.

 

En ambos casos vamos a controlar qué usuarios reciben features, para después ir exponiéndolas progresivamente a otros grupos de usuarios o la totalidad de estos. Además ambas opciones nos van a permitir realizar un A/B testing controlado.

 

No obstante hay algunas diferencias importantes que debemos conocer. 

 

Coste


Con Deployment rings vamos a necesitar varios entornos de producción, mientras que con Feature flags vamos a necesitar una herramienta para controlarlas (con un store para persistir las toggles), además de cambios en nuestro código para gestionarlas.

 

Para mí este último detalle es importante. Mantener muchas toggles no es gratis, tu código se ve afectado, y las toggles deben ser eliminadas una vez que hemos decidido que una feature nunca más será desactivada.

 

La deuda de toggles puede ser peligrosa, y debemos tener en cuenta que no se trata solo de borrar la toggle, sino la ramificación de código asociada. Por aquí hablan de ello: https://codescene.com/blog/feature-toggles-are-technical-debt/


Gestión del Radius Blast

 

Podemos pensar que es más sencilla con los Deployment rings, ya que se trata de enrutar a los usuarios al entorno adecuado según los hayamos categorizado (canaries, early adopter o usuario general en el ejemplo que presentaba).

 

Sin embargo las librerías y los servicios avanzados nos permiten definir grupos de usuarios y conseguir efectos similares con toggles.

 

Además las Feature toggles nos "regalan" un rollback sencillo: si una feature no funciona podemos desactivarla al momento.

 

Confianza


La confianza es algo importante para mi, siempre digo que quiero dormir por las noches. Con Feature toggles, lo normal es que tengas un único entorno productivo, por lo que cuando despliegas una release, esta va a todos los usuarios, aunque para algunos haya features desactivadas.

 

Si una release es "mala", a veces eso pasa :-(, y su problema no está tras una feature toggle, todos los usuarios lo sufrirán.

 

Sin embargo, con Deployment rings, solo los usuarios del primer anillo "sufrirían" esa "mala" release. 

 

Habilidades del equipo y aspectos organizativos

 

Diría que la opción de las Feature toggles es más cercana al desarrollo, mientras que los Deployment rings están más cerca de la infraestructura. Las habilidades de tu equipo o la organización de tu empresa pueden determinar la decisión.


¿Podemos usar ámbas opciones simultaneamente?

 

Claro, y en algunos casos será la opción más acertada :-)

 

Continuaré con estos temas en otros posts, espero que sean de interés.


Actualización:


Me indica Vicenç García que en Uber han hecho su propia herramienta para manejar esa deuda asociada a las Feature flags. ¡Gracias!


Introducing Piranha: An Open Source Tool to Automatically Delete Stale Code - Uber Engineering Blog

domingo, 23 de mayo de 2021

Conceptos: Deploy vs Release

Hola a todos,

 

hoy vengo a hablar de dos conceptos importantes, que a veces confundimos: Deploy y release.

 

¿Son lo mismo? ¿Podemos usarlos indistintamente? ¿Siempre que despliego estoy "releaseando"? ¿Necesito necesariamente desplegar para hacer release de nuevas características en mi software?

 

Vamos a entender ambos conceptos y responder a esas preguntas, y para ello comencemos por el primero, "Deploy".


Deploy

 

Deploy es el acto técnico de hacer que un software sea "copiado" a un entorno, y pueda ser utilizado por los usuarios.

 

El mecanismo que usemos para desplegar va a diferir bastante según la tecnología que usemos para nuestra aplicación: no desplegamos igual si nuestra aplicación funciona en Azure App Service (hay que copiar ficheros a

/home/site/wwwroot) o si se ejecuta en un cluster de kubernetes (desplegaríamos subiendo una imagen a un container registry y después dando una instrucción al cluster para usarla).

 

Por otro lado, el despliegue puede ser con parada o sin ella. En el primer caso la aplicación dejará de estar disponible por un tiempo (a poder ser el mínimo posible), mientras que en el segundo (despliegue sin parada) la aplicación estará siempre disponible a los usuarios mientras es actualizada con una nueva versión.

 

Aquí te dejo un enlace a una sesión donde explico técnicas para conseguir despliegues sin parada: Despliega como los grandes: zero downtime deployment.


Release

 

Se trata de hacer que una nueva característica (feature) de la aplicación esté disponible a los usuarios.

 

¿Cómo podemos hacer que una aplicación disponga de una nueva característica? La respuesta parece sencilla: desplegando una nueva versión del software que habilite esa nueva feature.

 

Sin embargo hay más modos, esa nueva feature puede ser "activada" en un momento dado, habiendo estado oculta por un tiempo. Para ello podemos por ejemplo hacer uso de Feature flags, para activar y desactivar features. Y lo que es mejor, puede ser el negocio el que lo haga, sin necesitar conocimiento técnico.

 

Escribiré sobre Feature flags, mientras tanto te dejo este post de Luis Fraile con algunas ideas.


Respondiendo a las preguntas

 

Una vez explicados los conceptos podemos responder a las preguntas anteriores

 

¿Podemos hablar indistintamente de Deploy y de Release?

 

No, como decía el despliegue es una práctica técnica (copiar nuevos bytes para que se ejecuten), mientras que la release es un concepto de negocio, que entrega nuevas features a los usuarios.

 

¿Siempre que despliego estoy "releaseando"?

 

No tiene por qué, podría desplegar una nueva versión que por ejemplo llevase nuevo código (clases), pero que aún no son usadas.

¿Y por qué querría hacer esto? Se escapa del ámbito de este post, de nuevo hablaré sobre ello, mientras tanto te dejo esta sesión de Edu Ferro donde lo entenderás:

 

#SDsummit -  "S3 Small Safe Steps. El secreto de la agilidad", Eduardo Ferro

 

¿Necesito necesariamente desplegar para hacer release de nuevas características en mi software?

 

No, de hecho podemos ofrecer una nueva feature a nuestros clientes, habiéndola desplegado en el pasado y habiéndola mantenido oculta durante un tiempo.


Bola extra: Delivery

 

Ahora que hemos visto la diferencia entre Deploy y Release, añado un nuevo concepto íntimamente relacionado con Agile: Delivery.

 

Uno de los principios del manifiesto ágil dice: "Our highest priority is to satisfy the customer through early and continuous delivery of valuable software"

 

Se habla de entrega (delivery) de software de valor al cliente, pero ¿cómo entregamos valor a un cliente?

 

Con nuevas releases de software, pero también desplegando nuevas versiones de software que aún no entreguen nuevas features, pero que por ejemplo vayan preparando el software para la llegada de dichas features, o que como decíamos, las lleven ocultas.

 

Pero también podemos entregar valor de otros modos, totalmente ajenos al código de nuestra aplicación: Piensa que podríamos entregar valor activando un sistema de telemetría, que permitiese a negocio tomar decisiones… interesante.

 

Hasta aquí mis ideas sobre estos conceptos importantes, ¿estás de acuerdo? ¿quieres matizar algo? 

¡No dudes en comentar aquí o en twitter!

 

Saludos

 

 

lunes, 26 de abril de 2021

Liveness

Hablábamos en el artículo anterior sobre Readiness, una sonda de kubernetes que indica cuando un pod está listo para atender tráfico, dejándo la plataforma de enviárselo en caso de no superar la sonda. Aquí puedes echarle un ojo.

Un pod puede no ser capaz de atender tráfico temporalmente (está arrancando por ejemplo) o debido a un error del que no pueda recuperarse solo. Para este segundo caso tenemos otro tipo de sonda, llamada Liveness, que es explicada por David Fowler en el siguiente tweet:

 

Definición de liveness de David Fowler

Esta sonda "pregunta" a un pod si sigue vivo, para en caso contrario reiniciarlo. Podemos verlo de un modo muy claro en la siguiente imagen (cortesía de la documentación de Google Cloud Platform):

 

Liveness en funcionamiento

 

Como veis, si la sonda no se supera, el pod es reiniciado, pudiendo esto resolver un problema del pod, que le impidiese hacer su trabajo con normalidad.

 

¿Siempre necesito definir esta sonda?

Para poder responder debemos entender el ciclo de vida de un pod, que podemos encontrar en la documentación oficial.
 
Allí se hace referencia a que los contenedores de un pod (si, no olvidemos que en un pod se ejecutan 1 o más contenedores) pueden ser reiniciados cuando terminan, dependiendo de la "restart policy" definida.
 
Esta política admite tres valores:

  • Always (valor por defecto)
  • On failure
  • Never

 
Salvo que utilicemos la opción "never", nuestros contenedores serán rearrancados después de terminar, usando un retardo exponencial (10s, 20s, 40s…) que está limitado a un máximo de hasta 5 minutos.
 
Si fijamos el valor "On failure" solo serán rearrancados contenedores que finalicen con un valor de retorno distinto a 0.
 
Explicado lo anterior, parece que dejando el valor por defecto, vamos a conseguir que nuestros pods siempre esten "vivos", ya que si sus contendores acabasen, por el motivo que fuere, serían reiniciados.
 
Pero la clave es: ¿mi contenedor siempre va a terminar si encuentra algún error o no es capaz de funcionar correctamente? ¿Estamos seguros de que no va a entrar en un estado de dead lock? ¿Y en un bucle infinito?
 
Si no estamos seguros de poder responder afirmativamente a las preguntas anteriores, mejor será definir una sonda de liveness, que se encargue de reiniciar el pod si este no es capaz de responder satisfactoriamente.


Definiendo la sonda de liveness

Debemos hacerlo en nuestros deployments:

La sonda anterior tratará de conectar con el puerto 80 cada 5 segundos, y en caso de poder hacerlo la prueba se considerará exitosa. En caso contrario, tras dos fallos consecutivos, el pod será reiniciado.
 
En este caso he definido un probe tcp, aunque no es la única opción disponible, ya que también podemos definir probes http o probes basados en comandos. En la documentación oficial podemos verlo en detalle, no obstante aquí os dejo un fragmento de una sonda http:
Ten en cuenta que tu eres el responsable de impementar el endpoint /healthy en tu aplicación. En el mismo puedes devolver Ok sin más (sonda "tonta") o realizar comprobaciones avanzadas, como tratar de ver si la base de datos está accesible (sonda "lista").


¿Http, tcp o comandos?

No es una respuesta sencilla, pero de entrada diría que una buena opción para empezar sería la siguiente:

  1. Http si está disponible
  2. Tcp si el pod no "habla" http
  3. Comando si el pod no "escucha" peticiones (hace tareas batch por ejemplo)

Pero no se trata de una "ley" a cumplir siempre, aquí lo que mejor funciona es el clásico "depende". Depende de tu aplicación y su contexto :-)
 
Lo que si me parece interesante es comentar que si tu pod tiene su contenedor principal y un contenedor sidecar, tipo proxy inverso nginx por ejemplo, una sonda tcp puede resultar insuficiente: podría conectar con el nginx sin problema, pudiendo estar el contenedor principal bloqueado. 

 

¿Cómo sería una sonda basada en un comando?

Como comentaba, si tu pod no escucha peticiones, no son viables las sondas http o tcp, y debemos basarnos en comandos.
 
Un ejemplo sería ver si el pod está generando periódicamente un fichero concreto en un directorio. En caso de que el pod no estuviese vivo, no podría generar el fichero y la sonda fallaría.
 
Ese script status_check.sh será el encargado de comprobar que el fichero existe y que ha sido creado recientemente (basándose en su fecha). En caso de no encontrar el fichero o detectar que no es reciente, el script saldrá con error haciendo que la sonda no se supere.

Recursos

Os dejo un par de enlaces interesantes:

La documentación oficial, donde entran en detalle de todas las configuraciones que podemos definir en una sonda, como el periodo de ejecución y los umbrales para considerar una sonda como exitosa o fallida.

Por otro lado, os dejo un proyecto opensource, Xabaril Health Checks, que si trabajais en .net core (o net 5) os ayudará a implementar sondas (readiness o liveness) basándoos en los health checks ofrecidos por la plataforma.

Espero que esta entrada os haya gustado. Saludos

lunes, 19 de abril de 2021

Readiness

¿Cuánto tiempo pasa desde que una aplicación es arrancada hasta que es capaz de desempeñar su trabajo con normalidad?

 

En aplicaciones de escritorio hay mucha variación, tenemos algunas en las que prácticamente ese tiempo es nulo, mientras que en otras, es tan elevado, que se recurre a técnicas como la splash screen.

 

Sin embargo en aplicaciones web en general, y en API's REST en particular, no solemos fijarnos tanto en esto, ya que el tiempo usualmente es mucho más pequeño.

Fijaos en esta sencilla API escrita en .net 5 (un solo fichero): 




¿Cuánto tardará en empezar a atender peticiones desde que sea iniciada? ¿Algunos milisegundos? Es posible. Fijaos que ese pequeño tiempo puede generarnos problemas, sobre todo si estamos en escenarios de auto-escalado o si buscamos despliegues sin parada (Aquí te dejo una sesión en la que hablo de este tipo de despliegues).


Por otro lado, imagina una aplicación que necesite descargar configuración o secretos desde un recurso externo antes de poder empezar a atender peticiones. En este caso ese tiempo será muy superior.


Mi definición de "readiness"


"Es el estado de una aplicación en el cual está capacitada para hacer su trabajo con normalidad"

En el supuesto anterior, la aplicación arranca y necesita cierto tiempo para descargar recursos externos (secretos u otra configuración). Una vez hecho está "ready", puede hacer su trabajo con normalidad y por tanto atender peticiones de usuario.



Imagén explicativa de Readiness. Un pod arranca y necesita tiempo hasta que puede aceptar tráfico


¿Qué es un readiness probe?


Es una sonda o verificación que nos indicará si una aplicación se encuentra en un estado de "ready" o "not ready". En base al resultado de la verificación, se enviará tráfico o no a la aplicación.

Veámoslo en el caso de kubernetes. Allí, los pods se crean y destruyen por diferentes motivos, alguno de los cuales no requieren nuestra intervención (un pod es movido de nodo, o simplemente falla y es rearrancado). Por tanto no sería nada deseable que un pod que aún no esté listo recibiese tráfico, ya que generaría errores.

Para conseguirlo, debemos definir una "readiness probe" en nuestros deployments, del siguiente modo:



En este caso he definido un probe http, aunque no es la única opción disponible, ya que también podemos definir probes tcp o probes basados en comandos. En la documentación oficial podemos verlo.

 

Como veis, he definido un periodo de ejecución de la sonda de 5 segundos, además de un umbral de 2 (threshold), que os paso a explicar:


  • Inicialmente, un pod arranca en estado "Not ready"
  • Una vez que supera la sonda de readiness pasa a estado "Ready"
  • Si la sonda falla 2 veces seguidas (aquí está ese umbral), pasa de nuevo a estado "Not ready", y no recibirá tráfico.

Solo hay un matiz a lo anterior: si no hemos definido el Readiness en nuestro deployment, el pod siempre será considerado como "Ready".

 Con todo esto, mi deployment de kubernetes queda así:



 


¿Por qué es importante el readiness en escenarios de auto-escalado?


Lo mencioné de pasada antes, merece explicarlo:

 

Si tenemos reglas de autoescalado, tarde o temprano, y en base a estas reglas, nuevos pods serán creados para atender la creciente demanda. En esos casos, una vez que el pod es creado, empezará a recibir tráfico de inmediato, a no ser que definamos las probes de readiness adecuadas. 

 

Si recibe tráfico de inmediato, y no tenemos reglas de readiness, vamos a tener momentos "problemáticos", que son esos tiempos muertos desde que un pod arranca hasta que está listo para atender tráfico.

 

Sin embargo si definimos sondas de readiness, el nuevo pod no recibirá tráfico hasta que éstas sondas son satisfactorias, evitando así cualquier tipo de downtime.



¿Hay más tipos de sonda?


Si, tenemos sondas de Liveness y de Startup, las veremos en otras entradas.


Espero que os haya resultado interesante.


domingo, 4 de noviembre de 2018

La cobertura de código, .NET Core y Azure DevOps!


Martín Fowler tiene un buen artículo acerca de la cobertura de código:
"From time to time I hear people asking what value of test coverage (also called code coverage) they should aim for, or stating their coverage levels with pride. Such statements miss the point. Test coverage is a useful tool for finding untested parts of a codebase. Test coverage is of little use as a numeric statement of how good your tests are."


Yo estoy 100% de acuerdo, es valioso conocer que partes de nuestro código no están cubiertas por tests, y para ello necesitamos herramientas que nos ofrezcan dicha información.

¿Cual es el estado en de la cobertura de código en .NET Core?

 
Cuando apareció .NET Core a algunos nos sorprendió que la capacidad de analizar la cobertura de código de nuestros tests había desaparecido, despues haber estado mucho tiempo disponible en .NET Framework.

Varias issues en github pedían su vuelta (579, 981 y 902 por ejemplo) pero el tiempo pasaba, las versiones de .NET Core se sucedían y seguíamos sin esta feature. Afortunadamente hay varias alternativas, tanto de pago (DotCover) como gratuitas (OpenCover y Coverlet), por lo que la ausencia de soporte “de serie” no ha sido determinante. Numerosos artículos muestran cómo hacerlo, como este de Scott Hanselman que fue el primero que leí.

Finalmente con el SDK 2.1.400 de .NET Core recuperamos esta capacidad de disponer de datos de cobertura de código. Vamos a ver como explotarla, viene un post largo...

Cobertura desde Visual Studio


Vamos a empezar, veamos cómo obtener la cobertura de nuestros test en Visual Studio:

  1. Debemos referenciar, en nuestro proyecto de pruebas, el paquete Microsoft.NET.Test.Sdk
  2. Una vez hecho, podemos pedir a Visual Studio la cobertura

Solicitar cobertura

Resultados de cobertura


Aquí merece la pena decir que la documentación oficial indica que añadamos algo más al csproj, y es el modo de debug Full. Debo decir que a mi nunca me ha hecho falta, aunque es algo que quiero analizar.

Un detalle importante, esto solo esta disponible en VisualStudio 2017 Enterprise :-(

Por otro lado, si os fijais en el resultado de cobertura veréis que se está teniendo en cuenta el propio código de los tests, ya escribí acerca de esto hace tiempo (https://www.serginet.com/2017/06/tip-codecoverage-vsts.html), no voy a extenderme ahora.

Tip: si no conseguis que Visual Studio os muestre información de cobertura, podéis probar a actualizar el paquete Microsoft.NET.Test.Sdk a una versión reciente.

Cobertura desde el CLI


¿Y si no tenemos Visual Studio Enterprise? ¿Quizás no trabajamos en Windows? ¿Y si preferimos Visual Studio Code?

No todo está perdido, ahora empieza la parte interesante de esta entrada, y es que aún podremos conseguir resultados de cobertura, apoyándonos en AzureDevops. Empecemos por al principio: obtener datos de cobertura con el CLI de .NET Core.

Nos situamos en el proyecto de tests y ejecutamos 

dotnet test  --collect:"Code Coverage"

Esto hará que un fichero binario, de extensión .coverage, aparezca dentro de una carpeta de nombre TestResults.

Fichero .coverage

Recuerda que igual que antes debes referenciar Microsoft.NET.Test.Sdk desde tu proyecto de tests.

¿Cómo explotamos este fichero? Como comentaba es binario y podemos arrastrarlo a Visual Studio Enterprise para ver detalles de cobertura. ¿Perdona? Si ya se, hemos vuelto al principio, a la dependencia con Visual Studio Enterprise.

Nos saltaremos esta dependencia con una Build de Azure DevOps, pero antes vamos a hablar de Report Generator, una interesante herramienta que transforma ficheros de cobertura en diversos formatos a nuevos formatos legibles por un humano. ¿Suena bien? Pues la primera en la frente, no soporta el formato .coverage, si queremos un buen informe debemos primero convertir el fichero de cobertura obtenido a un formato XML soportado.

Desde la propia documentación de Report Generator nos guían para conseguirlo: podemos hacer uso de una herramienta instalada por Visual Studio que nos permitirá la conversión, CodeCoverage.exe.

Su uso para este fin es sencillo: debemos usar el comando analyze, pasándole el fichero .coverage y especificando el fichero resultado que deseamos:


             CodeCoverage.exe analyze /output:informe.coveragexml informe.coverage

CodeCoverage.exe


Podeis encontrar esta herramienta en la instalación de Visual Studio, aquí:
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Team Tools\Dynamic Code Coverage Tools

Una vez con el fichero de cobertura en formato XML, podemos finalmente utilizar Report Generator para obtener nuestro informe de cobertura:

  1. Añadimos la tool de report generator (<DotNetCliToolReference Include="dotnet-reportgenerator-cli" Version="4.0.2" />) en el csproj del proyecto de tests. 
  2. La ejecutamos:
         dotnet reportgenerator "-reports:informe.coveragexml" "-targetdir:CoverageReport" "-reporttypes:HTML;HTMLSummary"



Con esto obtendremos un informe detallado en HTML, que incluso nos muestra el código por el que han pasado nuestros tests. Nada que envidiar a las capacidades de Visual Studio Enterprise




Nota: si hay algún assembly del que no tenéis código (un paquete de terceros que no hayais excluido de la cobertura por ejemplo) la herramienta mostrará avisos, pero aun así generará el informe.

Resumiendo, tenemos un buen informe de cobertura, pero aun tenemos una dependencia con Visual Studio Enterprise. Vamonos a Azure DevOps y consigamos el informe allí.

Cobertura en Azure DevOps


De entrada, conseguir datos de cobertura en Azure DevOps es muy sencillo. Tan solo tenemos que meter una tarea de tipo dotnet test, y habilitar el check que indica que se publiquen los resultados de test y su cobertura.



Esto va a funcionar, pero el resultado que nos dará será más bien pobre. Simplemente un porcentaje de cobertura y el fichero .coverage para que lo descarguemos. Ese porcentaje de poco vale, recuerda el artículo de Fowler.

¿Podemos publicar en Azure DevOps un buen informe?


Claro! para ello solo debemos saber tres cosas:

  • Azure DevOps soporta informes de cobertura detallados en dos formatos: Cobertura y JaCoco (puedes verlo aquí), así como informes Html
  •  En los agentes de build disponemos de CodeCoverage.exe, lo que nos habilita el uso de Report Generator
  • Report Generator nos permite generar un reporte en formato Cobertura!

Vamos a ello:


1. Nos debemos asegurar que tenemos desmarcada la opción de publicar resultados de tests y de cobertura. Queremos hacerlo nosotros.

2. Añadimos parametros a dotnet test para que genere la información de test y de cobertura. Además indicamos que dicha informaciónla guarde en la carpeta temporal del agente de build. Los parámetros son:
  • --logger trx
  • --collect:"Code Coverage"
  • --results-directory $(Agent.TempDirectory)


3. Publicamos el resultado de la ejecución de tests, no queremos perder esa información. Para ello metemos una tarea de tipo "Publish Test Results", configurándola como se ve a continuación:




4. Ahora vamos a convertir el archivo .coverage al formato xml. Para ello metemos una tarea de tipo powershell, que ejecute el siguiente script:




El script es sencillo, lista ficheros .coverage en la carpeta temporal del agente, y por cada uno lo convierte, del mismo modo que lo hicimos en nuestro equipo. Fijaos que los archivos de salida en formato XML los estamos dejando junto a los fuentes (usando la variable $(Build.SourcesDirectory)). Podríamos dejarlos en otro sitio, el caso es poder acceder a ellos.

La tarea del pipeline queda así:


Aquí os dejo el powershell completo:

Write-Host "temp: $(Agent.TempDirectory)"
$i=0;
Write-Host "Searching coverage files"
Get-ChildItem -Path $(Agent.TempDirectory) -Recurse | where {$_.extension -eq ".coverage"} | % { 
Write-Host "Transforming"
Write-Host $_.FullName
$output="$(Build.SourcesDirectory)\coverage$i.coveragexml"
Write-Host "Output"
Write-Host $output
& "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe" analyze /output:$output $_.FullName
Write-Host "Done"
$i++
}
Write-Host "Finished"

Nota: no tengo muy claro porque Azure DevOps me genera por cada proyecto de tests dos archivos de cobertura, tengo que averiguarlo ya que no sucede en local. Este es el motivo de que haga un bucle en el powershell.
En cualquier caso solo vamos a coger el primero de los ficheros convertidos para acabar publicándolo (previas conversiones que vienen a continuación).

5. Generamos el informe, primero en formato cobertura

Partiendo del fichero XML, metemos una tarea de tipo dotnet, que ejecutará Report Generator como se ve a continuación:

Convertir a formato cobertura


6. Generamos el informe, ahora en html

Partiendo del fichero XML, metemos una tarea de tipo dotnet, que ejecutará Report Generator como se ve a continuación:

Convertir a formato html


7. Finalmente publicamos la información de cobertura

Una tarea de tipo "Publish code coverage results", configurada como se muestra, es lo que necesitamos:

Publicar cobertura

Con esto lo tenemos todo, mirad a modo de resumen como quedan los pasos de la build definition:




¿Cual es el resultado?


Con todo esto, si encolamos una build, al acabar veremos lo que buscabamos:

Resultado de tests y porcentaje de cobertura (esto no es nuevo):



Y un infome detallado, publicado directamente en Azure DevOps!






Resumen y futuros pasos


Despues de todo tenemos lo que bucabamos. Disponemos de informes detallados de cobertura, y además no necesitamos Visual Studio Enterprise. Para ello nos hemos apoyado en Azure DevOps

En otros posts quiero seguir trabajando con estos temas:

  • Usar otra herramienta para generar la cobertura, de modo que podamos traajar en local 100% sin Visual Studio Enterprise
  • Automatizar esto para ser usado con Visual Studio Code
  • Explotar la información de cobertura desde SonarQube
  • Echar un ojo a Code Coverage Protector. Me lo recomendo Julio Avellaneda (@julitogtu) en twitter

Espero que os haya sido útil!