MSTest代码覆盖范围

时间:2009-01-06 05:29:19

标签: c# .net testing tdd code-coverage

如果我正在使用MSTest,有没有办法测试visual studio中的代码覆盖率?或者我必须购买NCover?

如果微软没有提供内置工具来进行代码覆盖,那么NCover Enterprise是否物有所值,或者旧的测试版是否足够好?

编辑: VS产品的描述以及哪些包括代码覆盖 https://www.visualstudio.com/vs/compare/

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

4 个答案:

答案 0 :(得分:14)

是的,您可以在Visual Studio中找到代码覆盖率信息,前提是您拥有提供该功能的Visual Studio版本,例如Team System。 在VS.NET中设置单元测试时,将创建localtestrun.testrunco​​nfig文件并将其作为解决方案的一部分添加。双击此文件,找到对话框左侧的“代码覆盖率”选项。选择要为其收集代码覆盖率信息的程序集,然后重新运行单元测试。代码覆盖率信息将被收集并可用。要获取代码覆盖率信息,请打开测试结果窗口,然后单击代码覆盖率结果按钮,这将打开一个包含结果的备用窗口。

答案 1 :(得分:13)

MSTest包括代码覆盖率,至少在我拥有的VS版本中。但是,您需要在testrunco​​nfig中启用检测,这只是丑陋而且是主要的PITA。

更简单的选择是使用TestDriven.NET,即使对于MSTest,也可以自动覆盖。而且由于它使用了MSTest核心,你仍然可以获得所有的VS优点,例如着色(覆盖代码的红/蓝线)。请参阅here(包括截屏视频),或者因为图片上写了千言万语:

alt text
(来源:mutantdesign.co.uk

答案 2 :(得分:7)

对于未来的读者:

哇,这不好玩。我希望这可以帮助那些在互联网上的人。

请注意" 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"以防万一"这些网址将来会消亡。 将下面的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

答案 3 :(得分:0)

如果您没有Visual Studio终极版,您还可以使用此MSBuild任务生成代码覆盖率报告。

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