2009-01-06 231 views
21

如果我正在使用MSTest,有沒有一種方法可以在Visual Studio中測試代碼覆蓋率?或者我必須購買NCover?MSTest代碼覆蓋率

如果微軟沒有提供內置的工具來執行代碼覆蓋,NCover企業是否值得這些錢或者是舊的beta版?

編輯: VS產品和說明的包括代碼覆蓋率 http://www.microsoft.com/visualstudio/en-us/products/teamsystem/default.mspx

TestDriven.NET(http://testdriven.net/)如果你的VS版本不支持它可以使用。

回答

14

是的,您可以從Visual Studio中找到代碼覆蓋率信息,前提是您擁有提供該功能的Visual Studio版本,例如Team System。 在VS.NET中設置單元測試時,將創建並添加localtestrun.testrunco​​nfig文件作爲解決方案的一部分。雙擊該文件並在對話框左側找到選項代碼覆蓋選項。選擇要爲其收集代碼覆蓋率信息的程序集,然後重新運行單元測試。代碼覆蓋率信息將被收集並可用。要獲取代碼覆蓋率信息,請打開測試結果窗口,然後單擊代碼覆蓋率結果按鈕,該按鈕將打開一個包含結果的替代窗口。

13

MSTest包含代碼覆蓋率,至少它在我的VS版本中有。但是,您需要在testrunco​​nfig中啓用該工具,該工具只是醜陋的,並且是主要的PITA。

更簡單的選擇是使用TestDriven.NET,它可以自動覆蓋,即使是MSTest。而且由於它使用了MSTest核心,所以你仍然可以獲得所有VS的優點,如着色(覆蓋代碼的紅色/藍色線條)。見here(包括截屏),或者由於圖像勝過千言萬語:

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

6

對於未來的讀者:

哇,這是不好玩。我希望這可以幫助那些在互聯網領域的人。

請注意,「CodeCoverage.exe」的存在可能取決於您擁有的Visual Studio版本。你可能需要在構建服務器上安裝VS(某些增強版本)。

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" 

您還可以在3個UnitTests.dlls合併成一個呼叫

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

我還發現,在互聯網上的一些XSL。 (以下3個環節都是大同小異的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

我在這裏張貼了xsl 「以防萬一」,在未來的URL的模。 將下面的xsl放入名爲「VSCoverageToHtml.xsl」的文件(如上所述)。

<?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> 

這是一個小型的命令行工具來幫助。

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; 
     } 
    } 
} 

另見:

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