用测试资源管理器对本机代码进行单元测试

在 Visual Studio 中,可以创建单元测试用 C++ 编写的非托管代码。 非托管代码有时称为本机代码。

下面的过程包含将获得启动的重要信息。 新节提供更详细地描述的一个演练中。

编写单元测试非托管代码 DLL

  1. 使用 本机测试项目 模板创建自己的一个单独的 Visual Studio 项目测试。

    该项目包含一些示例测试代码。

  2. 使 DLL 可访问的测试项目:

    • #include 包含 DLL 的外部访问的函数的声明的 .h 文件。

      .h 文件应包含函数声明标有 _declspec(dllimport)。 或者,使用 DEF 文件,可以导出方法。 有关更多信息,请参见导入和导出

      您的单元测试可以从 DLL 导出测试只能访问功能。

    • 添加 DLL 项目到引用测试项目:

      在测试项目中 属性,展开 通用属性框架和引用,然后选择 添加引用

  3. 在测试项目中,请创建测试选件类和测试方法使用测试宏并保持断言选件类:

    #include "stdafx.h"
    #include <CppUnitTest.h>
    #include "..\MyProjectUnderTest\MyCodeUnderTest.h"
    using namespace Microsoft::VisualStudio::CppUnitTestFramework;
    TEST_CLASS(TestClassName)
    {
    public:
      TEST_METHOD(TestMethodName)
      {
        // Run a function under test here.
        Assert::AreEqual(expectedValue, actualValue, L"message", LINE_INFO());
      }
    }
    
    • Assert 包含可用于验证测试结果的许多静态函数。

    • LINE_INFO() 参数是可选的。 在没有 PDB 文件的情况下,它允许测试运行程序标识失败的位置。

    • 您还可以编写测试设置和清理方法。 有关更多信息,请打开 TEST_METHOD 宏的定义,并阅读 CppUnitTest.h 的注释

    • 不能嵌套测试选件类。

  4. 使用测试管理器运行测试:

    1. 查看 菜单中,选择 其他窗口测试资源管理器

    2. 生成 Visual Studio 解决方案。

    3. 在测试资源管理器中,选择 全部运行

    4. 若要调查任何测试在更详细地测试资源管理器:

      1. 选择测试名称查看更多详细信息 (例如失败消息和堆栈跟踪。

      2. 例如打开的测试的名称 (通过双击) 转到失败位置或测试代码。

      3. 在测试的快捷菜单上,选择 调试所选测试 执行在调试器中测试。

演练:开发非托管 DLL 与测试资源管理器

您可以采用此演练中开发您的 DLL。 主要步骤如下:

  1. 创建一个本机测试项目。 在从正在开发的 DLL 的一个独立项目创建的测试。

  2. 创建一个 DLL 项目。 本演练创建新的 DLL,但是,测试的现有的 DLL 方法非常相似。

  3. 使 DLL 函数前者对测试。

  4. 以迭代方式增加测试。 建议“红色、绿色重构”循环,代码开发由生成的测试。

  5. 的调试失败测试。 可以运行测试调试模式。

  6. 重构,在保持不变时运行测试。 重构意味着改进代码的结构,而无需更改控件的外部行为。 可以对提高代码的性能、扩展性、可读性。 由于该视图不会更改此行为,则不会更改测试时,对代码中的某个重构更改。 测试,可帮助确保不引入 bug,则重构时。 如果没有测试,从而可以进行这些更改会有更多的信心比。

  7. 检查复盖率。 在执行更多您的代码时,单元测试更加有用。 您可以查看使用了代码的哪些部分测试。

  8. 从外部资源的隔离单元。 通常,DLL 取决于开发,例如其他 DLL,数据库系统,或者远程子系统的其他元素。 测试隔离的每个单元从其依赖项很有用。 外部组件会使测试运行速度更慢。 在开发过程中,其他元素可能不完整。

创建本机单元测试项目

  1. 在**“文件”菜单上,选择“新建”“项目”**。

    在对话框中,展开 已安装模板Visual C++测试

    选择 本机测试项目 模板。

    在本演练中,测试项目命名 NativeRooterTest。

    创建 C++ 单元测试项目

  2. 在新项目,请检查 unittest1.cpp

    具有 TEST_CLASS 和 TEST_METHOD 的测试项目

    注意:

    • 使用 TEST_METHOD(YourTestName){...},每个测试定义。

      您不必编写一个约定函数签名。 签名是由宏 TEST_METHOD 创建的。 宏生成返回 void 的实例功能。 它还生成返回有关测试方法的信息的静态函数。 此信息允许测试资源管理器找到方法。

    • 使用 TEST_CLASS(YourClassName){...},测试方法分为选件类。

      在运行测试时,每个实例的测试选件类创建的。 测试方法按未指定的顺序调用。 可以在每个模块、选件类或方法之前,调用的特殊方法。 有关更多信息,请参见 组织 C++ 测试。

  3. 验证测试运行测试资源管理器:

    1. 插入某些测试代码:

      TEST_METHOD(TestMethod1)
      {
      Assert::AreEqual(1,1);
      }
      

      请注意 Assert 选件类提供可用于验证结果的许多静态方法测试方法。

    2. 测试 菜单中,选择 运行所有测试

      测试生成和运行。

      测试资源管理器中显示。

      测试显示在 通过的测试下。

      具有 1 个已通过测试的单元测试资源管理器

创建非托管 DLL 项目

  1. 使用 Win32 项目 模板,创建一个 Visual C++ 项目。

    在本演练中,该项目命名 RootFinder。

    创建 C++ Win32 项目

  2. 选择 dll导出符号 在 Win32 应用程序向导。

    导出符号 选项生成可以使用声明导出的方法的便利宏。

    为 DLL 设置的 C++ 项目向导和导出符号

  3. 声明在主体 .h 文件中导出函数:

    使用 API 宏新建 DLL 代码项目和 .h 文件

    该声明 __declspec(dllexport) 导致选件类的公共和受保护的成员都显示在 DLL 外。 有关更多信息,请参见使用dllimport和dllexport在C++选件类

  4. 在主体 .cpp 文件中,添加函数的最小的主体:

    // Find the square root of a number.
    double CRootFinder::SquareRoot(double v)
    {
      return 0.0;
    }
    

耦合测试项目到 DLL 项目

  1. 添加 DLL 项目到项目引用测试项目:

    1. 打开一个测试项目的属性并选择 通用属性框架和引用

      C++ 项目属性 - 框架和引用

    2. 选择 添加新引用

      添加引用 对话框中,选择 DLL 项目并选择 添加

      C++ 项目属性 - 添加新引用

  2. 在该主体单元测试 .cpp 文件,包括 DLL 代码的 .h 文件:

    #include "..\RootFinder\RootFinder.h"
    
  3. 添加使用已导出函数的基本测试:

    TEST_METHOD(BasicTest)
    {
    CRootFinder rooter;
    Assert::AreEqual(
    // Expected value:
    0.0, 
    // Actual value:
    rooter.SquareRoot(0.0), 
    // Tolerance:
    0.01,
    // Message:
    L"Basic test failed",
    // Line number - used if there is no PDB file:
    LINE_INFO());
    }
    
  4. 生成解决方案。

    新测试将出现测试管理器。

  5. 在测试资源管理器中,选择 全部运行

    单元测试资源管理器 - 已通过基本测试

安装了测试和代码项目,并验证,可以运行测试在代码中运行的功能项目。 现在可以开始为实际编写测试和代码。

以迭代方式增加测试并使它们。

  1. 添加新测试:

    TEST_METHOD(RangeTest)
    {
      CRootFinder rooter;
      for (double v = 1e-6; v < 1e6; v = v * 3.2)
      {
        double actual = rooter.SquareRoot(v*v);
        Assert::AreEqual(v, actual, v/1000);
      }
    }
    

    提示

    我们建议您不要更改已通过的测试。相反,添加新测试,更新代码,使测试通过,然后添加另一个测试,依此类推。

    当用户更改其要求时,请禁用不再是正确的测试。新编写测试并使其工作一个节点,具有相同增量方式。

  2. 生成解决方案,然后在测试资源管理器中,选择 全部运行

    新测试失败。

    RangeTest 未通过

    提示

    验证每个测试失败,在编写完之后。这有助于避免编写从失败的测试的最简单错误。

  3. 引发测试代码,使新测试通过:

    #include <math.h>
    ...
    double CRootFinder::SquareRoot(double v)
    {
      double result = v;
      double diff = v;
      while (diff > result/1000)
      {
        double oldResult = result;
        result = result - (result*result - v)/(2*result);
        diff = abs (oldResult - result);
      }
      return result;
    }
    
  4. 生成解决方案然后在测试资源管理器中,选择 全部运行

    两个测试通过。

    单元测试资源管理器 - 已通过范围测试

    提示

    通过添加开发代码测试一个节点。确保所有每次迭代后测试通过。

调试失败测试

  1. 添加另一个测试:

    #include <stdexcept>
    ...
    // Verify that negative inputs throw an exception.
    TEST_METHOD(NegativeRangeTest)
    {
      wchar_t message[200];
      CRootFinder rooter;
      for (double v = -0.1; v > -3.0; v = v - 0.5)
      {
        try 
        {
          // Should raise an exception:
          double result = rooter.SquareRoot(v);
    
          _swprintf(message, L"No exception for input %g", v);
          Assert::Fail(message, LINE_INFO());
        }
        catch (std::out_of_range ex)
        {
          continue; // Correct exception.
        }
        catch (...)
        {
          _swprintf(message, L"Incorrect exception for %g", v);
          Assert::Fail(message, LINE_INFO());
        }
      }
    }
    
  2. 生成解决方案并选择 全部运行

  3. 打开 (或双击) 未通过的测试。

    失败的断言显示。 失败消息会显示在测试资源管理器细节窗格中。

    NegativeRangeTests 未通过

  4. 显示测试未通过的原因失败,通过功能步骤:

    1. 在 SquareRoot 函数的开始处设置断点。

    2. 在快捷菜单未通过测试,请选择 调试所选测试

      当在断点上运行的终止,逐句通过代码。

  5. 在您开发的功能的代码:

    #include <stdexcept>
    ...
    double CRootFinder::SquareRoot(double v)
    {
        // Validate parameter:
        if (v < 0.0) 
        {
          throw std::out_of_range("Can't do square roots of negatives");
        }
    
  6. 所有当前测试通过。

    所有测试通过

不更改测试的代码重构

  1. 简化了 SquareRoot 功能的核心计算:

    // old code:
    //   result = result - (result*result - v)/(2*result);
    // new code:
         result = (result + v/result)/2.0;
    
  2. 生成解决方案并选择,全部运行,以确保没有引入错误。

    提示

    好将单元测试设置为 confidence 您没有引入 bug,在更改代码时。

    继续重构与其他更改。

后续步骤

  • **隔离。**大多数 DLL,ID 依赖于其他子系统例如数据库和其他 DLL。 这些其他元素并行通常用于开发。 若要允许单元测试执行,而其他组件不可用,则必须替换 mock 或

  • **生成验证测试。**您的团队生成服务器上具有测试执行按设置的时间间隔。 这将确保不引入 bug,当若干个团队成员工作集成时。

  • **签入测试。**可以要求某些测试执行,在每个团队成员检查代码添加到源控件之前。 这通常是子集的完整设置生成验证测试。

    还可以要求代码复盖率的最低级别。

请参见

任务

Walkthrough: Creating and Using a Dynamic Link Library (C++)

概念

导入和导出

其他资源

托管/非托管代码互操作性概述

调试本机代码