自定义代码覆盖率分析

默认情况下,代码覆盖率将分析单元测试过程中加载的所有解决方案程序集。 建议使用此默认行为,因为它大多数时间很有用。 有关详细信息,请参阅使用代码覆盖率确定正在测试的代码数量

若要从代码覆盖率结果中排除测试代码,仅包括应用程序代码,请将 ExcludeFromCodeCoverageAttribute 属性添加到测试类。

若要包含不属于解决方案的程序集,请获取这些程序集的 .pdb 文件并将其复制到与程序集 .dll 文件相同的文件夹 。

运行设置文件

运行设置文件是由单元测试工具使用的配置文件。 在 .runsettings 文件中指定高级代码覆盖率设置。

若要自定义代码覆盖率,请执行以下步骤:

  1. 将运行设置文件添加到解决方案中。 在“解决方案资源管理器”的解决方案快捷菜单上,依次选择“添加”>“新建项”和“XML 文件” 。 保存带有类似于 CodeCoverage.runsettings 的名称的文件。

    如果看不到所有项模板,请选择“显示所有模板”,然后选择项模板。

  2. 添加本文结尾处示例文件中的内容,然后按需进行自定义,如以下章节所述。

  3. 选择运行设置文件。

    从 Visual Studio 2019 版本 16.4 开始,可以在项目根中自动检测运行设置文件。 否则,在“测试”菜单上,选择“配置运行设置”,然后选择“选择解决方案范围的运行设置文件”。 若要指定运行设置文件以从命令行中运行测试,请参阅配置单元测试

    选择“分析代码覆盖率”时,会从运行设置文件读取配置信息。

    提示

    运行测试或更新代码时,之前的任何代码覆盖率结果和代码着色都不会自动隐藏。

    若要禁用和启用自定义设置,请在“测试”菜单中取消选择或选择该文件。

    若要选择运行设置文件,在“测试”菜单上,选择“选择设置文件”。 若要指定运行设置文件以从命令行中运行测试,请参阅配置单元测试

    选择“分析代码覆盖率”时,会从运行设置文件读取配置信息。

    提示

    运行测试或更新代码时,之前的任何代码覆盖率结果和代码着色都不会自动隐藏。

    若要关闭和打开自定义设置,请选择“测试”、“配置运行设置”,然后取消选择或选择文件名。

符号搜索路径

代码覆盖率需要程序集的符号文件(.pdb 文件)。 对于解决方案生成的程序集,符号文件通常与二进制文件一起出现,且代码覆盖率将自动工作。 在某些情况下,你可能需要在你的代码覆盖率分析中包含引用的程序集。 在这种情况下,.pdb 文件可能不会与二进制文件相邻,但可在 .runsettings 文件中指定符号搜索路径 。

<SymbolSearchPaths>
      <Path>\\mybuildshare\builds\ProjectX</Path>
      <!--More paths if required-->
</SymbolSearchPaths>

注意

符号解析可能很耗时,尤其是在使用包含大量程序集的远程文件位置时。 因此,请考虑将 .pdb 文件复制到与二进制文件(.dll 和 .exe)相同的本地位置 。

包括或排除程序集和成员

可在代码覆盖范围分析中包括或排除程序集或特定类型和成员。 如果“包括”部分为空或省略,则包括已加载并具有关联 PDB 文件的所有程序集。 如果程序集或成员与“排除”部分中的子句匹配,则将其从代码覆盖范围中排除。 “排除”部分优先于“包括”部分:如果程序集同时列于“包括”和“排除”中,则不包括在代码覆盖范围中 。

例如,下面的 XML 通过指定程序集的名称将一个程序集排除:

<ModulePaths>
  <Exclude>
   <ModulePath>.*Fabrikam.Math.UnitTest.dll</ModulePath>
   <!-- Add more ModulePath nodes here. -->
  </Exclude>
</ModulePaths>

下面的示例指定在代码覆盖范围中只应包括一个程序集:

<ModulePaths>
  <Include>
   <ModulePath>.*Fabrikam.Math.dll</ModulePath>
   <!-- Add more ModulePath nodes here. -->
  </Include>
</ModulePaths>

下表显示各种匹配程序集和成员的方式,使其在代码覆盖范围中包括或排除。

XML 元素 匹配项
ModulePath 匹配程序集名称或文件路径指定的程序集。
CompanyName 按“公司”特性匹配程序集。
PublicKeyToken 按公钥标记匹配签名程序集。
按在其中定义元素的源文件的路径名称匹配元素。
特性 匹配具有指定特性的元素。 指定属性的完整名称,例如 <Attribute>^System\.Diagnostics\.DebuggerHiddenAttribute$</Attribute>

如果排除 CompilerGeneratedAttribute 属性,将从代码覆盖率分析中排除使用语言功能(如 asyncawaityield return 和自动实现的属性)的代码。 要排除真正生成的代码,只需排除 GeneratedCodeAttribute 属性。
函数 按完全限定的名称匹配过程、函数或方法,包括参数列表。 还可以使用正则表达式来匹配部分名称。

示例:

Fabrikam.Math.LocalMath.SquareRoot(double); (C#)

Fabrikam::Math::LocalMath::SquareRoot(double) (C++)

代码覆盖率格式

默认情况下,代码覆盖率收集并保存在 .coverage 文件中。 还可以使用其他格式(包括 Xml 和 Cobertura)收集覆盖率。 在不同编辑器和管道中使用不同格式可能很有用。 可以通过在 runsettings 文件的 DataCollector 配置部分中添加 <Format>Cobertura</Format><Format>Xml</Format> 在 runsettings 中启用此功能。 可以在 Visual Studio Enterprise 的代码覆盖率结果窗口中查看此格式。

还可以通过在 runsettings 文件中指定或在一个参数中指定,从命令行中指定不同的格式。 例如,dotnet 命令行使用 dotnet test --collect:"Code Coverage;Format=Cobertura"。 对于 vstest,请使用 vstest.console.exe /collect:"Code Coverage;Format=Cobertura"。 collect 参数将覆盖 runsettings 中指定的格式。

静态和动态本机检测

在 Visual Studio 2022 版本 17.2 中,我们(在磁盘上)添加了静态检测本机二进制文件的选项。 以前的版本中仅支持动态检测,这通常无法检测方法。 静态本机检测更稳定,建议使用。 静态本机检测需要为需要收集代码覆盖率的所有本机项目启用 /PROFILE 链接选项。

可以通过在“工具”>“选项”>“环境”>“预览功能”中启用预览功能“代码覆盖率本机静态检测”来启用本机静态检测。

还可以通过在 <CodeCoverage> 标记下添加 <EnableStaticNativeInstrumentation>True</EnableStaticNativeInstrumentation>,在 runsettings 中启用本机静态检测。 将此方法用于命令行方案。

默认情况下,始终启用动态本机检测。 如果同时启用静态和动态检测,Visual Studio 会尝试静态检测 C++ 代码,但如果不支持此操作(例如,当 /PROFILE 链接选项未启用时),将使用动态检测。 可以通过在 <CodeCoverage> 下添加 <EnableDynamicNativeInstrumentation>False</EnableDynamicNativeInstrumentation>,在 runsettings 中完全禁用动态本机检测。

启用静态本机检测后,将在测试执行之前在磁盘上检测和替换本机二进制文件。 测试执行后,将还原原始二进制文件。 可以通过在 <CodeCoverage> 标记下添加 <EnableStaticNativeInstrumentationRestore>False</EnableStaticNativeInstrumentationRestore>,来禁用在 runsettings 中还原原始文件。 这在 CI 方案中特别有用。

启用静态本机检测后,Visual Studio 将在测试二进制文件所在的目录中搜索和检测所有本机二进制文件。 可以指定应在其中搜索二进制文件的其他目录。 以下示例指定应检测来自 C:\temp 及其子目录的所有本机二进制文件,但以 Fabrikam.Math.dll 结尾的文件除外。

<ModulePaths>
  <IncludeDirectories>
    <Directory Recursive="true">C:\temp</Directory>
  </IncludeDirectories>
  <Exclude>
    <ModulePath>.*Fabrikam.Math.dll</ModulePath>
  </Exclude>
</ModulePaths>

正则表达式

Include 和 exclude 节点使用正则表达式,它们与通配符不同。 所有匹配项都不区分大小写。 下面是一些示例:

  • .* 与包含任意字符的字符串匹配

  • \. 与句点“.”匹配

  • \( \) 与括号“( )”匹配

  • \\ 与文件路径分隔符“\”匹配

  • ^ 与字符串的开头匹配

  • $ 与字符串的结尾匹配

下面的 XML 演示如何使用正则表达式来包括和排除特定程序集:

<ModulePaths>
  <Include>
    <!-- Include all loaded .dll assemblies (but not .exe assemblies): -->
    <ModulePath>.*\.dll$</ModulePath>
  </Include>
  <Exclude>
    <!-- But exclude some assemblies: -->
    <ModulePath>.*\\Fabrikam\.MyTests1\.dll$</ModulePath>
    <!-- Exclude all file paths that contain "Temp": -->
    <ModulePath>.*Temp.*</ModulePath>
  </Exclude>
</ModulePaths>

下面的 XML 演示如何使用正则表达式来包括和排除特定函数:

<Functions>
  <Include>
    <!-- Include methods in the Fabrikam namespace: -->
    <Function>^Fabrikam\..*</Function>
    <!-- Include all methods named EqualTo: -->
    <Function>.*\.EqualTo\(.*</Function>
  </Include>
  <Exclude>
    <!-- Exclude methods in a class or namespace named UnitTest: -->
    <Function>.*\.UnitTest\..*</Function>
  </Exclude>
</Functions>

警告

如果正则表达式中存在错误(如存在未转义或不匹配的括号),则不会运行代码覆盖率分析。

有关正则表达式的详细信息,请参阅在 Visual Studio 中使用正则表达式

示例 .runsettings 文件

复制此代码并对其进行编辑以满足需求。

<?xml version="1.0" encoding="utf-8"?>
<!-- File name extension must be .runsettings -->
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
        <Configuration>
          <CodeCoverage>
<!--
Additional paths to search for .pdb (symbol) files. Symbols must be found for modules to be instrumented.
If .pdb files are in the same folder as the .dll or .exe files, they are automatically found. Otherwise, specify them here.
Note that searching for symbols increases code coverage runtime. So keep this small and local.
-->
<!--
            <SymbolSearchPaths>
                   <Path>C:\Users\username\source\repos\ProjectX</Path>
                   <Path>\\mybuildshare\builds\ProjectX</Path>
            </SymbolSearchPaths>
-->

<!--
About include/exclude lists:
Empty "Include" clauses imply all; empty "Exclude" clauses imply none.
Each element in the list is a regular expression (ECMAScript syntax). See /visualstudio/ide/using-regular-expressions-in-visual-studio.
An item must first match at least one entry in the include list to be included.
Included items must then not match any entries in the exclude list to remain included.
-->

            <!-- Match assembly file paths: -->
            <ModulePaths>
              <Include>
                <ModulePath>.*\.dll$</ModulePath>
                <ModulePath>.*\.exe$</ModulePath>
              </Include>
              <Exclude>
                <ModulePath>.*CPPUnitTestFramework.*</ModulePath>
              </Exclude>
              <!-- Specifies additional list of directories where binaries static native instrumentation should be searched. -->
              <IncludeDirectories>
                <Directory Recursive="true">C:\b59fb11c-1611-4562-9a2b-c35719da65d3</Directory>
              </IncludeDirectories>
            </ModulePaths>

            <!-- Match fully qualified names of functions: -->
            <!-- (Use "\." to delimit namespaces in C# or Visual Basic, "::" in C++.)  -->
            <Functions>
              <Exclude>
                <Function>^Fabrikam\.UnitTest\..*</Function>
                <Function>^std::.*</Function>
                <Function>^ATL::.*</Function>
                <Function>.*::__GetTestMethodInfo.*</Function>
                <Function>^Microsoft::VisualStudio::CppCodeCoverageFramework::.*</Function>
                <Function>^Microsoft::VisualStudio::CppUnitTestFramework::.*</Function>
              </Exclude>
            </Functions>

            <!-- Match attributes on any code element: -->
            <Attributes>
              <Exclude>
                <!-- Don't forget "Attribute" at the end of the name -->
                <Attribute>^System\.Diagnostics\.DebuggerHiddenAttribute$</Attribute>
                <Attribute>^System\.Diagnostics\.DebuggerNonUserCodeAttribute$</Attribute>
                <Attribute>^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$</Attribute>
                <Attribute>^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$</Attribute>
              </Exclude>
            </Attributes>

            <!-- Match the path of the source files in which each method is defined: -->
            <Sources>
              <Exclude>
                <Source>.*\\atlmfc\\.*</Source>
                <Source>.*\\vctools\\.*</Source>
                <Source>.*\\public\\sdk\\.*</Source>
                <Source>.*\\microsoft sdks\\.*</Source>
                <Source>.*\\vc\\include\\.*</Source>
              </Exclude>
            </Sources>

            <!-- Match the company name property in the assembly: -->
            <CompanyNames>
              <Exclude>
                <CompanyName>.*microsoft.*</CompanyName>
              </Exclude>
            </CompanyNames>

            <!-- Match the public key token of a signed assembly: -->
            <PublicKeyTokens>
              <!-- Exclude Visual Studio extensions: -->
              <Exclude>
                <PublicKeyToken>^B77A5C561934E089$</PublicKeyToken>
                <PublicKeyToken>^B03F5F7F11D50A3A$</PublicKeyToken>
                <PublicKeyToken>^31BF3856AD364E35$</PublicKeyToken>
                <PublicKeyToken>^89845DCD8080CC91$</PublicKeyToken>
                <PublicKeyToken>^71E9BCE111E9429C$</PublicKeyToken>
                <PublicKeyToken>^8F50407C4E9E73B6$</PublicKeyToken>
                <PublicKeyToken>^E361AF139669C375$</PublicKeyToken>
              </Exclude>
            </PublicKeyTokens>

            <!-- We recommend you do not change the following values: -->

            <!-- Set this to True to collect coverage information for functions marked with the "SecuritySafeCritical" attribute. Instead of writing directly into a memory location from such functions, code coverage inserts a probe that redirects to another function, which in turns writes into memory. -->
            <UseVerifiableInstrumentation>True</UseVerifiableInstrumentation>
            <!-- When set to True, collects coverage information from child processes that are launched with low-level ACLs, for example, UWP apps. -->
            <AllowLowIntegrityProcesses>True</AllowLowIntegrityProcesses>
            <!-- When set to True, collects coverage information from child processes that are launched by test or production code. -->
            <CollectFromChildProcesses>True</CollectFromChildProcesses>
            <!-- When set to True, restarts the IIS process and collects coverage information from it. -->
            <CollectAspDotNet>False</CollectAspDotNet>
            <!-- When set to True, static native instrumentation will be enabled. -->
            <EnableStaticNativeInstrumentation>True</EnableStaticNativeInstrumentation>
            <!-- When set to True, dynamic native instrumentation will be enabled. -->
            <EnableDynamicNativeInstrumentation>True</EnableDynamicNativeInstrumentation>
            <!-- When set to True, instrumented binaries on disk are removed and original files are restored. -->
            <EnableStaticNativeInstrumentationRestore>True</EnableStaticNativeInstrumentationRestore>

          </CodeCoverage>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>
<?xml version="1.0" encoding="utf-8"?>
<!-- File name extension must be .runsettings -->
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
        <Configuration>
          <CodeCoverage>
<!--
Additional paths to search for .pdb (symbol) files. Symbols must be found for modules to be instrumented.
If .pdb files are in the same folder as the .dll or .exe files, they are automatically found. Otherwise, specify them here.
Note that searching for symbols increases code coverage runtime. So keep this small and local.
-->
<!--
            <SymbolSearchPaths>
                   <Path>C:\Users\username\source\repos\ProjectX</Path>
                   <Path>\\mybuildshare\builds\ProjectX</Path>
            </SymbolSearchPaths>
-->

<!--
About include/exclude lists:
Empty "Include" clauses imply all; empty "Exclude" clauses imply none.
Each element in the list is a regular expression (ECMAScript syntax). See /visualstudio/ide/using-regular-expressions-in-visual-studio.
An item must first match at least one entry in the include list to be included.
Included items must then not match any entries in the exclude list to remain included.
-->

            <!-- Match assembly file paths: -->
            <ModulePaths>
              <Include>
                <ModulePath>.*\.dll$</ModulePath>
                <ModulePath>.*\.exe$</ModulePath>
              </Include>
              <Exclude>
                <ModulePath>.*CPPUnitTestFramework.*</ModulePath>
              </Exclude>
              <!-- Specifies additional list of directories where binaries static native instrumentation should be searched. -->
              <IncludeDirectories>
                <Directory Recursive="true">C:\b59fb11c-1611-4562-9a2b-c35719da65d3</Directory>
              </IncludeDirectories>
            </ModulePaths>

            <!-- Match fully qualified names of functions: -->
            <!-- (Use "\." to delimit namespaces in C# or Visual Basic, "::" in C++.)  -->
            <Functions>
              <Exclude>
                <Function>^Fabrikam\.UnitTest\..*</Function>
                <Function>^std::.*</Function>
                <Function>^ATL::.*</Function>
                <Function>.*::__GetTestMethodInfo.*</Function>
                <Function>^Microsoft::VisualStudio::CppCodeCoverageFramework::.*</Function>
                <Function>^Microsoft::VisualStudio::CppUnitTestFramework::.*</Function>
              </Exclude>
            </Functions>

            <!-- Match attributes on any code element: -->
            <Attributes>
              <Exclude>
                <!-- Don't forget "Attribute" at the end of the name -->
                <Attribute>^System\.Diagnostics\.DebuggerHiddenAttribute$</Attribute>
                <Attribute>^System\.Diagnostics\.DebuggerNonUserCodeAttribute$</Attribute>
                <Attribute>^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$</Attribute>
                <Attribute>^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$</Attribute>
              </Exclude>
            </Attributes>

            <!-- Match the path of the source files in which each method is defined: -->
            <Sources>
              <Exclude>
                <Source>.*\\atlmfc\\.*</Source>
                <Source>.*\\vctools\\.*</Source>
                <Source>.*\\public\\sdk\\.*</Source>
                <Source>.*\\microsoft sdks\\.*</Source>
                <Source>.*\\vc\\include\\.*</Source>
              </Exclude>
            </Sources>

            <!-- Match the company name property in the assembly: -->
            <CompanyNames>
              <Exclude>
                <CompanyName>.*microsoft.*</CompanyName>
              </Exclude>
            </CompanyNames>

            <!-- Match the public key token of a signed assembly: -->
            <PublicKeyTokens>
              <!-- Exclude Visual Studio extensions: -->
              <Exclude>
                <PublicKeyToken>^B77A5C561934E089$</PublicKeyToken>
                <PublicKeyToken>^B03F5F7F11D50A3A$</PublicKeyToken>
                <PublicKeyToken>^31BF3856AD364E35$</PublicKeyToken>
                <PublicKeyToken>^89845DCD8080CC91$</PublicKeyToken>
                <PublicKeyToken>^71E9BCE111E9429C$</PublicKeyToken>
                <PublicKeyToken>^8F50407C4E9E73B6$</PublicKeyToken>
                <PublicKeyToken>^E361AF139669C375$</PublicKeyToken>
              </Exclude>
            </PublicKeyTokens>

            <!-- We recommend you do not change the following values: -->

            <!-- Set this to True to collect coverage information for functions marked with the "SecuritySafeCritical" attribute. Instead of writing directly into a memory location from such functions, code coverage inserts a probe that redirects to another function, which in turns writes into memory. -->
            <UseVerifiableInstrumentation>True</UseVerifiableInstrumentation>
            <!-- When set to True, collects coverage information from child processes that are launched with low-level ACLs, for example, UWP apps. -->
            <AllowLowIntegrityProcesses>True</AllowLowIntegrityProcesses>
            <!-- When set to True, collects coverage information from child processes that are launched by test or production code. -->
            <CollectFromChildProcesses>True</CollectFromChildProcesses>
            <!-- When set to True, restarts the IIS process and collects coverage information from it. -->
            <CollectAspDotNet>False</CollectAspDotNet>

          </CodeCoverage>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>