cpp-mock-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Mock Testing

模拟测试

本指南为AI编码代理提供在C++项目中,使用Google Mock(GMock)框架并遵循一致软件测试模式来自动化创建模拟测试的说明。

1. Benefits

1. 优势

  • Isolation
    Isolates the unit under test from external dependencies, ensuring tests focus on the specific component's behavior.
  • Control
    Provides precise control over dependency behavior through expectations and return values, enabling thorough testing of edge cases and error conditions.
  • Verification
    Automatically verifies that dependencies are called correctly with expected parameters and call counts.
  • Flexibility
    Supports various testing scenarios including strict mocks, nice mocks, and sequence verification for complex interactions.
  • 隔离性
    将被测单元与外部依赖项隔离,确保测试专注于特定组件的行为。
  • 可控性
    通过预期设置和返回值,精确控制依赖项的行为,能够全面测试边缘情况和错误场景。
  • 验证性
    自动验证依赖项是否按预期的参数和调用次数被正确调用。
  • 灵活性
    支持多种测试场景,包括严格模拟(strict mocks)、宽松模拟(nice mocks)以及用于复杂交互的调用顺序验证。

2. Patterns

2. 模式

  • Mock Objects
    Simulated objects that mimic the behavior of real objects in controlled ways. They verify interactions between the unit under test and its dependencies.
  • Interface Mocking
    Creating mock implementations of abstract interfaces or base classes to isolate the unit under test from concrete implementations.
  • Behavior Verification
    Verifying that methods are called with expected arguments and in the correct order, rather than just checking return values.
  • Return Value Stubbing
    Configuring mock objects to return specific values when their methods are called, allowing control over dependency behavior during tests.
  • Exception Injection
    Using mocks to simulate error conditions by throwing exceptions, enabling tests to verify error handling logic.
  • 模拟对象
    以可控方式模拟真实对象行为的仿真对象,用于验证被测单元与其依赖项之间的交互。
  • 接口模拟
    为抽象接口或基类创建模拟实现,将被测单元与具体实现隔离开来。
  • 行为验证
    验证方法是否以预期参数按正确顺序被调用,而非仅检查返回值。
  • 返回值桩模拟
    配置模拟对象,使其在方法被调用时返回特定值,从而在测试期间控制依赖项的行为。
  • 异常注入
    使用模拟对象抛出异常来模拟错误场景,以便测试错误处理逻辑。

3. Workflow

3. 工作流程

  1. Identify Dependencies
    Identify interfaces or classes that need to be mocked (e.g., database connections, file systems, network services, external APIs).
  2. Create Mock Classes
    Create mock classes for interfaces under
    test(s)/unit/<module>/
    using GMock's
    MOCK_METHOD
    macro.
  3. Register with CMake
    Add the test file to
    test(s)/unit/<module>/CMakeLists.txt
    using
    meta_gtest()
    with
    WITH_GMOCK
    option.
    cmake
    meta_gtest(
      WITH_GMOCK
      TARGET ${PROJECT_NAME}-test
      SOURCES
        <header>_test.cpp
    )
  4. Define Expectations
    Set up expectations using
    EXPECT_CALL
    to specify:
    • Which methods should be called
    • Expected arguments (using matchers)
    • Call frequency (Times, AtLeast, AtMost, etc.)
    • Return values or actions
  5. Test Coverage Requirements
    Include comprehensive scenarios:
    • Normal operation with mocked dependencies
    • Error conditions (exceptions, null returns, invalid data)
    • Boundary conditions in dependency interactions
    • Sequence of calls to multiple dependencies
    • Concurrent access scenarios when applicable
  6. Apply Templates
    Structure all tests using the template pattern below.
  1. 识别依赖项
    识别需要被模拟的接口或类(例如数据库连接、文件系统、网络服务、外部API)。
  2. 创建模拟类
    使用GMock的
    MOCK_METHOD
    宏,在
    test(s)/unit/<module>/
    目录下为接口创建模拟类。
  3. 注册到CMake
    使用带有
    WITH_GMOCK
    选项的
    meta_gtest()
    ,将测试文件添加到
    test(s)/unit/<module>/CMakeLists.txt
    中。
    cmake
    meta_gtest(
      WITH_GMOCK
      TARGET ${PROJECT_NAME}-test
      SOURCES
        <header>_test.cpp
    )
  4. 定义预期
    使用
    EXPECT_CALL
    设置预期,指定:
    • 哪些方法应该被调用
    • 预期的参数(使用匹配器)
    • 调用频率(Times、AtLeast、AtMost等)
    • 返回值或动作
  5. 测试覆盖要求
    需包含全面的场景:
    • 带有模拟依赖项的正常操作场景
    • 错误场景(异常、空返回、无效数据)
    • 依赖项交互的边界条件
    • 多个依赖项的调用顺序
    • 适用时的并发访问场景
  6. 应用模板
    使用以下模板模式构建所有测试。

4. Commands

4. 命令

CommandDescription
make cmake-gcc-test-unit-build
CMake preset configuration with GMock support and Compile with Ninja
make cmake-gcc-test-unit-run
Execute tests via ctest (mock tests are part of unit tests)
make cmake-gcc-test-unit-coverage
Execute tests via ctest and generate coverage reports including mock test coverage
命令描述
make cmake-gcc-test-unit-build
带有GMock支持的CMake预设配置,使用Ninja编译
make cmake-gcc-test-unit-run
通过ctest执行测试(模拟测试属于单元测试的一部分)
make cmake-gcc-test-unit-coverage
通过ctest执行测试并生成包含模拟测试覆盖情况的覆盖率报告

5. Style Guide

5. 风格指南

  • Test Framework
    Use Google Mock (GMock) framework via
    #include <gmock/gmock.h>
    and
    #include <gtest/gtest.h>
    .
  • Mock Class Definition
    Define mock classes inheriting from the interface to be mocked. Use
    MOCK_METHOD
    macro with proper method signature, including const qualifiers and override specifiers.
  • Include Headers
    GMock/GTest headers are listed first in mock test files as a convention to clearly identify the file as a test file using the GMock framework.
    Include necessary headers in this order:
    1. GMock/GTest headers (
      <gmock/gmock.h>
      ,
      <gtest/gtest.h>
      )
    2. Standard library headers (
      <memory>
      ,
      <string>
      , etc.)
    3. Project interface headers
    4. Project implementation headers
  • Namespace
    Use
    using namespace <namespace>;
    and
    using namespace ::testing;
    for convenience within test functions to access GMock matchers and actions.
  • Test Organization
    Use table-driven testing for multiple scenarios with the same mock setup. Each
    TEST
    or
    TEST_F
    should focus on one aspect of the interaction with mocked dependencies.
  • Mock Types
    • NiceMock
      Ignores unexpected calls (use for non-critical dependencies)
    • StrictMock
      Fails on any unexpected calls (use for strict verification)
    • Default Mock
      Warns on unexpected calls (balanced approach)
  • Expectations
    • Use
      EXPECT_CALL
      to set up expectations before exercising the unit under test
    • Chain matchers with
      .With()
      ,
      .WillOnce()
      ,
      .WillRepeatedly()
      ,
      .Times()
    • Prefer specific matchers (
      Eq()
      ,
      Gt()
      ,
      _
      ) over generic ones when possible
  • Matchers and Actions
    • Use built-in matchers:
      _
      (anything),
      Eq()
      ,
      Ne()
      ,
      Lt()
      ,
      Gt()
      ,
      Le()
      ,
      Ge()
      ,
      IsNull()
      ,
      NotNull()
    • Container matchers:
      IsEmpty()
      ,
      SizeIs()
      ,
      Contains()
      ,
      ElementsAre()
    • String matchers:
      StartsWith()
      ,
      EndsWith()
      ,
      HasSubstr()
      ,
      MatchesRegex()
    • Use
      Return()
      ,
      ReturnRef()
      ,
      Throw()
      ,
      DoAll()
      ,
      Invoke()
      for actions
  • Sequence Verification
    Use
    InSequence
    or
    Sequence
    objects when call order matters.
  • Traceability
    Employ
    SCOPED_TRACE(tc.label)
    for traceable failures in table-driven mock tests.
  • Assertions
    Use
    EXPECT_*
    macros to allow all test cases to run. Mock expectations are automatically verified at the end of each test.
  • 测试框架
    通过
    #include <gmock/gmock.h>
    #include <gtest/gtest.h>
    使用Google Mock (GMock)框架。
  • 模拟类定义
    定义继承自待模拟接口的模拟类。使用
    MOCK_METHOD
    宏,并包含正确的方法签名,包括const限定符和override说明符。
  • 头文件包含
    按照惯例,GMock/GTest头文件应在模拟测试文件中排在最前面,以明确标识该文件是使用GMock框架的测试文件。
    按以下顺序包含必要的头文件:
    1. GMock/GTest头文件(
      <gmock/gmock.h>
      <gtest/gtest.h>
    2. 标准库头文件(
      <memory>
      <string>
      等)
    3. 项目接口头文件
    4. 项目实现头文件
  • 命名空间
    在测试函数内使用
    using namespace <namespace>;
    using namespace ::testing;
    ,以便便捷地访问GMock匹配器和动作。
  • 测试组织
    对于具有相同模拟设置的多场景测试,使用表格驱动测试。每个
    TEST
    TEST_F
    应专注于与模拟依赖项交互的一个方面。
  • 模拟类型
    • NiceMock
      忽略意外调用(用于非关键依赖项)
    • StrictMock
      遇到任何意外调用时失败(用于严格验证场景)
    • 默认模拟
      遇到意外调用时发出警告(平衡方案)
  • 预期设置
    • 在执行被测单元之前,使用
      EXPECT_CALL
      设置预期
    • 使用
      .With()
      .WillOnce()
      .WillRepeatedly()
      .Times()
      链式调用匹配器
    • 尽可能使用特定匹配器(
      Eq()
      Gt()
      _
      )而非通用匹配器
  • 匹配器与动作
    • 使用内置匹配器:
      _
      (任意值)、
      Eq()
      Ne()
      Lt()
      Gt()
      Le()
      Ge()
      IsNull()
      NotNull()
    • 容器匹配器:
      IsEmpty()
      SizeIs()
      Contains()
      ElementsAre()
    • 字符串匹配器:
      StartsWith()
      EndsWith()
      HasSubstr()
      MatchesRegex()
    • 使用
      Return()
      ReturnRef()
      Throw()
      DoAll()
      Invoke()
      定义动作
  • 调用顺序验证
    当调用顺序重要时,使用
    InSequence
    Sequence
    对象。
  • 可追溯性
    在表格驱动模拟测试中,使用
    SCOPED_TRACE(tc.label)
    实现可追溯的失败定位。
  • 断言
    使用
    EXPECT_*
    宏,允许所有测试用例运行。模拟预期会在每个测试结束时自动验证。

6. Template

6. 模板

Use these templates for new mock tests. Replace placeholders with actual values.
使用以下模板创建新的模拟测试,将占位符替换为实际值。

6.1. File Header Template

6.1. 文件头模板

cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <memory>
#include <string>
#include <vector>

#include "<module>/<interface>.hpp"
#include "<module>/<implementation>.hpp"

using namespace <namespace>;
using namespace ::testing;
cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <memory>
#include <string>
#include <vector>

#include "<module>/<interface>.hpp"
#include "<module>/<implementation>.hpp"

using namespace <namespace>;
using namespace ::testing;

6.2. Mock Class Template

6.2. 模拟类模板

cpp
/**
 * @brief Mock implementation of <Interface> for testing.
 */
class Mock<Interface> : public <Interface>
{
public:
  MOCK_METHOD(<return_type>, <method_name>, (<param_types>), (override));
  MOCK_METHOD(<return_type>, <method_name2>, (<param_types>), (const, override));
};
cpp
/**
 * @brief 用于测试的<Interface>模拟实现。
 */
class Mock<Interface> : public <Interface>
{
public:
  MOCK_METHOD(<return_type>, <method_name>, (<param_types>), (override));
  MOCK_METHOD(<return_type>, <method_name2>, (<param_types>), (const, override));
};

6.3. Table-Driven Mock Test Template

6.3. 表格驱动模拟测试模板

cpp
TEST(<Module>Test, <FunctionName>WithMock)
{
  // In-Got-Want
  struct Tests
  {
    std::string label;

    struct In
    {
      /* input types and names */
    } in;

    struct Want
    {
      <output_type> expected;     // expected output type(s) and name(s)
      /* expected mock call parameters and behavior */
      <size_t> call_count;        // number of times method should be called
      <return_type> return_value; // value mock should return
      <param_type> param;         // expected parameter value(s)
    } want;
  };

  // Table-Driven Testing
  const std::vector<Tests> tests = {
    {
      "case-description-1", 
      /* in */ {/* input values */}, 
      /* want */ {/* expected */, /* call_count */ 1, /* return_value */ {}, /* param */ {}}
    },
    {
      "case-description-2", 
      /* in */ {/* input values */}, 
      /* want */ {/* expected */, /* call_count */ 1, /* return_value */ {}, /* param */ {}}
    },
    // add more cases as needed
  };

  for (const auto &tc : tests)
  {
    SCOPED_TRACE(tc.label);

    // Arrange
    auto mock_dependency = std::make_shared<Mock<Interface>>();

    EXPECT_CALL(*mock_dependency, <method_name>(tc.want.param))
        .Times(tc.want.call_count)
        .WillOnce(Return(tc.want.return_value));

    <Implementation> object(mock_dependency);

    // Act
    auto got = object.<function>(tc.in.<input>);

    // Assert
    EXPECT_EQ(got, tc.want.expected);
  }
}
cpp
TEST(<Module>Test, <FunctionName>WithMock)
{
  // 输入-实际-预期
  struct Tests
  {
    std::string label;

    struct In
    {
      /* 输入类型和名称 */
    } in;

    struct Want
    {
      <output_type> expected;     // 预期输出类型和名称
      /* 预期的模拟调用参数和行为 */
      <size_t> call_count;        // 方法应被调用的次数
      <return_type> return_value; // 模拟对象应返回的值
      <param_type> param;         // 预期的参数值
    } want;
  };

  // 表格驱动测试
  const std::vector<Tests> tests = {
    {
      "用例描述1", 
      /* 输入 */ {/* 输入值 */}, 
      /* 预期 */ {/* 预期结果 */, /* 调用次数 */ 1, /* 返回值 */ {}, /* 参数 */ {}}
    },
    {
      "用例描述2", 
      /* 输入 */ {/* 输入值 */}, 
      /* 预期 */ {/* 预期结果 */, /* 调用次数 */ 1, /* 返回值 */ {}, /* 参数 */ {}}
    },
    // 根据需要添加更多用例
  };

  for (const auto &tc : tests)
  {
    SCOPED_TRACE(tc.label);

    // 准备阶段
    auto mock_dependency = std::make_shared<Mock<Interface>>();

    EXPECT_CALL(*mock_dependency, <method_name>(tc.want.param))
        .Times(tc.want.call_count)
        .WillOnce(Return(tc.want.return_value));

    <Implementation> object(mock_dependency);

    // 执行阶段
    auto got = object.<function>(tc.in.<input>);

    // 断言阶段
    EXPECT_EQ(got, tc.want.expected);
  }
}

6.4. Sequence Verification Template

6.4. 调用顺序验证模板

cpp
TEST(<Module>Test, <FunctionName>WithSequence)
{
  // Arrange
  auto mock_dependency = std::make_shared<StrictMock<Mock<Interface>>>();

  InSequence seq;
  EXPECT_CALL(*mock_dependency, <method1>(_)).WillOnce(Return(<value1>));
  EXPECT_CALL(*mock_dependency, <method2>(_)).WillOnce(Return(<value2>));

  <Implementation> object(mock_dependency);

  // Act
  auto got = object.<function>();

  // Assert
  EXPECT_EQ(got, <expected>);
}
cpp
TEST(<Module>Test, <FunctionName>WithSequence)
{
  // 准备阶段
  auto mock_dependency = std::make_shared<StrictMock<Mock<Interface>>>();

  InSequence seq;
  EXPECT_CALL(*mock_dependency, <method1>(_)).WillOnce(Return(<value1>));
  EXPECT_CALL(*mock_dependency, <method2>(_)).WillOnce(Return(<value2>));

  <Implementation> object(mock_dependency);

  // 执行阶段
  auto got = object.<function>();

  // 断言阶段
  EXPECT_EQ(got, <expected>);
}

6.5. Exception Testing Template

6.5. 异常测试模板

cpp
TEST(<Module>Test, <FunctionName>ThrowsOnError)
{
  // Arrange
  auto mock_dependency = std::make_shared<Mock<Interface>>();

  EXPECT_CALL(*mock_dependency, <method_name>(_))
      .WillOnce(Throw(std::runtime_error("error message")));

  <Implementation> object(mock_dependency);

  // Act & Assert
  EXPECT_THROW(object.<function>(), std::runtime_error);
}
cpp
TEST(<Module>Test, <FunctionName>ThrowsOnError)
{
  // 准备阶段
  auto mock_dependency = std::make_shared<Mock<Interface>>();

  EXPECT_CALL(*mock_dependency, <method_name>(_))
      .WillOnce(Throw(std::runtime_error("错误消息")));

  <Implementation> object(mock_dependency);

  // 执行与断言
  EXPECT_THROW(object.<function>(), std::runtime_error);
}

6.6. NiceMock Template

6.6. NiceMock模板

cpp
TEST(<Module>Test, <FunctionName>WithNiceMock)
{
  // Arrange
  auto mock_dependency = std::make_shared<NiceMock<Mock<Interface>>>();

  ON_CALL(*mock_dependency, <method_name>(_))
      .WillByDefault(Return(<default_value>));

  EXPECT_CALL(*mock_dependency, <critical_method>(_))
      .Times(1)
      .WillOnce(Return(<value>));

  <Implementation> object(mock_dependency);

  // Act
  auto got = object.<function>();

  // Assert
  EXPECT_EQ(got, <expected>);
}
cpp
TEST(<Module>Test, <FunctionName>WithNiceMock)
{
  // 准备阶段
  auto mock_dependency = std::make_shared<NiceMock<Mock<Interface>>>();

  ON_CALL(*mock_dependency, <method_name>(_))
      .WillByDefault(Return(<默认值>));

  EXPECT_CALL(*mock_dependency, <critical_method>(_))
      .Times(1)
      .WillOnce(Return(<>));

  <Implementation> object(mock_dependency);

  // 执行阶段
  auto got = object.<function>();

  // 断言阶段
  EXPECT_EQ(got, <预期值>);
}

7. References

7. 参考资料