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!

2 comentarios: