> 技术文档 > 掌握googletest:C++的单元测试与模拟框架

掌握googletest:C++的单元测试与模拟框架

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:googletest是谷歌开发的开源C++测试框架,用于编写和执行单元测试,提供了丰富的断言和测试策略。它支持测试套件、测试用例、参数化测试、固定点、主运行器等,以及 gmock 模拟框架。 googletest 有助于提高代码质量和简化测试过程,是实现TDD和持续集成的重要工具。
googletest

1. googletest开源框架介绍

googletest是Google开发的一个跨平台的C++测试框架,它主要用于编写和运行测试用例。作为一个开源的测试框架,googletest被广泛应用于各种软件项目中,包括Google自己的项目。其主要特点包括易于使用、支持测试的自动发现和运行、具有丰富的断言库,以及对测试的参数化和模拟测试提供了强大的支持。

googletest使用了一种基于行为驱动开发的方法,允许开发者以接近自然语言的方式来编写测试用例。框架的主要组件包括断言库、测试套件、参数化测试和模拟框架等。这些组件共同为开发者提供了一种强大而灵活的方式来编写和维护测试用例。

在本章中,我们将首先对googletest框架进行一个基本的介绍,包括其设计理念、主要组件和使用场景。这将为后面章节中对框架各个部分深入讨论打下基础。

2. 断言库的使用方法

编写健壮的测试代码离不开有效的断言。断言是单元测试的基础,它用于验证代码中的关键假设是否成立。在本章节中,我们将深入了解googletest框架的断言库,并探讨如何使用它们来增强测试的准确性和可靠性。

2.1 断言类型概览

2.1.1 常见断言方法及其用途

googletest框架提供了一系列的断言宏(Assert Macros),以支持测试中的条件验证。以下是一些常用的断言方法及其用途:

  • gtest/gtest.h 中定义的 ASSERT_* EXPECT_*
  • 用于比较基本数据类型的断言,如 ASSERT_EQ , ASSERT_NE , ASSERT_GT , 等等
  • 用于检查字符串相等的断言,如 ASSERT_STREQ , ASSERT_STRNE , 等等
  • 用于检查浮点数比较的断言,如 ASSERT_FLOAT_EQ , ASSERT_DOUBLE_EQ , 等等

每个 ASSERT_* 断言在条件不满足时将立即终止测试,而 EXPECT_* 断言则不会。通常,使用 EXPECT_* 来进行测试断言是一种更为可取的做法,因为它允许在测试函数中继续执行其它断言,提供更多的诊断信息。

示例代码:

TEST(MyTestSuite, ComparisonTest) { int value1 = 1; int value2 = 2; // 使用EQ断言来检查两个整数是否相等 ASSERT_EQ(value1, value2); // 如果上述断言失败,测试会立即终止,并输出value1和value2的值作为诊断信息 // 使用NE断言来检查两个整数是否不等 EXPECT_NE(value1, value2); // 如果上述断言失败,输出诊断信息,但测试会继续执行}

2.1.2 自定义断言消息的重要性

在编写测试时,自定义断言消息能够提供额外的上下文信息,有助于快速定位问题。googletest 允许在断言后直接跟上一个字符串,作为断言失败时的详细描述。

示例代码:

TEST(MyTestSuite, CustomMessageTest) { int value = 10; // 提供自定义错误消息 ASSERT_EQ(5, value) << \"This is a custom failure message indicating expected value.\";}

这段代码在 value 不等于5时会提供一条包含 “This is a custom failure message indicating expected value.” 的错误消息。

2.2 高级断言技巧

2.2.1 断言的组合使用

在测试中,你可能需要确保多个条件同时满足。虽然可以单独为每个条件编写断言,但在某些情况下组合断言(如 ASSERT_TRUE )更有效率。

示例代码:

TEST(MyTestSuite, CombinedAssertionTest) { int x = 1; int y = 2; int z = 3; // 同时检查多个条件 ASSERT_TRUE(x < y && y < z); // 如果任何子条件失败,断言失败并提供失败的子条件作为诊断信息}

2.2.2 跨测试的断言检查

有时候,测试的某些条件断言需要跨多个测试共享。为确保测试的独立性,googletest 提供了 HasFailure() Add_FAILURE() 方法来跨测试检查和报告失败。

示例代码:

class MyTest : public testing::Test { protected: static bool failure_occurred;};bool MyTest::failure_occurred = false;TEST_F(MyTest, FirstTest) { EXPECT_TRUE(some_condition); if (!some_condition) { // 记录失败情况供后续测试使用 MyTest::failure_occurred = true; testing::Test::RecordProperty(\"status\", \"failed\"); }}TEST_F(MyTest, SecondTest) { // 检查是否有任何之前的测试失败了 ASSERT_FALSE(MyTest::failure_occurred);}

在上面的代码中,如果 FirstTest 中的某个断言失败了, failure_occurred 将被设置为 true 。然后,在 SecondTest 测试中,如果 failure_occurred true ,测试将失败。

在本章中,我们讨论了断言库的使用方法,包括断言类型概览和高级断言技巧。理解断言的工作机制以及如何有效使用它们,对于编写健壮且可维护的测试代码至关重要。在下一章中,我们将继续深入探讨测试套件与测试用例的创建与管理。

3. 测试套件与测试用例的创建与管理

在现代软件开发中,有效的测试管理对于保证产品质量至关重要。测试套件和测试用例是组织和执行测试的基本单元。在本章节中,我们将深入探讨如何编写高质量的测试用例,并构建和维护测试套件。

3.1 测试用例的编写规则

测试用例是测试套件中的最小单元,它包含了用于执行特定测试目标的输入数据、测试步骤、预期结果和实际结果。编写高质量的测试用例是确保软件质量的第一步。

3.1.1 单个测试函数的结构和生命周期

每个测试函数应该清晰地表达一个单一的测试意图。在一个典型的测试框架中,测试函数通常遵循以下生命周期:

  • 初始化(SetUp) :在每个测试用例开始之前执行,用于准备测试环境。
  • 执行(Test Body) :测试的主体部分,包含了实际的测试代码。
  • 清理(TearDown) :在每个测试用例结束后执行,用于清理测试环境并恢复到初始状态。

代码块演示了一个简单的测试函数生命周期:

TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(1, Factorial(0)); // SetUp & Test Body} // TearDownTEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(1, Factorial(1)); EXPECT_EQ(2, Factorial(2)); EXPECT_EQ(6, Factorial(3)); EXPECT_EQ(24, Factorial(4)); // SetUp & Test Body} // TearDown

在上述代码中, TEST 宏定义了一个测试用例, EXPECT_EQ 是断言宏,用于验证实际结果是否与预期相符。每个测试用例在执行完毕后自动进行清理。

3.1.2 测试用例的组织与命名规范

组织测试用例是提高可读性和可维护性的关键。遵循以下命名规则和组织方法可以提升测试套件的质量:

  • 命名规范 :使用描述性名称,清晰地传达测试目的。例如, TEST(ClassName, MethodName_Scenario_ExpectedOutcome)
  • 逻辑分组 :将相关的测试用例组合在一起,可能根据功能、类或模块进行分组。
  • 数据驱动测试 :当测试用例需要使用多个数据集时,可以将数据从测试逻辑中分离,以简化测试的维护。

3.2 测试套件的构建与维护

测试套件是相关测试用例的集合,它们共享相同的初始化代码和清理代码。构建和维护测试套件有助于系统化地运行和管理测试。

3.2.1 测试套件的作用与结构

测试套件的主要作用是:

  • 管理测试执行,如并行运行、分组运行特定测试等。
  • 提供一个高级的抽象层,可以运行整个套件或其中的子集。

一个测试套件的结构通常包括:

  • 测试套件声明 :声明测试套件的名称和包含的测试用例。
  • 初始化和清理代码 :每个测试套件可能有专用的初始化和清理代码,用于设置测试环境和清理资源。

代码块展示了如何使用googletest定义一个测试套件:

TEST(MyTestSuite, Test1) { // 测试逻辑}TEST(MyTestSuite, Test2) { // 测试逻辑}// 定义测试套件SUITE(MyTestSuite) { // 可以在这里添加全局的SetUp和TearDown}

3.2.2 测试套件的配置和运行策略

配置和运行测试套件可以采用多种策略:

  • 并行测试 :通过配置来并行运行多个测试,以提高测试效率。
  • 过滤测试用例 :根据特定条件过滤,只运行符合规则的测试用例。
  • 分层运行 :允许运行整个套件或只运行特定的测试用例。

表格表示了不同测试套件配置对测试执行的影响:

配置方法 描述 运行所有测试用例 执行套件中的每一个测试用例 运行指定标签的测试用例 只执行带有特定标签的测试用例 运行指定名称模式的测试用例 过滤和执行名称符合特定模式的测试用例 并行运行测试用例 使用多线程同时运行多个测试用例,减少总执行时间

使用googletest的命令行参数可以实现这些配置,例如使用 --gtest_filter 来过滤测试用例:

./my_test --gtest_filter=MyTestSuite.*

上述命令将运行所有属于 MyTestSuite 的测试用例。

通过本章节的介绍,您应该对测试用例和测试套件有了深入的理解,并能够编写组织良好、易于维护的测试代码。下一章节将深入讲解参数化测试的实施及其优势,这将帮助您进一步提升测试的效率和质量。

4. 参数化测试的实施与优势

4.1 参数化测试的原理

4.1.1 参数化测试框架介绍

参数化测试是一种在软件测试中广泛应用的技术,其核心理念是将测试用例中的固定数据变为可变参数,通过不同的参数组合来执行同一测试逻辑,以覆盖更广泛的测试场景。在googletest框架中,参数化测试通过 TEST_P 宏来实现,它允许开发者定义测试用例时指定参数,从而在实际执行测试时根据不同的参数进行多次测试。

参数化测试相比于传统的硬编码测试用例,有着不可比拟的优势。它不仅可以减少重复的测试代码,还可以提高测试的灵活性和可维护性。更重要的是,参数化测试能够帮助测试人员更细致地控制测试行为,尤其是在涉及到边界条件或者特殊输入数据时,参数化测试可以显著地提高测试的覆盖率。

4.1.2 参数化测试的设计模式

在googletest中,参数化测试的设计模式通常遵循以下步骤:

  1. 定义一个参数提供者(Parameter Provider):这通常是一个返回测试参数集合的函数或者类。
  2. 使用 TEST_P 宏定义参数化测试用例: TEST_P 接受一个测试用例名称和参数集,其中参数集由参数提供者提供。
  3. 编写测试逻辑:在 TEST_P 中编写测试逻辑,该逻辑将会对每个提供的参数集执行一次。
  4. 使用 INSTANTIATE_TEST_CASE_P 宏来实际实例化测试用例:这个宏用于在测试运行前指定哪些参数集需要执行。

下面是一个简单的参数化测试示例代码:

#include // 参数提供者class MyParams { public: static std::vector<std::tuple> GetTestParams() { return {{1, 1}, {1, 2}, {2, 1}, {2, 2}}; }};// 参数化测试用例TEST_P(MyParamTest, TestAddition) { int a, b; int expected, actual; std::tie(a, b) = GetParam(); expected = a + b; actual = Add(a, b); ASSERT_EQ(expected, actual);}// 实例化测试用例INSTANTIATE_TEST_CASE_P(Default, MyParamTest, ::testing::ValuesIn(MyParams::GetTestParams()));

4.2 参数化测试的优势与应用

4.2.1 提高代码复用率

参数化测试的第一个优势在于极大地提高了测试代码的复用率。在传统测试方法中,如果需要测试多种数据组合或不同的输入值,测试人员通常会编写多个几乎相同的测试用例,导致代码冗余和重复。而使用参数化测试,相同的测试逻辑可以应用于不同的输入参数,这样不仅减少了代码总量,也使得测试用例更加整洁和易于管理。

通过参数化测试,测试人员可以专注于编写单一的测试逻辑,并通过参数集来覆盖不同的测试场景,这样不仅提高了测试效率,也降低了维护成本。此外,参数化测试使得测试用例的扩展变得十分简单,只需添加新的参数集即可,而无需修改测试逻辑本身。

4.2.2 简化测试的维护工作

由于参数化测试将测试逻辑与具体数据分离,这在维护测试代码时也带来了极大的便利。当测试逻辑发生变化时,不需要对每一个测试用例进行修改,只需更新参数提供者或测试逻辑本身。同时,当需要增加新的测试数据时,也仅需添加相应的参数集。

此外,参数化测试使得测试的配置更加灵活。在不同的测试环境或测试阶段,可能需要使用不同的数据集进行测试。使用参数化测试框架,测试人员可以根据需要动态地选择参数集,而无需修改测试代码本身。这种灵活性在持续集成和持续部署(CI/CD)流程中尤为重要,可以针对开发、测试、生产等不同环境执行不同的测试策略。

下面通过一个表格来展示参数化测试与传统测试方法的对比:

特性 参数化测试 传统测试 代码复用率 高 低 维护工作量 简化 复杂 测试数据 动态管理 静态定义 扩展性 好 差 适应性 强 弱 效率 高 低

参数化测试通过减少代码冗余、简化测试维护和提高配置的灵活性,为软件测试提供了一种高效、可维护的解决方案。无论是对于小型项目还是大型系统的测试,参数化测试都能提供巨大的价值,特别是在持续集成和持续交付的环境中,其优势尤为明显。

5. gmock模拟框架的介绍

5.1 gmock模拟框架概述

5.1.1 gmock的安装与配置

Google Mock(简称gmock)是Google开源的针对C++的模拟框架,它是与googletest测试框架紧密集成的一个组件。gmock被设计用来模拟复杂的依赖项和行为,使得单元测试可以更加专注于待测试的代码单元。

要开始使用gmock,首先需要在项目中安装googletest和gmock库。在多数Linux发行版中,可以通过包管理器安装相应的开发包。例如,在Ubuntu中,可以使用以下命令:

sudo apt-get install libgoogle-mock-dev

对于Windows系统,可以从Google的GitHub仓库中获取预编译的二进制文件,或者自行编译源码。

安装完成后,需要在C++测试文件中包含gmock和googletest的头文件,并配置编译器以链接到这些库。一个简单的配置示例如下:

#include #include int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();}

这段代码同时包含了googletest和gmock的头文件,并初始化了测试框架。 RUN_ALL_TESTS() 宏负责执行所有注册的测试用例。

5.1.2 gmock的核心概念与组件

gmock的核心概念包括模拟对象(Mock Object),预期行为(Expectations),和验证(Verification)。通过模拟对象,测试人员可以创建一个理想化的版本来替代真实的依赖项。预期行为是指测试人员对模拟对象行为的预期,例如,当调用特定的方法时应返回特定的值。验证则是检查这些预期是否得到满足。

gmock提供了丰富的组件,包括:

  • MATCHER :允许用户定义自己的匹配规则。
  • ACTION :定义对调用的响应。
  • NAMED_VALUE_PARAMETER :提供一种定义和使用命名参数的方法。
  • ON_CALL :与 WillByDefault 一起使用,允许修改默认预期行为。
  • WITH_MOCK_METHODS :用于类模板,将模拟方法添加到类中。

下面是一个使用gmock核心组件的简单例子,展示了一个模拟对象的创建和配置:

#include using ::testing::Return;class MockDatabase { public: MOCK_METHOD(bool, lookup, (const std::string& key, std::string* result), (override));};TEST(DatabaseTest, LookupReturnsFalseForMissingKey) { MockDatabase db; std::string data; EXPECT_CALL(db, lookup(\"key\", &data)) .WillOnce(Return(false)); EXPECT_FALSE(db.lookup(\"key\", &data));}

在这个例子中, MockDatabase 是一个模拟类,其中的 lookup 方法被模拟了。我们使用 EXPECT_CALL 宏来设置预期行为,并指明了当 lookup 方法被调用时,应当返回 false

5.2 gmock在测试中的应用

5.2.1 模拟对象的创建和配置

模拟对象允许测试人员在测试环境中模拟复杂的外部依赖。创建模拟对象时,通常只需要继承自gmock提供的宏 MOCK_METHOD 定义的类模板。然后可以使用 EXPECT_CALL 宏来定义期望调用的方法和返回值。

在配置模拟对象时,可以使用不同的参数来精确控制预期行为:

using ::testing::_;using ::testing::Return;TEST(DatabaseTest, LookupReturnsFalseForMissingKey) { MockDatabase db; std::string data; // Expect a call to lookup with any string argument for key, but ignore it. // The result should be false. EXPECT_CALL(db, lookup(_, _)) .WillOnce(Return(false)); // Call lookup and expect a false return value. EXPECT_FALSE(db.lookup(\"key\", &data));}

在这个测试中, _ 是一个匹配器,表示匹配任意的参数。这里的意思是不管传入什么值作为 key ,返回值都应该是 false

5.2.2 模拟行为和验证的实现

gmock允许测试人员定义复杂的预期行为,比如对于参数的匹配,对于返回值的控制,以及对于调用次数的限制等。这在真实环境中可以极大地增加测试的灵活性和可控性。

例如,可以通过 Times 来限制一个函数调用的次数:

using ::testing::Return;using ::testing::AtLeast;TEST(DatabaseTest, LookupIsCalledAtLeastOnce) { MockDatabase db; std::string data; EXPECT_CALL(db, lookup(_, _)) .Times(AtLeast(1)) .WillRepeatedly(Return(false)); // Call lookup many times. It is expected to be called at least once. for (int i = 0; i < 10; ++i) { db.lookup(\"key\", &data); }}

在这个测试中, AtLeast(1) 表示至少会被调用一次,而 WillRepeatedly 表示之后的调用都会返回 false

gmock还提供了验证功能,可以检查是否所有的预期行为都已满足:

TEST(DatabaseTest, VerifyAllExpectations) { MockDatabase db; std::string data; EXPECT_CALL(db, lookup(_, _)) .WillOnce(Return(true)) .WillOnce(Return(false)); // Call lookup. db.lookup(\"key1\", &data); db.lookup(\"key2\", &data); // Verify all the expectations. ::testing::Mock::VerifyAndClearExpectations(&db);}

在测试结束后调用 VerifyAndClearExpectations ,gmock会检查所有的预期是否都已经满足。如果某个预期没有被满足,测试将会失败。

以上章节详细介绍了gmock模拟框架的安装、配置、核心概念、以及如何在测试中应用模拟对象和行为。通过灵活运用gmock提供的工具,开发者可以针对复杂的测试场景设计更加可靠、可控的测试用例。

6. 固定点与主运行器的配置

6.1 固定点的原理与实践

6.1.1 固定点的定义和作用

固定点是指在软件测试中,特定的代码位置,其输出结果可以被预测和复现。固定点的实现通常用于依赖于外部环境的复杂系统的测试中,比如数据库操作、文件系统交互,以及外部服务调用等。通过在测试中引入固定点,开发者可以将代码的测试状态“固定”在一个已知的、稳定的环境中,以确保测试结果的准确性和可重复性。

固定点不仅提升了测试的可靠性,它还有助于隔离外部因素对测试的影响,使得测试用例之间相互独立,避免了测试间的干扰,从而提升测试的效率和质量。

6.1.2 在测试中实现固定点

在测试中实现固定点,主要依赖于以下几种策略:

  1. 使用Mock对象 :Mock对象能够模仿真实的对象行为,但其结果是可配置和可预测的。在单元测试中,使用Mock对象代替真实的依赖可以创建一个稳定的测试环境。

  2. 依赖注入(DI) :通过依赖注入,测试代码可以向被测对象提供预定义的依赖,这些依赖可以是Mock对象或其他形式的固定点。

  3. 预设条件 :在测试开始前,预先设置好外部依赖的状态,例如数据库记录、文件系统中的文件内容等。测试运行后,再将外部环境恢复到初始状态,以保证测试的隔离性。

下面是一个使用Mock对象创建固定点的简单示例:

// 假设有一个与数据库交互的函数void FunctionDependingOnDatabase() { Database dbConnection; dbConnection.Connect(\"localhost\"); // ... 其他数据库操作}// 在测试中创建Mock对象class MockDatabase {public: void Connect(const std::string&) { /* 预设的Mock连接行为 */ } // ... 其他Mock方法};TEST(FunctionDependingOnDatabaseTest, FixedPointCreation) { MockDatabase mockDB; // 通过Mock来固定外部依赖 EXPECT_CALL(mockDB, Connect) .Times(1) .WillOnce(Return()); // 预设Connect方法的行为 // 使用Mock对象执行测试 FunctionDependingOnDatabase();}

在上述代码中,通过定义一个 MockDatabase 类并利用gMock框架的 EXPECT_CALL 宏,我们成功地为数据库连接操作设定了一个固定的预期行为,从而避免了实际的数据库连接,固定了测试点。

6.2 主运行器的配置技巧

6.2.1 主运行器的作用与结构

主运行器(Main Runner)是测试框架中的一个核心组件,它负责协调和管理测试用例的执行流程。主运行器的主要职责包括:

  • 初始化测试环境。
  • 调度并运行测试用例。
  • 收集并报告测试结果。

主运行器的结构通常包括以下几个部分:

  • 初始化代码 :设置测试环境和全局变量,如日志记录器、资源分配等。
  • 测试发现机制 :找到需要执行的测试用例,这可能是通过显式列出测试、扫描测试文件等方式完成。
  • 测试执行逻辑 :执行测试用例,并根据测试结果更新状态。
  • 结果报告 :将测试结果输出到控制台、日志文件或报告生成器中。

主运行器的实现和配置在不同的测试框架中可能有所不同,但基本的职责和结构保持一致。

6.2.2 自定义主运行器的步骤与案例

为了满足特定的测试需求,开发者可能需要自定义主运行器。以下是一个自定义主运行器的步骤,以googletest为例:

  1. 定义测试用例的入口点 :创建一个main函数作为自定义主运行器的起点。

    cpp int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); // 自定义初始化代码 // ... }

  2. 运行测试 :使用 RUN_ALL_TESTS() 宏来启动测试。

    ```cpp
    int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    // 自定义初始化代码
    // …

    int result = RUN_ALL_TESTS();return result;

    }
    ```

  3. 处理命令行参数 :利用 InitGoogleTest 函数处理来自命令行的参数。

    ```cpp
    int main(int argc, char **argv) {
    // 初始化Google Test,处理参数
    ::testing::InitGoogleTest(&argc, argv);

    // 执行测试int result = RUN_ALL_TESTS();return result;

    }
    ```

  4. 自定义测试逻辑 :在 main 函数中加入自定义逻辑,比如测试用例的过滤、测试环境的初始化和清理等。

    ```cpp
    int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);

    // 自定义过滤逻辑::testing::TestFilter test_filter;// ... 配置过滤逻辑// 根据过滤逻辑运行测试int result = RUN_ALL_TESTS();return result;

    }
    ```

通过以上步骤,开发者可以创建一个灵活的、功能丰富的自定义主运行器。这个运行器不仅能够运行测试,还能够根据实际需要调整测试行为。

自定义主运行器的案例展示了一个如何实现自定义初始化和参数处理,以及如何调整测试用例的执行逻辑的完整流程。这为测试实践提供了高度的灵活性,满足了复杂测试环境下的需求。

通过本章节的介绍,我们深入了解了固定点的概念及其在测试中的实践应用,并学习了如何配置和利用自定义主运行器来更好地控制测试流程。这些知识点对于提高测试的可靠性、效率和可控性都有着重要的意义。

7. 匹配器与行动在模拟中的应用

在单元测试中,特别是在使用gmock模拟框架时,匹配器(Matchers)和行动(Actions)是构建灵活而强大的测试用例的关键组件。本章节将深入介绍匹配器和行动的概念、使用方法以及它们在模拟中的实际应用。

7.1 匹配器的使用与自定义

7.1.1 匹配器的概念和分类

匹配器是用于在gmock中进行参数匹配的工具。它们允许测试者指定预期调用的参数应该满足哪些条件,而不是硬编码具体值。这样的灵活性使得模拟对象可以适应不同的调用情况,从而提高测试的复用性和鲁棒性。

匹配器主要可以分为两类:

  • 预定义匹配器 :这些是gmock已经提供的通用匹配器,如 Eq(n) (等于n)、 Lt(n) (小于n)、 Gt(n) (大于n)等。
  • 自定义匹配器 :在某些情况下,预定义匹配器无法满足特定的测试需求,这时可以创建自己的匹配器来执行更复杂的匹配逻辑。

7.1.2 自定义匹配器的方法与示例

创建自定义匹配器需要继承 MATCHER 宏提供的结构,并实现其 MatchAndNotify 方法。下面是一个简单的例子,展示了如何创建一个检查字符串是否以特定前缀开始的匹配器。

#include MATCHER_P(StartsWith, prefix, \"This string should start with \" + std::string(prefix)) { return testing::string::StartsWith(n, prefix);}// 使用自定义匹配器TEST(MyTestSuite, CustomMatcherExample) { std::string test_string = \"Hello World\"; EXPECT_CALL(mock, SomeMethod(StartsWith(\"Hello\"))) .Times(1); mock.SomeMethod(test_string);}

通过这个例子,我们可以看到如何利用 MATCHER 宏和标准库的字符串处理函数来构建一个实用的自定义匹配器。这样的匹配器可以用于多种测试场景,使得测试逻辑更加清晰和可维护。

7.2 行动的配置与执行

7.2.1 行动的作用与应用场景

行动是gmock中用于定义当模拟方法被调用时应该执行的操作。行动使得测试不仅仅局限于验证方法是否被调用,还可以在调用时执行特定的逻辑,如设置返回值、抛出异常等。

行动的常见应用场景包括:

  • 设置返回值:在调用特定方法时返回一个预设值。
  • 记录调用:当模拟对象被调用时,记录某些信息。
  • 抛出异常:在某些条件下,让模拟方法抛出异常来模拟错误情况。

7.2.2 设计和实现复杂行动的策略

在设计复杂的行动时,可以使用多种gmock提供的机制,如 Action 接口、 Invoke 等。以下是一个使用 Invoke 来设置返回值的示例:

#include // 自定义行动,返回一个特定值int ReturnFourtyTwo() { return 42;}TEST(MyTestSuite, ActionExample) { MockType mock; EXPECT_CALL(mock, SomeMethod()) .WillOnce(::testing::Invoke(ReturnFourtyTwo)); int result = mock.SomeMethod(); EXPECT_EQ(42, result);}

在复杂的应用场景中,可以通过组合多个行动来构建更复杂的测试逻辑,或者使用 Return DoAll 等预定义行动来简化代码。

通过本章节的介绍,我们了解了匹配器和行动在模拟测试中的重要性和实际应用。在后续的测试实践中,可以通过具体案例进一步探索这些组件的高级用法和优化技巧。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:googletest是谷歌开发的开源C++测试框架,用于编写和执行单元测试,提供了丰富的断言和测试策略。它支持测试套件、测试用例、参数化测试、固定点、主运行器等,以及 gmock 模拟框架。 googletest 有助于提高代码质量和简化测试过程,是实现TDD和持续集成的重要工具。

本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

猜字谜