2009-01-06 10 views
21

¿Hay alguna manera de probar la cobertura del código dentro de Visual Studio si estoy usando MSTest? ¿O debo comprar NCover?MSTest Code Coverage

¿Vale la pena el dinero del NCover Enterprise o las versiones anteriores son lo suficientemente buenas si Microsoft no proporciona herramientas integradas para la cobertura del código?

EDIT: Descripción de los productos VS y cuáles incluyen la cobertura de código http://www.microsoft.com/visualstudio/en-us/products/teamsystem/default.mspx

TestDriven.NET (http://testdriven.net/) se puede utilizar si su versión VS no lo soporta.

Respuesta

14

Sí, puede encontrar información de cobertura de código desde Visual Studio, siempre que tenga una versión de Visual Studio que proporcione esa funcionalidad, como Team System. Al configurar las pruebas unitarias en VS.NET, se creará y agregará un archivo localtestrun.testrunconfig como parte de la solución. Haga doble clic en este archivo y busque la opción Cobertura de código a la izquierda del cuadro de diálogo. Seleccione los conjuntos para los cuales desea recopilar información de cobertura de código y luego vuelva a ejecutar las pruebas unitarias. La información de cobertura del código será recopilada y estará disponible. Para obtener la información de cobertura del código abra la ventana de resultados de la prueba y haga clic en el botón de resultados de cobertura del código, que abrirá una ventana alternativa con los resultados.

13

MSTest incluye cobertura de código, al menos lo hace en la versión de VS que tengo. Sin embargo, debe habilitar la instrumentación en el testrunconfig, que es simplemente feo y un PITA importante.

Una opción mucho más fácil es usar TestDriven.NET, que puede automatizar la cobertura, incluso para MSTest. Y dado que usa el núcleo MSTest, aún obtiene todo el valor VS, como la coloración (líneas rojas/azules para el código cubierto). Ver here (incluyendo un screencast), o desde una imagen dice más que mil palabras:

alt text http://www.mutantdesign.co.uk/weblog/images/DrivingMSTestandTeamCoverageusingTes.NET_F424/MSTestAndTeamCoverage_thumb1.gif

0

Si usted no tiene el Estudio de edición de Visual último, también se puede utilizar esta tarea MSBuild para generar el informe de cobertura de código .

http://archive.msdn.microsoft.com/vscoveragetoxmltask

+0

enlace está roto. Esta es la razón por la cual las respuestas de solo enlace son mal vistas. – James

6

Para los futuros lectores:

Wow, esto no fue divertido. Espero que esto ayude a alguien en internet.

Tenga en cuenta que la existencia de "CodeCoverage.exe" puede depender de la versión de Visual Studio que tenga. Y es posible que deba instalar VS (alguna versión mejorada) en el servidor de compilación.

set __msTestExe=C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\MSTest.exe 
set __codeCoverageExe=C:\Program Files (x86)\Microsoft Visual Studio 14.0\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe 

rem (the below is a custom C# console application, code seen below) 
set __customCodeCoverageMergerExe=CoverageCoverterConsoleApp.exe 

rem below exe is from https://www.microsoft.com/en-us/download/details.aspx?id=21714 
set __msXslExe=C:\MyProgFiles\MsXslCommandLine\msxsl.exe 

REM the below calls will create the binary *.coverage files 
"%__codeCoverageExe%" collect /output:"D:\BuildStuff\TestResults\AAA_DynamicCodeCoverage.coverage" "%__msTestExe%" /testcontainer:"D:\BuildStuff\BuildResults\My.UnitTests.One.dll" /resultsfile:"D:\BuildStuff\TestResults\My.UnitTests.One.trx" 
"%__codeCoverageExe%" collect /output:"D:\BuildStuff\TestResults\BBB_DynamicCodeCoverage.coverage" "%__msTestExe%" /testcontainer:"D:\BuildStuff\BuildResults\My.UnitTests.Two.dll" /resultsfile:"D:\BuildStuff\TestResults\My.UnitTests.Two.trx" 
"%__codeCoverageExe%" collect /output:"D:\BuildStuff\TestResults\CCC_DynamicCodeCoverage.coverage" "%__msTestExe%" /testcontainer:"D:\BuildStuff\BuildResults\My.UnitTests.Three.dll" /resultsfile:"D:\BuildStuff\TestResults\My.UnitTests.Three.trx" 


rem below, the first argument is the new file, the 2nd through "N" args are the result-files from the three "%__codeCoverageExe%" collect above 
rem this will take the three binary *.coverage files and turn them into one .xml file 
"%__customCodeCoverageMergerExe%" "D:\BuildStuff\TestResults\DynamicCodeCoverage.merged.coverage.converted.xml" "D:\BuildStuff\TestResults\AAA_DynamicCodeCoverage.coverage" "D:\BuildStuff\TestResults\BBB_DynamicCodeCoverage.coverage" "D:\BuildStuff\TestResults\CCC_DynamicCodeCoverage.coverage" 


"%__msXslExe%" "D:\BuildStuff\TestResults\DynamicCodeCoverage.merged.coverage.converted.xml" "D:\BuildStuff\Xsl\VSCoverageToHtml.xsl" -o "D:\BuildStuff\TestResults\CodeCoverageReport.html" 

También se pueden combinar los 3 UnitTests.dlls en una llamada

REM the below calls will create the binary *.coverage files 
"%__codeCoverageExe%" collect /output:"D:\BuildStuff\TestResults\ZZZ_DynamicCodeCoverage.coverage" "%__msTestExe%" /testcontainer:"D:\BuildStuff\BuildResults\My.UnitTests.One.dll" /testcontainer:"D:\BuildStuff\BuildResults\My.UnitTests.Two.dll" /testcontainer:"D:\BuildStuff\BuildResults\My.UnitTests.Three.dll" /resultsfile:"D:\BuildStuff\TestResults\My.UnitTests.AllOfThem.trx" 


rem below, the first argument is the new file, the 2nd through "N" args are the result-files from the three "%__codeCoverageExe%" collect above 
rem this will take the one binary *.coverage files and turn them into one .xml file 
"%__customCodeCoverageMergerExe%" "D:\BuildStuff\TestResults\DynamicCodeCoverage.merged.coverage.converted.xml" "D:\BuildStuff\TestResults\ZZZ_DynamicCodeCoverage.coverage" 


"%__msXslExe%" "D:\BuildStuff\TestResults\DynamicCodeCoverage.merged.coverage.converted.xml" "D:\BuildStuff\Xsl\VSCoverageToHtml.xsl" -o "D:\BuildStuff\TestResults\CodeCoverageReport.html" 

VSCoverageToHtml.xsl

También encontré algunos XSL en internet. (los 3 siguientes enlaces son más o menos la misma XSL)

http://codetuner.blogspot.com/2011_09_01_archive.html

http://jp.axtstar.com/?page_id=258

http://codetuner.blogspot.com/2011/09/convert-mstest-code-covarage-results-in.html

les dejo el XSL aquí "por si acaso" Die de los URL en el futuro . Coloque el xsl siguiente en un archivo llamado "VSCoverageToHtml.xsl" (como se menciona anteriormente).

<?xml version="1.0" encoding="utf-8"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="html" indent="yes"/> 
    <xsl:template match="/" > 
     <html> 
      <head> 

       <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"/>   

       <style type="text/css"> 
        th { 
        background-color:#dcdcdc; 
        border:solid 1px #a9a9a9; 
        text-indent:2pt; 
        font-weight:bolder; 
        } 
        #data { 
        text-align: center; 
        } 
       </style> 

       <script language="JavaScript" type="text/javascript" > 

        function CreateJavescript(){ 
        var fileref=document.createElement('script'); 
        fileref.setAttribute("type","text/javascript"); 
        fileref.setAttribute("src", "script1.js"); 
        document.getElementsByTagName("head")[0].appendChild(fileref); 
        } 

        function toggleDetail(control) { 
        var ctrlId = $(control).attr('Id'); 
        $("tr[id='"+ctrlId +"']").toggle(); 
        }     

       </script> 

       <title>Code Coverage Report</title> 
      </head> 
      <body onload='CreateJavescript()' > 
       <h1>Code Coverage Report</h1> 
       <table border="1"> 
        <tr> 
         <th colspan="3"/> 
         <th>Name</th> 
         <th>Blocks Covered</th> 
         <th>Blocks Not Covered</th> 
         <th>Coverage</th> 
        </tr> 
        <xsl:apply-templates select="//CoverageDSPriv/Module" /> 
       </table> 
      </body> 
     </html> 
    </xsl:template> 

    <xsl:template match="Module"> 
     <xsl:variable name="parentId" select="generate-id(./..)" /> 
     <xsl:variable name="currentId" select="generate-id(.)" /> 
     <tr id="{$parentId}"> 
      <td id="{$currentId}"  colspan="3"    onClick="toggleDetail(this)"  onMouseOver="this.style.cursor= 'pointer' ">[+]</td> 
      <td> 
       <xsl:value-of select="ModuleName" /> 
      </td> 
      <td id="data"> 
       <xsl:value-of select="BlocksCovered" /> 
      </td> 
      <td id="data"> 
       <xsl:value-of select="BlocksNotCovered" /> 
      </td> 
      <xsl:call-template name="CoverageColumn"> 
       <xsl:with-param name="covered" select="BlocksCovered" /> 
       <xsl:with-param name="uncovered" select="BlocksNotCovered" /> 
      </xsl:call-template> 
     </tr> 
     <xsl:apply-templates select="NamespaceTable" /> 
     <tr id="{$currentId}-end" style="display: none;"> 
      <td colspan="5"/> 
     </tr> 
    </xsl:template> 

    <xsl:template match="NamespaceTable"> 
     <xsl:variable name="parentId" select="generate-id(./..)" /> 
     <xsl:variable name="currentId" select="generate-id(.)" /> 
     <tr id="{$parentId}" style="display: none;"> 
      <td> - </td> 
      <td id="{$currentId}" 
       colspan="2" 
       onClick="toggleDetail(this)" 
       onMouseOver="this.style.cursor= 'pointer' ">[+]</td> 
      <td> 
       <xsl:value-of select="NamespaceName" /> 
      </td> 
      <td id="data"> 
       <xsl:value-of select="BlocksCovered" /> 
      </td> 
      <td id="data"> 
       <xsl:value-of select="BlocksNotCovered" /> 
      </td> 
      <xsl:call-template name="CoverageColumn"> 
       <xsl:with-param name="covered" select="BlocksCovered" /> 
       <xsl:with-param name="uncovered" select="BlocksNotCovered" /> 
      </xsl:call-template> 
     </tr> 
     <xsl:apply-templates select="Class" /> 
     <tr id="{$currentId}-end" style="display: none;"> 
      <td colspan="5"/> 
     </tr> 
    </xsl:template> 

    <xsl:template match="Class"> 
     <xsl:variable name="parentId" select="generate-id(./..)" /> 
     <xsl:variable name="currentId" select="generate-id(.)" /> 
     <tr id="{$parentId}" style="display: none;"> 
      <td> - </td> 
      <td> - </td> 
      <td id="{$currentId}" 
       onClick="toggleDetail(this)" 
       onMouseOver="this.style.cursor='pointer' ">[+]</td> 
      <td> 
       <xsl:value-of select="ClassName" /> 
      </td> 
      <td id="data"> 
       <xsl:value-of select="BlocksCovered" /> 
      </td> 
      <td id="data"> 
       <xsl:value-of select="BlocksNotCovered" /> 
      </td> 
      <xsl:call-template name="CoverageColumn"> 
       <xsl:with-param name="covered" select="BlocksCovered" /> 
       <xsl:with-param name="uncovered" select="BlocksNotCovered" /> 
      </xsl:call-template> 
     </tr> 
     <xsl:apply-templates select="Method" /> 
     <tr id="{$currentId}-end" style="display: none;"> 
      <td colspan="5"/> 
     </tr> 
    </xsl:template> 

    <xsl:template match="Method"> 
     <xsl:variable name="parentId" select="generate-id(./..)" /> 
     <tr id="{$parentId}" style="display: none;"> 
      <td> -</td> 
      <td> - </td> 
      <td> - </td> 
      <td> 
       <xsl:value-of select="MethodName" /> 
      </td> 
      <td id="data"> 
       <xsl:value-of select="BlocksCovered" /> 
      </td> 
      <td id="data"> 
       <xsl:value-of select="BlocksNotCovered" /> 
      </td> 
      <xsl:call-template name="CoverageColumn"> 
       <xsl:with-param name="covered" select="BlocksCovered" /> 
       <xsl:with-param name="uncovered" select="BlocksNotCovered" /> 
      </xsl:call-template> 
     </tr> 
    </xsl:template> 

    <xsl:template name="CoverageColumn"> 
     <xsl:param name="covered" select="0" /> 
     <xsl:param name="uncovered" select="0" /> 
     <td id="data"> 
      <xsl:variable name="percent" 
       select="($covered div ($covered + $uncovered)) * 100" /> 
          <xsl:attribute name="style"> 
       background-color: 
       <xsl:choose> 
        <xsl:when test="number($percent >= 90)">#86ed60;</xsl:when> 
        <xsl:when test="number($percent >= 70)">#ffff99;</xsl:when> 
        <xsl:otherwise>#FF7979;</xsl:otherwise> 
       </xsl:choose> 
      </xsl:attribute> 
      <xsl:if test="$percent > 0"> 
       <xsl:value-of select="format-number($percent, '###.##')" />% 
      </xsl:if> 
      <xsl:if test="$percent = 0"> 
       <xsl:text>0.00%</xsl:text> 
      </xsl:if> 
     </td> 
    </xsl:template> 
</xsl:stylesheet> 

Aquí hay una pequeña herramienta de línea de comandos para ayudar.

https://www.microsoft.com/en-us/download/details.aspx?id=21714

using System; 

using Microsoft.VisualStudio.Coverage.Analysis; 
using System.Collections.Generic; 

/* References 
\ThirdPartyReferences\Microsoft Visual Studio 11.0\Microsoft.VisualStudio.Coverage.Analysis.dll 
\ThirdPartyReferences\Microsoft Visual Studio 11.0\Microsoft.VisualStudio.Coverage.Interop.dll 
*/ 

namespace MyCompany.VisualStudioExtensions.CodeCoverage.CoverageCoverterConsoleApp 
{ 
    class Program 
    { 
     static int Main(string[] args) 
     { 
      if (args.Length < 2) 
      { 
       Console.WriteLine("Coverage Convert - reads VStest binary code coverage data, and outputs it in XML format."); 
       Console.WriteLine("Usage: ConverageConvert <destinationfile> <sourcefile1> <sourcefile2> ... <sourcefileN>"); 
       return 1; 
      } 

      string destinationFile = args[0]; 
      //destinationFile = @"C:\TestResults\MySuperMergedCoverage.coverage.converted.to.xml"; 

      List<string> sourceFiles = new List<string>(); 

      //files.Add(@"C:\MyCoverage1.coverage"); 
      //files.Add(@"C:\MyCoverage2.coverage"); 
      //files.Add(@"C:\MyCoverage3.coverage"); 


      /* get all the file names EXCEPT the first one */ 
      for (int i = 1; i < args.Length; i++) 
      { 
       sourceFiles.Add(args[i]); 
      } 

      CoverageInfo mergedCoverage; 
      try 
      { 
       mergedCoverage = JoinCoverageFiles(sourceFiles); 
      } 
      catch (Exception e) 
      { 
       Console.WriteLine("Error opening coverage data: {0}", e.Message); 
       return 1; 
      } 

      CoverageDS data = mergedCoverage.BuildDataSet(); 

      try 
      { 
       data.WriteXml(destinationFile); 
      } 
      catch (Exception e) 
      { 

       Console.WriteLine("Error writing to output file: {0}", e.Message); 
       return 1; 
      } 

      return 0; 
     } 

     private static CoverageInfo JoinCoverageFiles(IEnumerable<string> files) 
     { 
      if (files == null) 
       throw new ArgumentNullException("files"); 

      // This will represent the joined coverage files 
      CoverageInfo returnItem = null; 
      string path; 

      try 
      { 
       foreach (string sourceFile in files) 
       { 
        // Create from the current file 

        path = System.IO.Path.GetDirectoryName(sourceFile); 
        CoverageInfo current = CoverageInfo.CreateFromFile(sourceFile, new string[] { path }, new string[] { path }); 

        if (returnItem == null) 
        { 
         // First time through, assign to result 
         returnItem = current; 
         continue; 
        } 

        // Not the first time through, join the result with the current 
        CoverageInfo joined = null; 
        try 
        { 
         joined = CoverageInfo.Join(returnItem, current); 
        } 
        finally 
        { 
         // Dispose current and result 
         current.Dispose(); 
         current = null; 
         returnItem.Dispose(); 
         returnItem = null; 
        } 

        returnItem = joined; 
       } 
      } 
      catch (Exception) 
      { 
       if (returnItem != null) 
       { 
        returnItem.Dispose(); 
       } 
       throw; 
      } 

      return returnItem; 
     } 
    } 
} 

Véase también:

Code Coverage files merging using code in VS 2012 Dynamic Code Coverage