Agile C++

使用 Visual Studio 和 TFS 进行 Agile C++ 开发和测试

John Socha-Leialoha

下载代码示例

您是致力于在 Visual C++ 中构建的应用程序的开发人员和测试人员。 作为一名开发人员,如果您能够提高工作效率,编写较高质量的代码,并能够根据需要重写代码以改善体系结构,而不必担心妨碍任何内容,岂不是很好? 作为一名测试人员,您是否希望花更少的时间来编写和维护测试,以便有时间进行其他测试活动?

在本文中,我将介绍我们 Microsoft 团队构建应用程序时所使用的一些技术。

我们的团队相当小。 我们有 10 个人同时处理 3 个不同的项目。 这些项目以 C# 和 C++ 编写。 C++ 代码大多用于必须在 Windows PE(Windows 的精简版)中运行的程序,其中 Windows PE 通常用于操作系统安装。 它还用作 Microsoft System Center Configuration Manager 任务序列的一部分,以运行无法在完整操作系统中运行的任务,例如将硬盘捕获到虚拟硬盘 (VHD) 文件。 对于小团队而言,需要做很多的工作,因此我们需要提高工作效率。

我们的团队使用 Visual Studio 2010 和 Team Foundation Server (TFS) 2010。 我们使用 TFS 2010 来进行版本控制、工作跟踪、持续集成、代码覆盖率收集和报告。

我们团队编写测试的时机和原因

首先,我将介绍我们团队编写测试的原因(答案可能与您团队的答案有所不同)。 对于我们的开发人员和测试人员,特定答案可能稍有不同,但也许不像您最初想象中那般不同。 作为开发人员,以下是我的目标:

  • 无构建破坏
  • 无回归
  • 自信地进行重构
  • 自信地修改体系结构
  • 通过测试驱动开发 (TDD) 来推动设计

当然,质量是这些目标背后的重要“原因”。 实现这些目标后,开发人员的生活将比其不是开发人员时更加高效和有趣。

对于我们的测试人员,我将仅关注 Agile 测试人员的一个方面:编写自动化测试。 测试人员编写自动化测试时,他们的目标包括无回归、接受驱动开发以及收集和报告代码覆盖率。

当然,我们的测试人员所做的不仅仅是编写自动化测试。 我们的测试人员负责收集代码覆盖率,因为我们希望代码覆盖率数字包含所有测试结果,而不仅仅是单元测试结果(稍后会对此进行详细介绍)。

在本文中,我将介绍我们团队用以实现此处所述目标的不同工具和技术。

使用网关签入来消除构建破坏

过去,我们的团队使用分支来确保测试人员始终拥有用于测试的稳定版本。 但是,维护分支会产生开销。 我们拥有网关签入后,仅使用分支进行发布,这是一个很好的转变。

使用网关签入需要您设置构建控件以及一个或多个构建代理。 我不打算在此对该主题进行介绍,但是您可以在 bit.ly/jzA8Ff 中 MSDN 库页面上的“管理 Team Foundation Build”中找到详细信息。

设置并运行构建代理后,您可以通过在 Visual Studio 中执行以下步骤来创建网关签入的新构建定义:

  1. 在菜单栏中单击“视图”,然后单击“团队资源管理器”,以确保团队资源管理器工具窗口可见。

  2. 展开您的团队项目,然后右键单击“构建”。

  3. 单击“新建构建定义”。

  4. 单击左侧的“触发器”,然后选择“网关签入”,如图 1 所示。

    Select the Gated Check-in Option for Your New Build Definition

    图 1 为您的新建构建定义选择网关签入选项

  5. 单击“构建默认值”,然后选择构建控制器。

  6. 单击“处理”,然后选择要构建的项目。

保存该构建定义后(我们称为“网关签入”),您将会在提交您的签入后看到一个新对话框(请参见图 2)。 单击“构建更改”来创建搁置集并将其提交至构建服务器。 如果没有构建错误,并且通过所有单元测试,则 TFS 将为您签入更改。 否则,它会拒绝签入。

Gated Check-in Dialog Box

图 2 网关签入对话框

网关签入十分有用,因为其可确保您永远不会破坏构建。 它们也能确保通过所有单元测试。 开发人员在签入前,往往很容易忘记运行所有测试。 但是使用网关签入后,这类情况将不再出现。

编写 C++ 单元测试

现在,您已了解如何运行作为网关签入一部分的单元测试,那么我们来看看您为本机 C++ 代码编写这些单元测试的一种方法。

我非常喜欢 TDD,原因如下: 它能帮我关注行为,使我的设计更简单。 我也有一个测试形式的安全网,这些测试定义了行为约定。 我可以重构,而不必担心因不小心违反行为约定而引入错误。 我也了解其他开发人员不会中断他们所不知道的所需行为。

团队中的一名开发人员有方法使用内置测试运行程序 (mstest) 来测试 C++ 代码。 他使用调用本机 C++ DLL 公开的公共函数的 C++/CLI 来编写 Microsoft .NET Framework 单元测试。 我在本部分对该方法进行了进一步的介绍,使您能够直接实例化生产代码内部的本机 C++ 类。 换句话说,除公共接口外,您还可以对其他内容进行测试。

解决方案是将生产代码添加到静态库中,该静态库能链接至单元测试 DLL、生产 EXE 或 DLL 中,如图 3 所示。

图 3 测试和生产通过静态库共享相同代码

设置项目遵循该过程所需的步骤如下所示: 首先创建静态库:

  1. 在 Visual Studio 中,依次单击“文件”、“新建”和“项目”。
  2. 在“已安装的模板”列表中单击“Visual C++”(您将需要展开“其他语言”)。
  3. 单击项目类型列表中的“Win32 项目”。
  4. 输入项目的名称,然后单击“确定”。
  5. 单击“下一步”,单击“静态”库,然后单击“完成”。

现在创建测试 DLL。 设置测试项目还需要执行若干其他步骤。 需要创建项目,并使其能够访问静态库中的代码和头文件。

首先,在“解决方案资源管理器”窗口中右键单击解决方案。 单击“添加”,然后单击“新项目”。 单击模板列表中 Visual C++ 节点下的“测试”。 键入项目名称(我们的团队在项目名称末尾添加了“UnitTests”),然后单击“确定”。

在解决方案资源管理器中,右键单击新项目,然后单击“属性”。 单击左侧树中的“常见属性”。 单击“添加新引用”。 单击“项目”选项卡,选择使用静态库的项目,并单击“确定”来取消“添加引用”对话框。

展开左侧树中的“配置属性”节点,然后展开 C/C++ 节点。 单击 C/C++ 节点下的“常规”。 单击“配置”组合框,然后选择“所有配置”,以确保同时更改“调试”版本和“发布”版本。

单击“附加 Include 库”,然后输入静态库的路径,在这里您需要将静态库名称替换为 MyStaticLib:

$(SolutionDir)\MyStaticLib;%(AdditionalIncludeDirectories)

单击相同属性列表中的“公共语言运行时支持”属性,并将其更改为“公共语言运行时支持(/clr)”。

单击“配置属性”下的“常规”部分,并将“TargetName”属性更改为“$(ProjectName)”。 默认情况下,所有测试项目的该属性被设置为“DefaultTest”,但是它应为您项目的名称。 单击“OK”(确定)。

您将需要重复此过程的第一部分,以将静态库添加到生产 EXE 或 DLL 中。

编写您的第一个单元测试

您现在应该拥有了编写新单元测试所需的所有信息。 您的测试方法将为采用 C++ 编写的 .NET 方法,因此语法将与本机 C++ 稍有不同。 如果您了解 C#,将会发现它在许多方面是 C++ 和 C# 语法的一个混合。 有关详细信息,请参阅 bit.ly/iOKbR0 上的 MSDN 库文档“针对 CLR 的语言功能”。

假设您要测试的类定义如下所示:

#pragma once
class MyClass {
  public:
    MyClass(void);
    ~MyClass(void);

    int SomeValue(int input);
};

现在您需要为 SomeValue 方法编写测试以指定该方法的行为。 图 4 显示了简单单元测试可能的外观,其中显示了整个 .cpp 文件。

图 4 简单单元测试

#include "stdafx.h"
#include "MyClass.h"
#include <memory>
using namespace System;
using namespace Microsoft::VisualStudio::TestTools::UnitTesting;

namespace MyCodeTests {
  [TestClass]
  public ref class MyClassFixture {
    public:
      [TestMethod]
      void ShouldReturnOne_WhenSomeValue_GivenZero() {
        // Arrange
        std::unique_ptr<MyClass> pSomething(new MyClass);
 
        // Act
        int actual = pSomething->SomeValue(0);
 
        // Assert
        Assert::AreEqual<int>(1, actual);
      }
  };
}

如果您对编写单元测试不熟悉,我将使用称为“Arrange,Act,Assert”的模式。 “Arrange”部分设置所要测试方案的前置条件。 “Act”部分为调用您要测试的方法的地方。 “Assert”部分为您检查方法是否按所需方式执行的地方。 我想在每部分前添加一个注释,以增加可读性并简化“Act”部分的查找。

测试方法都以“TestMethod”属性标记,如图 4 所示。 这些方法又必须包含在以“TestClass”属性标记的类中。

请注意,测试方法中的第一行创建了本机 C++ 类的新实例。 我想使用 unique_ptr 标准 C++ 库类来确保该实例会在测试方法结束后自动删除。 因此您可以清楚地看到,您能够将本机 C++ 与您的 CLI/C++ .NET 代码混合。 当然也会存在某些限制,我将在下一部分中对此进行概述。

同样,如果您以前没有编写过 .NET 测试,可以使用 Assert 类提供的很多有用方法来检查不同的条件。 我想使用通用版本来明确我希望从结果获得的数据类型。

请充分利用 C++/CLI 测试

如前所述,当您将本机 C++ 代码和 C++/CLI 代码混合时,需要注意一些限制。 这些不同是由于两个代码库间内存管理的不同而导致的。 本机 C++ 使用 C++ 的新运算符来分配内存,您需要自己释放内存。 分配一部分内存后,您的数据将始终位于同一位置。

另一方面,C++/CLI 代码中的指针会因为该代码从 NET Framework 继承的垃圾收集模型而具有完全不同的行为。 您可以使用 gcnew 运算符而不是新运算符来在 C++/CLI 中创建新 .NET 对象,这将为对象返回对象句柄,而不是指针。 句柄基本上是指向指针的多个指针。 当垃圾收集在内存中移动托管对象时,它会根据新位置对句柄进行更新。

混合托管指针和本机指针时必须非常小心。 我将介绍其中一些差异,并讲述有关针对本机 C++ 对象充分利用 C++/CLI 测试的提示和技巧。

假设您要测试一个方法,其可返回指向字符串的指针。 在 C++ 中,您能够以 LPCTSTR 来表示字符串指针。 但是在 C++/CLI 中,通常以“String^”来表示 .NET 字符串。 类名称后的托字号表示托管对象的句柄。

以下是一个示例,说明如何测试方法调用返回的字符串的值:

// Act
LPCTSTR actual = pSomething->GetString(1);
 
// Assert
Assert::AreEqual<String^>("Test", gcnew String(actual));

最后一行包含所有详细信息。 有一个 AreEqual 方法可接受受管字符串,但是没有针对本机 C++ 字符串的相应方法。 因此,您需要使用受管字符串。 AreEqual 方法的第一个参数是一个受管字符串,因此尽管没有使用 _T 或 L 将其标注为 Unicode 字符串,它实际上也是一个 Unicode 字符串。

String 类有一个接受 C++ 字符串的构造函数,因此您可以创建一个新的受管字符串,其将包含来自待测试方法的实际值,这样 AreEqual 就可确保它们的值相同。

Assert 类有两个极具吸引力的方法:IsNull 和 IsNotNull。 然而,这些方法的参数是句柄而不是对象指针,这意味着您只能将这些方法用于受管对象。 相反,您可以使用 IsTrue 方法,如下所示:

Assert::IsTrue(pSomething != nullptr, "Should not be null");

它能完成同样的工作,但是需要使用更多的代码。 我添加了一个注释,从而使期望在出现在测试结果窗口的消息中清晰明了,如图 5 所示。

Test Results Showing the Additional Comment in the Error Message

图 5 在错误消息中显示附加注释的测试结果

共享设置和拆解代码

应将测试代码视为生产代码。 换句话说,应重构测试和生产代码,以简化测试代码的维护。 在某些情况下,测试类中的所有测试方法可能拥有一些共同的设置和拆解代码。 您可以指定在每项测试前运行的方法,也可以指定在每项测试后运行的方法(您可以使用其中一种,两种或都不使用)。

TestInitialize 属性标记了一种将在测试类中每个测试方法前运行的方法。 同样,TestCleanup 属性标记了一种将在测试类中每个测试方法后运行的方法。 例如:

[TestInitialize]
void Initialize() {
  m_pInstance = new MyClass;
}
 
[TestCleanup]
void Cleanup() {
  delete m_pInstance;
}

MyClass *m_pInstance;

首先,请注意我针对 m_pInstance 使用了一个指向类的简单指针。 为什么我没有使用 unique_ptr 来管理生存期?

同样,答案与混合本机 C++ 和 C++/CLI 有关。 C++/CLI 中的实例变量是托管对象的一部分,因此它们只能是托管对象的句柄以及指向本机对象或值类型的指针。 您需要回到新建和删除基础来管理本机 C++ 实例的生存期。

使用指向实例变量的指针

如果您正在使用 COM,则可能会遇到这种情况,即您希望编写如下代码:

[TestMethod]
Void Test() {
  ...
HRESULT hr = pSomething->GetWidget(&m_pUnk);
  ...
}

IUnknown *m_pUnk;

这将无法编译,并会产生如下错误消息:

无法将参数 1 从“cli::interior_ptr<Type>”转换为“IUnknown **”

在本示例中,C++/CLI 实例变量地址类型为 interior_ptr<IUnknown *>,该类型与本机 C++ 代码不兼容。 您想知道为什么吗? 我仅需要一个指针。

测试类为托管类,因此该类的实例可以通过垃圾收集器在内存中移动。 因此,如果您有指向实例变量的指针,则对象移动后,该指针将无效。

可以在本机调用过程中锁定对象,如下所示:

cli::pin_ptr<IUnknown *> ppUnk = &m_pUnk;
HRESULT hr = pSomething->GetWidget(ppUnk);

第一行锁定实例,直至变量超出范围,这将使您可以将指向实例变量的指针传递给本机 C++,尽管变量包含在托管测试类中。

编写可测试的代码

在本文的开头,我提到了编写可测试代码的重要性。 我使用 TDD 来确保代码具有可测试性,但是某些开发人员更喜欢在编写代码后编写测试。 无论怎么做,我们都不仅要考虑单元测试,同时还要考虑整个测试堆栈,这点非常重要。

Mike Cohn 是 Agile 著名的多产作者,他绘制了测试自动化金字塔,该金字塔提供了有关测试类型的内容以及每层上应进行的测试数。 开发人员应该编写所有或大多数单元和组件测试,并可能编写一些集成测试。 有关该测试金字塔的详细信息,请参阅 Cohn 的博客文章“测试自动化金字塔的遗忘层”(bit.ly/eRZU2p)。

测试人员通常负责编写验收测试和 UI 测试。 这些测试有时也称为端到端测试或 E2E 测试。 在 Cohn 的金字塔中,与其他类型测试的区域相比,UI 三角形是最小的。 这意味着您需要编写尽可能少的自动化 UI 测试。 自动化 UI 测试往往非常脆弱,且编写和维护成本较高。 对 UI 所做的细小更改极易破坏 UI 测试。

如果您的代码没有编写为可测试代码,则您将可以轻松得到倒金字塔,其中大部分自动化测试为 UI 测试。 这是一种很不好的情况,但底线是开发人员需确保测试人员可以在 UI 下编写集成和验收测试。

此外,无论出于什么原因,我遇到的大部分测试人员都非常熟悉以 C# 编写测试,而回避以 C++ 编写测试。 因此,我们的团队需要在测试和自动化测试下的 C++ 代码之间架起一个桥梁。 桥梁采用装置形式,这些装置为 C++/CLI 类,它们像任何其他管理类一样呈现给 C# 代码。

构建 C# 到 C++ 装置

此处使用的技术与我在编写 C++/CLI 测试中介绍的并没有太大区别。 所有这些技术均使用相同类型的混合模式代码。 区别在于它们最终的用法。

第一步是创建新项目,该项目将包含您的装置:

  1. 在解决方案资源管理器中,右键单击解决方案节点,单击“添加”,然后单击“新项目”。
  2. 在其他语言、Visual C++ 和 CLR 下,单击“类库”。
  3. 输入要勇于该项目的名称,然后单击“确定”。
  4. 重复以上步骤以创建测试项目,从而添加引用和 include 文件。

装置类本身与测试类有些类似,但是不具有各种属性(请参阅图 6)。

图 6 C# 到 C++ 测试装置

#include "stdafx.h"
#include "MyClass.h"
using namespace System;
 
namespace MyCodeFixtures {
  public ref class MyCodeFixture {
    public:
      MyCodeFixture() {
        m_pInstance = new MyClass;
      }
 
      ~MyCodeFixture() {
        delete m_pInstance;
      }
 
      !MyCodeFixture() {
        delete m_pInstance;
      }
 
      int DoSomething(int val) {
        return m_pInstance->SomeValue(val);
      }
 
      MyClass *m_pInstance;
  };
}

请注意,没有头文件! 这是我最喜欢的 C++/CLI 功能之一。 因为该类库构建了托管程序集,并且有关类的信息存储为 .NET 类型的信息,因此您不需要头文件。

该类也包含了析构函数和终结器。 此处的析构函数并不是真正的析构函数。 而在 IDisposable 接口中,编译器会将析构函数重新编写为 Dispose 方法的实现。 因此,任何拥有析构函数的 C++/CLI 类都会实现 IDisposable 接口。

!MyCodeFixture 方法为终结器,垃圾收集器决定释放该对象时会调用该方法,除非您之前已调用 Dispose 方法。 您既可以采用 using 语句来控制嵌入式本机 C++ 对象的生存期,也可以让垃圾收集器来处理生存期。 有关此行为的详细信息,请访问bit.ly/kW8knr 上的 MSDN 库文章“析构函数语义更改”。

拥有 C++/CLI 装置类后即可编写类似图 7 的 C# 单元测试。

图 7 C# 单元测试系统

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyCodeFixtures;
 
namespace MyCodeTests2 {
  [TestClass]
  public class UnitTest1 {
    [TestMethod]
    public void TestMethod1() {
      // Arrange
      using (MyCodeFixture fixture = new MyCodeFixture()) {
        // Act
        int result = fixture.DoSomething(1);
 
        // Assert
        Assert.AreEqual<int>(1, result);
      }
    }
  }
}

我想使用 using 语句来明确控制装置对象的生存期,而不是依赖垃圾收集器。 这在测试方法中尤为重要,它能确保测试不与其他测试交互。

捕获和报告代码覆盖率

我在本文开头处介绍的最后一部分为代码覆盖率。 我们团队的目标是使代码覆盖率能自动被构建服务器捕获,能发布至 TFS 且易于获取。

第一步是了解如何从运行的测试中捕获 C++ 代码覆盖率。 在 Web 上搜索时,我找到了 Emil Gustafsson 的一篇信息丰富的博客文章,标题为“使用 Visual Studio 2008 Team System 的本机 C++ 代码覆盖率报告”(bit.ly/eJ5cqv)。 此文章显示了捕获代码覆盖率信息所需的步骤。 我将其转换为 CMD 文件,这样就可以随时在我的开发计算机上运行该文件,以捕获代码覆盖率信息:

"%VSINSTALLDIR%\Team Tools\Performance Tools\vsinstr.exe" Tests.dll /COVERAGE
"%VSINSTALLDIR%\Team Tools\Performance Tools\vsperfcmd.exe" /START:COVERAGE /WaitStart /OUTPUT:coverage
mstest /testcontainer:Tests.dll /resultsfile:Results.trx
"%VSINSTALLDIR%\Team Tools\Performance Tools\vsperfcmd.exe" /SHUTDOWN

您将需要用包含测试的 DLL 实际名称替换 Tests.dll。 还需要准备要检测的 DLL:

  1. 在解决方案资源管理器窗口中,右键单击测试项目。
  2. 单击“属性”。
  3. 选择“调试配置”。
  4. 展开“配置属性”,然后展开“链接器”并单击“高级”。
  5. 将“配置文件”属性更改为“是(/PROFILE)”。
  6. 单击“OK”(确定)。

这些步骤启用了分析,您需要启用该功能以实现程序集,从而捕获代码覆盖率信息。

重构您的项目并运行 CMD 文件。 这将创建一个覆盖率文件。 将该覆盖率文件加载至 Visual Studio,以确保可以从测试中捕获代码覆盖率。

在构建服务器上执行这些步骤并将结果发布至 TFS 需要一个自定义构建模板。 TFS 构建模板存储在版本控制中,并且它们属于特定团队项目。 您将在每个团队项目下找到名为“BuildProcessTemplates”的文件夹,该文件夹很可能拥有多个构建模板。

若要使用下载中包含的自定义构建模板,请打开“源代码控制资源管理器”窗口。 导航到团队项目中的“BuildProcessTemplates”文件夹,并确保已将其映射到您计算机的一个目录。 将 BuildCCTemplate.xaml 文件复制到该映射位置。 将该模板添加至源代码控制并将其签入。

必须先将模板文件签入,这样才可在构建定义时使用这些文件。

现在您已签入构建模板,可以创建构建定义来运行代码覆盖率。 可以使用 vsperfmd 命令来收集 C++ 代码覆盖率,如前所示。 Vsperfmd 会侦听所有检测的可执行文件的代码覆盖率信息,这些可执行文件会随着 Vsperfmd 的运行而运行。 因此,您不希望其他的检测测试同时运行。 您也应该确保仅有一个构建代理在计算机上运行,该构建代理将处理这些代码覆盖率的运行。

我创建了一个会在夜间运行的构建定义。 您可以通过以下步骤达到这一目的:

  1. 在“团队资源管理器”窗口中,展开您团队项目的节点。
  2. 右键单击“构建”,其为您团队项目下的一个节点。
  3. 单击“新建构建定义”。
  4. 在“触发器”部分,单击“计划”并选择要运行代码覆盖率的时间。
  5. 在“处理”部分,单击调用顶部构建过程模板部分中的“显示详细信息”,然后选择签入到源代码控制中的构建模板。
  6. 填写其他必填部分并保存。

添加测试设置文件

构建定义也需要测试设置文件。 其为 XML 文件,其中列出了要为其捕获并发布结果的 DLL。 以下是为代码覆盖率设置该文件的步骤:

  1. 双击 Local.testsettings 文件,然后打开“测试设置”对话框。
  2. 单击左侧列表中的“数据”和“诊断”。
  3. 单击“代码覆盖率”并选中复选框。
  4. 单击列表上的“配置”按钮。
  5. 选中包含您的测试(也包含测试所测试的代码)的 DLL 旁的框。
  6. 取消选中准备就绪的检测程序集,因为构建定义将处理该问题。
  7. 单击“确定”和“应用”,然后单击“关闭”。

如果您想构建多个解决方案或者有多个测试项目,则您将需要复制一个测试设置文件,其包含应监视其代码覆盖率的所有程序集的名称。

若要完成此任务,请将测试设置文件复制到您分支的根目录下,并给定一个描述性名称,如 CC.testsettings。 编辑 XML。 文件将至少包含来自上述步骤中的一个 CodeCoverageItem 元素。 您需要为要捕获的每个 DLL 添加一个条目。 请注意,路径与项目文件的位置而不是测试设置文件的位置相关。 将该文件签入源代码控制中。

最后,您需要修改构建定义来使用以下测试设置文件:

  1. 在“团队资源管理器”窗口中,展开您团队项目的节点,然后展开“构建”。
  2. 双击之前创建的构建定义。
  3. 单击“编辑构建定义”。
  4. 在“处理”部分,展开“自动化测试”,然后展开 1。 测试程序集并单击“TestSettings”文件。 单击“…”按钮,然后选择我们之前创建的测试设置文件。
  5. 保存更改。

您可以通过右键单击并选择“排队新构建”来测试该构建定义,从而立即启动新构建。

报告代码覆盖率

我创建了自定义 SQL Server Reporting Services 报告,其显示了代码覆盖率,如图 8 所示(我模糊了实际项目的名称以防止犯错)。 该报告使用 SQL 查询来读取 TFS 仓库中的数据,并显示 C++ 和 C# 代码的组合结果。

The Code-Coverage Report

图 8 代码覆盖率报告

在此我将不详述此报告的工作方式,但是我想提醒您注意几个方面。 出于以下两个原因,数据库包含了来自 C++ 代码覆盖率的太多信息:测试方法代码包括在结果中,标准 C++ 库(在头文件中)包括在结果中。

我将代码添加至 SQL 查询,其将筛选出此额外数据。 如果您看一下报告中的 SQL,就会发现这一点:

and CodeElementName not like 'std::%'
and CodeElementName not like 'stdext::%'
and CodeElementName not like '`anonymous namespace'':%'
and CodeElementName not like '_bstr_t%'
and CodeElementName not like '_com_error%'
and CodeElementName not like '%Tests::%'

这些行排除了特定命名空间(std、stdext 和匿名)的代码覆盖率结果、Visual C++ 附带的一些类(_bstr_t 和 _com_error)以及以“Tests”结尾的命名空间中的任何代码。

后者排除了以“Tests”结尾的命名空间,从而排除了测试类中的所有方法。 创建新测试项目时,因为项目名称以“Tests”结尾,因此,默认情况下所有测试类都位于以“Tests”结尾的命名空间中。 您可以在此处添加您希望排除的其他类或命名空间。

我只是简要介绍了您可以进行的操作 - 请随时跟进我的博客进度,网址为 blogs.msdn.com/b/jsocha。           

John Socha-Leialoha 是 Microsoft 管理平台和服务提供小组中的开发人员。他过去的成就包括编写 Norton Commander(在 C 和装配器中)以及编写《Peter Norton 的汇编语言书》(Brady,1987)。

衷心感谢以下技术专家对本文的审阅:Rong Lu