MSTest Code Coverage

前端 未结 3 1960
-上瘾入骨i
-上瘾入骨i 2020-12-11 00:16

Is there a way to test code coverage within visual studio if I\'m using MSTest? Or do I have to buy NCover?

Is the NCover Enterprise worth the money or are the old

相关标签:
3条回答
  • 2020-12-11 00:56

    (Note, this answer (here/below) is for DotNet FRAMEWORK. I've created a dotnet-core answer here : How to get code coverage report in donetcore 2 application )

    ...............................

    For future readers:

    Wow, this was NOT fun. I hope this helps someone out there in internet land.

    Please note that the existence of "CodeCoverage.exe" may depend on the version of Visual Studio you have. And you may have to install VS (some enhanced version) in the build server.

    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"
    

    You can also combine the 3 UnitTests.dlls into one call

    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

    I also found some xsl in the internet. (the 3 links below are pretty much the same 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

    I'm posting the xsl here "just in case" those URL's die in the future. Put the below xsl in a file called "VSCoverageToHtml.xsl" (as referenced above).

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

    Here is a small command line tool to help.

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

    Also see:

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

    0 讨论(0)
  • 2020-12-11 01:05

    MSTest includes code coverage, at least it does in the version of VS I have. However, you need to enable the instrumentation in the testrunconfig, which is just ugly and a major PITA.

    A much easier option is to use TestDriven.NET, which can automate coverage, even for MSTest. And since it uses the MSTest core, you still get all the VS goodness such as colorization (red/blue lines for covered code). See here (including a screencast), or since an image says a thousand words:


    (source: mutantdesign.co.uk)

    0 讨论(0)
  • 2020-12-11 01:08

    Yes, you can find code coverage information from within Visual Studio, provided that you have a version of Visual Studio that provides that functionality, such as the Team System. When setting up the unit tests in VS.NET, a localtestrun.testrunconfig file will be created and added as part of the solution. Double-click this file and find the option Code Coverage option on the left of the dialog. Select the assemblies for which you want to collect code coverage information and then re-run the unit tests. Code coverage information will be collected and is available. To get the code coverage information open the test results window and click on the code coverage results button, which will open an alternative window with the results.

    0 讨论(0)
提交回复
热议问题