如果使用MSTest,是否可以在Visual Studio中测试代码覆盖率?还是我必须购买NCover?
如果微软不提供内置的工具来进行代码覆盖,NCover Enterprise是否值得花这笔钱,或者旧的beta是否足够好?
编辑:VS产品的说明,其中包括代码覆盖范围https://www.visualstudio.com/vs/compare/
如果您的VS版本不支持,则可以使用[TestDriven.NET(http://testdriven.net/)。
是的,只要您具有提供该功能的Visual Studio版本,例如Team System,就可以在Visual Studio中找到代码覆盖率信息。在VS.NET中设置单元测试时,将创建localtestrun.testrunconfig文件并将其添加为解决方案的一部分。双击该文件,然后在对话框左侧找到选项“代码覆盖率”选项。选择要为其收集代码覆盖率信息的程序集,然后重新运行单元测试。代码覆盖率信息将被收集并可用。要获取代码覆盖率信息,请打开测试结果窗口,然后单击代码覆盖率结果按钮,这将打开一个包含结果的替代窗口。
MSTest包括代码覆盖范围,至少它在我拥有的VS版本中包含。但是,您需要在testrunconfig中启用检测,这很丑陋并且是主要的PITA。
更简单的选择是使用TestDriven.NET,即使对于MSTest,它也可以自动覆盖。并且由于它使用了MSTest内核,因此您仍然可以获得所有VS优势,例如着色(覆盖代码的红/蓝线)。请参阅here(包括屏幕录像),或者由于图像说出一千个单词:
(来源:mutantdesign.co.uk)
((此答案(在此/下)是针对DotNet FRAMEWORK的。我在此处创建了一个dotnet-core答案:How to get code coverage report in donetcore 2 application)] >>
...............................
供将来的读者使用:
哇,这不好玩。我希望这对互联网领域的人有所帮助。
请注意,“ 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.dll合并为一个调用
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
如果没有Visual Studio终极版,也可以使用此MSBuild任务来生成代码覆盖率报告。