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!

martes, 27 de marzo de 2018

Todas las abstracciones fugan

Esta frase la he escuchado (y leído) bastantes veces, y para ser honesto, nunca terminé de entender a que se refería. Hace poco volví a leerla y me dije: Entendámosla de una vez por todas
¿De donde viene todo? Del artículo The law of leaky abstractions, escrito en Noviembre de 2002 por Joel Spolsky.

“All non-trivial abstractions, to some degree, are leaky”

Viene a decir que toda abstracción, excluyendo aquellas que son triviales, tarde un temprano fuga, revelando aspectos que tratábamos de abstraer.

Veámoslo con un ejemplo:


Todos hemos visto una implementación del patrón repository como la siguiente:

    public interface IStudentRepository : IDisposable
    {
        IQueryable<Student> GetStudents();
        Student GetStudentByID(int studentId);
        void InsertStudent(Student student);
        void DeleteStudent(int studentID);
        void UpdateStudent(Student student);
        void Save();
    }

El hecho de que el método GetStudents devuelva un IQueryable implica que debamos conocer detalles acerca de la implementación del repositorio, conocer que hay detrás, ya que el proveedor de Linq ejecutará algunas instrucciones en memoria y otras sobre una motor de bases de datos. No podemos ignorarlo si queremos no meternos en líos. Podemos decir por tanto que nuestra abstracción IStudentRepository fuga


¿Hay más ejemplos comunes?


Por supuesto, una rápida búsqueda por internet nos dará un bonito listado de abstracciones, que usamos comúnmente, y que fugan (¿todas lo hacen no? XDXD)
  • El garbage collector: tarde o temprano, si tu aplicación realiza mucha presión sobre la memoria, o si es una aplicación que necesita un altísimo rendimiento, tendrás que entender su implementación para alcanzar ese nivel de optimización deseado.
  • Un sistema de ficheros distribuido, bajo SMB por ejemplo, trata de abstraerte de que los ficheros están en remoto, mostrándolos como si de un disco local se tratase.  No obstante la latencia o los posibles micro-cortes de red pueden hacer que tengas que manejar dichos ficheros de un modo diferente (y conocer que de hecho esos ficheros no son locales).
  • La clase string de C# por ejemplo, que debido su implementación genera un nuevo string cada vez que concatenamos dos strings existentes previamente. Si no conocemos este detalle, podemos sufrir serios problemas de rendimiento y escalabilidad.

¿Es esto malo?


No lo considero necesariamente malo, simplemente está ahí y lo mejor es que lo conozcamos y seamos conscientes de ello.
Nuevas tecnologías y herramientas tratan de elevar el nivel de abstracción que manejamos al desarrollar software, nos obstante, ya que estas abstracciones fugarán de un modo u otro, debemos conocer sus detalles, cómo estas abstracciones funcionan y han sido implementadas, antes de pasar sin más a utilizarlas.

Recursos: 


miércoles, 23 de agosto de 2017

Cuestiónate todo: Throw vs Throw ex

Hace unos días leía una entrada sobre excepciones en Campus MVP: GAMBADAS: Los tres principales pecados al gestionar errores y excepciones. Allí nos daban consejos para trabajar con excepciones, y entre ellos nos recordaban como debemos relanzar una excepción, y cual es la diferencia entre relanzarla haciendo throw o haciendo throw ex. Me refiero a esto:

image

En el artículo se indicaba que cuando relanzamos una excepción haciendo throw ex estamos “reseteando” el call stack, por lo que un elemento superior que capture la excepción no va a poder saber donde se generó originalmente,y obtendrá como origen la línea en la que hacemos el throw ex.

image

Según lo leí la cabeza me jugo una mala pasada y me dije: “eso esta mal, hacer throw o throw ex es lo mismo”. Lo malo es que rápidamente me fui a Twitter a pregonarlo, donde me corrigieron a la primera de cambio: no no es lo mismo. Si nos vamos a la documentación oficial de C# (https://docs.microsoft.com/es-es/dotnet/csharp/language-reference/keywords/throw) vemos esto:
image

Parece estar meridianamente claro… ¿pero es así en todos los casos?  ¿De donde había sacado yo las idea de que era lo mismo una cosa que la otra?

El ejemplo

Vamos a comprobarlo, para ello he creado un código de ejemplo, es sencillo. Tenemos un método  que lanza una excepción, ya que hace una división por cero
public static int FaultyCode(int number)
{
    int divisor = 0;
    return number / divisor;
}
Luego tenemos un segundo método (CatchAndThrowEx) llama al anterior, recubriendo la llamada en un bloque try/catch. Una vez capturada la excepción la relanza con throw ex.
public static void CatchAndThrowEx()
{
    try
    {
        var result = FaultyCode(5);
    }
    catch (Exception ex)
    {
        throw ex;
    }
}
También un tercer método(CatchAndThrow) similar al anterior y que también llama al código que falla. La llamada es recubierta en un bloque try/catch. Una vez capturada la excepción la relanza con throw.
public static void CatchAndThrow()
{
    try
    {
        var result = FaultyCode(5);
    }
    catch(Exception ex)
    {
        throw;
    }
}
Finalmente, desde el main de una aplicación de consola, invocamos a los dos métodos que relanzan la excepción, y la capturamos, trazando su call stack, para que veamos si este ha sido “reseteado” o no.

public static void Main(string[] args)
{
    try
    {
        Console.WriteLine("==============================================");
        Console.WriteLine("CatchAndThrow:");
        CatchAndThrow();
    }
    catch (Exception ex)
    {
        ex.ToConsole();
    }

    try
    {

        Console.WriteLine("==============================================");
        Console.WriteLine("CatchAndThrowEx:");
        CatchAndThrowEx();
    }
    catch (Exception ex)
    {
        ex.ToConsole();
    }
}
Si ejecutamos lo anterior, obtenemos lo esperado: cuando se relanza con throw ex se pierde el call stack y no somos capaces de ver el método donde se originó la excepción… malo
Core 2

Seguimos cuestionando

Antes de abandonar me dije, lo he ejecutado compilando en  debug… ¿habrá cambio en release?
Si ejecuto en release (dotnet run -c Release) las cosas cambian!  Como podéis ver a continuación throw y throw ex se comportan exactamente igual.
image

¡Esto no estaba en el guión!
Visto lo visto me dije, será cosa de .Net Core 2? Vamos a comprobar en otras configuraciones. El resultado está en la siguiente tabla, y siempre es el mismo: cuando se ha compilado en release throw y throw ex se comportan igual:

PlataformaCompilaciónResultado
.Net Core 1.1 Debug Throw mantiene el call stack. Throw ex no lo hace
.Net Core 1.1 Release Throw y Throw ex tienen el mismo comportamiento
.Net Core 2 Debug Throw mantiene el call stack. Throw ex no lo hace
.Net Core 2 Release Throw y Throw ex tienen el mismo comportamiento
.Net Framework 4.6.2  Debug Throw mantiene el call stack. Throw ex no lo hace
.Net Framework 4.6.2  Release Throw y Throw ex tienen el mismo comportamiento 



¿Donde está el truco?

Suele haber un motivo, siempre lo hay, que explique comportamientos extraños, comportamiento que contradicen la documentación oficial como es en este caso.

Yo ni lo había imaginado, pero Juanma (https://twitter.com/gulnor) rápidamente intuyó que pasaba: El método FaultyCode era demasiado sencillo, tanto que al compilar en Release se convierte en un inline, por lo que pasa a ser código del método que le invoca.

Si complicamos FaultyCode, el compilador no lo convertirá en un inline, y finalmente  veremos que el comportamiento vuelve a ser el esperado, y el documentado:

  • Throw mantiene el call stack
  • Throw ex lo resetea


Saludos, espero que os haya resultado interesante