C++ 单元测试之 gtest & gmock

C++ 单元测试之 Gtest & Gmock
最近在写C++时,leader要求我们需要在代码中加入单元测试。于是自己便在互联网上包罗万象以及加入自己的理解,写成一篇文章记录下来,供自己有需要时查看。

1 单元测试简介
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。至于单元的大小或范围,并没有一个明确的标准,单元可以是一个函数、方法、类、功能模块或者子系统。C++单元测试是一种软件测试方法,用于测试代码中最小的可测试单元。单元通常是指函数、方法或类中的一个独立的功能模块。
C++单元测试的目的是验证代码的正确性,发现并修复潜在的错误。Gtest用于编写单元测试,而Gmock用于编写模拟对象(mock object)。通过编写测试用例,模拟各种输入和边界条件,验证代码的行为是否符合预期。

单元测试的一般流程:

选择测试框架:选择适合的测试框架,如GTest、CppUnit等。测试框架提供了一套API和工具,用于编写和执行测试用例。
编写测试用例:根据代码的功能和需求,编写测试用例来覆盖各种情况。测试用例应该独立、可重复和可验证。
设置测试环境:在测试开始之前,设置测试环境,包括创建对象、初始化数据等。
执行测试用例:使用测试框架执行编写的测试用例。测试框架会自动运行每个测试用例,并记录执行结果。
断言验证:在每个测试用例中,使用断言来验证代码的行为是否符合预期。断言通常是一个条件表达式,如果条件为真,则测试通过;否则,测试失败。
分析测试结果:测试框架会生成详细的测试结果报告,包括测试用例的执行情况、通过的测试数量和失败的测试数量。
修复错误:如果测试结果中有失败的测试用例,说明代码存在问题。开发者需要分析失败的原因,并修复代码中的错误。
重复测试:在修复错误后,重新执行测试用例,确保问题已经解决,并且没有引入新的问题。

单元测试强调以下几点:

语句覆盖:选择足够的测试数据,使得代码的每个语句都至少被执行一次。
判定覆盖:选择足够的测试数据,使得程序的每个判定条件都至少出现一次真值和一次假值。
条件覆盖:使得每一个判断语句的每个逻辑条件可能的值至少出现一次。
条件组合覆盖:即当一个判定条件由多个条件组合而成时,使得每个条件的各种可能的组合都出现一次。
数据覆盖:即用尽可能多的数据传入接口中进行测试。
单元测试的优点包括:
 提高代码质量:通过测试用例覆盖代码的各种情况,可以发现潜在的错误和边界条件问题,提高代码的质量和可靠性。
 便于重构和维护:当代码需要重构或者修改时,可以通过运行测试用例来验证修改是否引入新的错误。
 提高开发效率:自动化的测试过程可以减少手动测试的工作量,提高开发效率。
 促进团队合作:测试用例可以作为代码行为的规范和文档,促进团队成员之间的交流和合作。

单元测试入口:
在C++中,任何程序都需要main函数,gtest同理,下面是单元测试的入口main。

1.    #include <gtest/gtest.h>  
2.       
3.    int main(int argc, char **argv)  
4.    {  
5.        test::InitGoogleTest(&argc,argv);    //初始化gtest  
6.        return RUN_ALL_TESTS();    //运行程序中所有的测试用例  
7.    } 
1
2
3
4
5
6
7
测试用例编写:
首先我们需要有一个接口,例如以下函数:

1.    int sum(int a,int b)  
2.    {  
3.        sum = a + b;  
4.        if(sum > 100) { 
5.            return 100;  
6.       }
7.        return sum;  
8.    } 
1
2
3
4
5
6
7
8
测试用例:

1.    TEST(calculate,test_sum)  
2.    {  
3.        EXPECT_EQ(8,sum(5,3));  
4.        EXPECT_EQ(100,sum(60,40));  
5.        EXPECT_EQ(100,sum(150,50));  
6.    }  
1
2
3
4
5
6
EXPECT_EQ(expected,actual),这个断言用来判断actual与expected是否相等。expected是预期值,actual是实际值。当二者相等时,测试通过。

总而言之,C++单元测试是一种重要的软件开发实践,可以帮助开发者验证代码的正确性,提高代码质量和可维护性。

2 Gtest简介
Google Test(简称 gtest)是一个用于C++的开源测试框架,它是Google的一个开源项目,由Google开发并维护。它旨在提供一种简单而强大的方式来编写单元测试、集成测试和功能测试。gtest是一个跨平台的(Liunx、Mac OS X、Windows 、Cygwin 、Windows CE and Symbian ) C++单元测试框架,由google公司发布。gtest是为在不同平台上为编写C++测试而生成的。它提供了丰富的断言、致命和非致命判断、参数化、“死亡测试”等等。
使用 GoogleTest 时,首先要编写断言,这些断言是检查条件是否为真的语句。断言的结果可以是成功、非致命失败或致命失败。如果发生致命故障,则中止当前功能;否则程序将正常继续。测试使用断言来验证测试代码的行为。如果测试崩溃或者断言失败,那么它就失败了;否则就成功了。
以下是Google Test的一些主要特点和功能:

简单易用:Google Test提供了一个简洁的API,使得编写和运行测试用例变得非常容易。它支持各种断言和期望,可以轻松地验证代码的行为。
支持多种测试风格:Google Test支持两种主要的测试风格,即测试夹具(Test Fixture)和测试案例(Test Case)。测试夹具允许在多个测试案例之间共享设置和清理代码,而测试案例则是测试代码的最小单元。
丰富的断言:Google Test提供了多种断言,包括基本的比较断言(如EXPECT_EQ、ASSERT_TRUE等)和容器断言(如EXPECT_CONTAINER_EQ、ASSERT_THAT等),可以满足各种测试需求。
参数化测试:Google Test支持参数化测试,即在一个测试案例中多次运行相同的测试代码,但使用不同的参数。这使得开发人员可以更全面地测试代码的不同输入和边界条件。
测试覆盖率报告:Google Test可以生成测试覆盖率报告,帮助开发人员了解测试代码的覆盖情况。这对于评估测试的质量和确定需要添加的测试案例非常有用。
多平台支持:Google Test可以在多个平台上运行,包括Windows、Linux和Mac。它与不同的编译器和构建系统兼容,并且可以与其他测试框架(如Google Mock)无缝集成。
丰富的文档和社区支持:Google Test拥有详细的文档和示例,帮助开发人员快速上手并解决问题。此外,Google Test还有一个活跃的社区,可以在其中交流和获取支持。
使用gtest时,需要编写断言(assertions),断言语句会检测条件是否为真。一个断言可存在三种结果:success(成功),nonfatal failure(非致命失败),或 fatal failure(致命失败)。当出现致命失败时,终止当前函数;否则程序继续执行。

Tests使用断言语句来检测代码的结果。如果一个test出现崩溃或有一个失败的断言,则该test是fails,否则是succeeds。

2.1 断言(Assertions)
GoogleTest的断言是类似函数调用的宏。通过对类或函数的行为进行断言来进行测试。当断言失败时,GoogleTest会打印出断言的源文件和行号位置,以及一个失败信息。您还可以提供一个自定义的失败信息,它将附加在GoogleTest的消息后面。
断言以一对形式出现,测试相同的事物但对当前函数有不同的影响。
gtest中,断言的宏可以理解为分为两类,一类是ASSERT系列,一类是EXPECT系列。一个直观的解释就是:

ASSERT_* 系列的断言,当检查点失败时,退出当前函数(注意:并非退出当前案例)。
EXPECT_* 系列的断言,当检查点失败时,继续往下执行。
ASSERT_版本在失败时生成致命错误,并中止当前函数。EXPECT_版本生成非致命错误,不会中止当前函数。通常情况下,优先使用EXPECT_,因为它允许在一个测试中报告多个失败。然而,如果断言失败后继续执行不合理,应该使用ASSERT_。
要提供自定义的失败信息,只需使用 << 运算符或一系列这样的运算符将其传递给宏。请参考以下示例,使用ASSERT_EQ和EXPECT_EQ宏来验证值的相等性:
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";
for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}
1
2
3
4
这段代码是一个测试代码片段,用于比较两个向量x和y是否相等。
首先,使用ASSERT_EQ断言来比较向量x和y的大小是否相等。如果大小不相等,断言失败并输出一条错误消息:“Vectors x and y are of unequal length”。这是一个致命错误,如果断言失败,测试将立即终止。
然后,使用一个循环遍历向量的每个元素。在循环中,使用EXPECT_EQ断言来比较向量x和y在每个索引处的值是否相等。如果值不相等,断言失败并输出一条错误消息:"Vectors x and y differ at index ",后面跟着索引i的值。这是一个非致命错误,即使断言失败,测试仍会继续执行。
通过在断言宏中使用 << 运算符,可以将自定义错误消息附加到GoogleTest的默认错误消息后面。这样可以提供更详细的错误信息,以便在测试失败时更容易理解问题所在。
任何可以流式传输到ostream的内容都可以流式传输到断言宏中,特别是C字符串和字符串对象。如果将宽字符串(wchar_t*,在Windows上为UNICODE模式的TCHAR*,或std::wstring)流式传输到断言中,将在打印时转换为UTF-8。
GoogleTest提供了一系列的断言来验证代码的行为。您可以检查布尔条件,基于关系运算符比较值,验证字符串值,浮点数值等等。甚至还有断言可以通过提供自定义谓词来验证更复杂的状态。有关GoogleTest提供的完整断言列表,请参阅断言参考官方文档。

2.2 简单测试
创建一个测试的步骤如下:
使用TEST()宏来定义和命名一个测试函数。这些是普通的C++函数,不返回任何值。
在这个函数中,除了包含任何有效的C++语句外,还可以使用各种GoogleTest断言来检查值。
测试的结果由断言决定;如果测试中的任何断言失败(无论是致命的还是非致命的),或者测试崩溃,整个测试都会失败。否则,它成功。

TEST(TestSuiteName, TestName) {
...测试体...
}
1
2
3
TEST()的参数从一般到具体。第一个参数是测试套件的名称,第二个参数是测试套件内的测试名称。两个名称都必须是有效的C++标识符,并且不应包含任何下划线(_)。一个测试的完整名称由其所在的测试套件和其个别名称组成。来自不同测试套件的测试可以具有相同的个别名称。
例如,我们来看一个简单的整数函数:

int Factorial(int n); // 返回n的阶乘
1
这个函数的测试套件可能如下所示:

// 测试0的阶乘。
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(0), 1);
}
// 测试正数的阶乘。
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(1), 1);
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(3), 6);
EXPECT_EQ(Factorial(8), 40320);
}
1
2
3
4
5
6
7
8
9
10
11
GoogleTest将测试结果按照测试套件进行分组,因此逻辑相关的测试应该在同一个测试套件中;换句话说,它们TEST()的第一个参数应该相同。在上面的示例中,我们有两个测试,HandlesZeroInput和HandlesPositiveInput,它们属于同一个测试套件FactorialTest。
在命名测试套件和测试时,应遵循与命名函数和类相同的约定。
可用性:Linux、Windows、Mac。

一个test suite包括一个或多个tests。可以将多个tests分组到test suite中,以此反映所测试代码的结构。当一个test suite中的多个tests需要共享一些通用对象和子程序时,可将其放入一个test fixture class。 一个test program可包含多个test suites.

2.3 编写main()函数
大多数用户不需要编写自己的main函数,而是链接gtest_main(而不是gtest),它定义了一个适当的入口点。本节的其余部分仅适用于在测试运行之前需要进行一些无法在固定装置和测试套件框架中表达的自定义操作时。
如果你编写自己的main函数,它应该返回RUN_ALL_TESTS()的值。
你可以从以下模板开始:

#include "this/package/foo.h"
#include <gtest/gtest.h>
namespace my {
namespace project {
namespace {
// 用于测试类Foo的夹具。
class FooTest : public ::testing::Test {
protected:
// 如果它们的函数体为空,可以删除以下任何或所有函数。
FooTest() {
// 在这里可以为每个测试做一些设置工作。
}
~FooTest() override {
// 在这里可以做一些不会抛出异常的清理工作。
}
// 如果构造函数和析构函数不足以设置和清理每个测试,可以定义以下方法:
void SetUp() override {
// 这里的代码将在构造函数之后(在每个测试之前)立即调用。
}
void TearDown() override {
// 这里的代码将在每个测试之后(在析构函数之前)立即调用。
}
// 在此处声明的类成员可以被测试套件中的所有测试使用
// 用于Foo。
};
// 测试Foo::Bar()方法是否执行Abc。
TEST_F(FooTest, MethodBarDoesAbc) {
    const std::string input_filepath = "this/package/testdata/myinputfile.dat";
    const std::string output_filepath = "this/package/testdata/myoutputfile.dat";
    Foo f;
    EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0);
}
// 测试Foo是否执行Xyz。
TEST_F(FooTest, DoesXyz) {
// 测试Foo的Xyz功能。
}
} // namespace
} // namespace project
} // namespace my
int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
::testing::InitGoogleTest()函数解析命令行中的GoogleTest标志,并删除所有已识别的标志。这允许用户通过各种标志来控制测试程序的行为,我们将在高级指南中介绍这些标志。在调用RUN_ALL_TESTS()之前必须调用此函数,否则标志将无法正确初始化。
在Windows上,InitGoogleTest()也可以处理宽字符串,因此也可以在以UNICODE模式编译的程序中使用。

Google Test提供了一个基本的main()实现。只需将测试与gtest_main库链接即可。

总的来说,Google Test是一个功能强大且易于使用的C++单元测试框架。它提供了丰富的功能和灵活的测试风格,可以帮助开发者编写高质量的测试代码,并提供了测试覆盖率报告和多平台支持等特性。

3 Gmock简介
Mock,更确切地说应该是Mock Object。它究竟是什么?它有什么作用?比如当我们在单元测试、模块的接口测试时,当这个模块需要依赖另外一个或几个类,而这时这些个类还没有开发好,这时我们就可以定义了Mock对象来模拟那些类的行为。说得更直白一些,就是自己实现一个假的依赖类,对这个类的方法你想要什么行为就可以有什么行为,你想让这个方法返回什么结果就可以返回怎么样的结果。

Google Mock(简称gmock)是Google在2008年推出的一套针对C++的Mock框架,它灵感取自于jMock、EasyMock、harcreat。它提供了以下这些特性:
轻松地创建mock类
支持丰富的匹配器(Matcher)和行为(Action)
支持有序、无序、部分有序的期望行为的定义
多平台的支持

GMock是Google Test框架的一部分,它是一个用于C++的开源的模拟对象(mock object)框架。GMock旨在帮助开发人员编写可测试、可维护和可扩展的单元测试。
模拟对象是在软件测试中使用的一种特殊对象,它模拟了真实对象的行为。通过使用模拟对象,开发人员可以隔离被测试代码的依赖项,从而更容易地进行单元测试。GMock提供了一些功能来创建和操作模拟对象,以及验证模拟对象的行为。

GMock的主要特点包括:
创建模拟对象:GMock允许开发人员创建模拟对象,这些对象可以模拟真实对象的行为。通过模拟对象,开发人员可以定义对象的期望行为,并在测试中使用它们。
定义模拟对象的行为:使用GMock,开发人员可以定义模拟对象的行为,包括返回值、抛出异常和执行操作等。这使得开发人员能够模拟各种场景,以测试代码在不同情况下的行为。
验证模拟对象的行为:GMock允许开发人员验证模拟对象的行为是否符合预期。开发人员可以检查模拟对象的方法是否以期望的次数被调用,以及参数是否符合预期。
支持参数匹配:GMock支持参数匹配,开发人员可以使用参数匹配器来定义模拟对象的方法参数的期望值。这使得测试代码更加灵活,可以处理各种不同的输入。
支持模拟接口和模拟类:GMock允许开发人员创建模拟接口和模拟类。模拟接口是一个纯虚类,用于定义一组方法,而模拟类是一个实现了模拟接口的类。这使得开发人员能够模拟接口和类的行为。

使用Mock类的一般流程如下:
引入你要用到的Google Mock名称. 除宏或其它特别提到的之外所有Google Mock名称都位于testing命名空间之下.
建立模拟对象(Mock Objects).
可选的,设置模拟对象的默认动作.
在模拟对象上设置你的预期(它们怎样被调用,应该怎样回应?).

4 如何引入Gtest和Gmock?
以Visual Studio 2022为例,接下来介绍一下怎么在Windows 10/11和VS2022环境之下使用gtest和gmock对代码进行单元测试。

1.首先打开Visual Studio 2022,新建一个解决方案,然后在解决方案下创建我们的项目。


2.鼠标右键单击“test”项目,选择“管理NuGet程序包(N) ”。


3.在“浏览”下,搜索框中输入gtest,便能看到gtest和gmock的程序包。点击安装,若出现弹窗,点击“OK”即可。需等待一定的时间。

将gtest和gmock下载完成是这样的。会在资源文件夹下生成两个.cc文件,如下图所示。


4.下载完成之后。便能够在我们的代码里面使用:

 #include <gtest/gtest.h>
 #include <gmock/gmock.h>
1
2
将头文件导入了。


5.然后就是在代码中编写我们的测试代码,以我这段测试延时队列的代码为例:

1.    #include <gtest/gtest.h>
     #include <gmock/gmock.h>
2.    // 定义一个模拟的线程池类
3.    class MockThreadPool {
4.    public:
5.        MOCK_METHOD(void, EnQueue, (std::function<void()>), ()); // 模拟EnQueue方法
6.    };
7.    
8.    // 定义一个模拟的延迟队列类
9.    class MockDelayedQueue {
10.    public:
11.        MOCK_METHOD(void, AddTask, (std::function<void()>, std::chrono::milliseconds), ()); // 模拟AddTask方法
12.    };
13.    
14.    // 定义一个测试用例ThreadPoolTest,测试EnQueue方法
15.    TEST(ThreadPoolTest, EnQueueTest)
16.    {
17.        MockThreadPool mockThreadPool; // 创建一个模拟的线程池对象
18.        EXPECT_CALL(mockThreadPool, EnQueue(_)).Times(3); // 设置对EnQueue方法的期望调用次数为3次
19.        mockThreadPool.EnQueue({}); // 调用EnQueue方法添加一个空任务到线程池
20.        mockThreadPool.EnQueue({}); // 调用EnQueue方法添加一个空任务到线程池
21.        mockThreadPool.EnQueue({}); // 调用EnQueue方法添加一个空任务到线程池
22.    
23.        std::chrono::ThreadPool threadPool(4); // 创建一个真实的线程池对象,最大线程数为4
24.        std::atomic<int> count(0); // 创建一个原子整型变量,用于记录任务的执行次数
25.    
26.        for (int i = 0; i < 10; ++i) {
27.            threadPool.EnQueue([&count]() { // 将一个lambda函数加入线程池中
28.                count++; // 执行任务,将count加1
29.                });
30.        }
31.    
32.        std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待1秒,确保任务执行完成
33.        EXPECT_EQ(count.load(), 10); // 断言count的值是否为10,即任务是否全部执行完成
34.    }
35.    
36.    // 定义一个测试用例DelayedQueueTest,测试AddTask方法
37.    TEST(DelayedQueueTest, AddTaskTest)
38.    {
39.        MockDelayedQueue mockDelayedQueue; // 创建一个模拟的延迟队列对象
40.        EXPECT_CALL(mockDelayedQueue, AddTask(_, _)).Times(3); // 设置对AddTask方法的期望调用次数为3次
41.        mockDelayedQueue.AddTask({}, std::chrono::milliseconds(100)); // 调用AddTask方法添加一个延迟任务,延迟时间为100毫秒
42.        mockDelayedQueue.AddTask({}, std::chrono::milliseconds(200)); // 调用AddTask方法添加一个延迟任务,延迟时间为200毫秒
43.        mockDelayedQueue.AddTask({}, std::chrono::milliseconds(300)); // 调用AddTask方法添加一个延迟任务,延迟时间为300毫秒
44.    
45.        std::chrono::DelayedQueue queue(4); // 创建一个真实的延迟队列对象,最大线程数为4
46.        std::atomic<int> count(0); // 创建一个原子整型变量,用于记录任务的执行次数
47.    
48.        queue.AddTask([&count]() { // 将一个lambda函数加入延迟队列中
49.            count++; // 执行任务,将count加1
50.            }, std::chrono::seconds(1)); // 设置延迟时间为1秒
51.    
52.        std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待2秒,确保任务执行完成
53.        EXPECT_EQ(count.load(), 1); // 断言count的值是否为1,即任务是否执行完成
54.    }
55.    
56.    } // namespace chrono
57.    } // namespace std
58.    
59.    int main(int argc, char** argv)
60.    {
61.        testing::InitGoogleTest(&argc, argv); // 初始化gtest
62.        testing::InitGoogleMock(&argc, argv); // 初始化gmock
63.        return RUN_ALL_TESTS(); // 运行所有测试用例
64.    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
点击运行之后,程序执行结果如下:

这段单元测试代码的结果显示了两个测试套件中的测试情况。
第一个测试套件是ThreadPoolTest,其中有一个测试用例EnQueueTest。该测试用例执行成功,用时1025毫秒。
第二个测试套件是DelayedQueueTest,其中有一个测试用例AddTaskTest。该测试用例执行成功,用时2020毫秒。
整个测试过程的总时间为3047毫秒。
总共有2个测试用例,全部通过。

5 如何使用?如何编写测试代码?
下面是一个使用gtest编写的简单的单元测试例子:

 1.     #include <gtest/gtest.h>
2.    
3.    // 要测试的函数
4.    int add(int a, int b)
5.    {
6.        return a + b;
7.    }
8.    
9.    // 测试用例
10.    TEST(AddTest, PositiveNumbers)
11.    {
12.        // 调用待测试的函数
13.        int result = add(2, 3);
14.        
15.        // 使用断言函数判断结果是否符合预期
16.        EXPECT_EQ(result, 5);
17.    }
18.    
19.    TEST(AddTest, NegativeNumbers)
20.    {
21.        int result = add(-2, -3);
22.        EXPECT_EQ(result, -5);
23.    }
24.    
25.    TEST(AddTest, Zero)
26.    {
27.        int result = add(0, 0);
28.        EXPECT_EQ(result, 0);
29.    }
30.    
31.    // 运行所有的测试用例
32.    int main(int argc, char** argv)
33.    {
34.        // 初始化gtest
35.        ::testing::InitGoogleTest(&argc, argv);
36.        
37.        // 运行所有的测试用例
38.        return RUN_ALL_TESTS();
39.    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
上述代码定义了一个add函数,然后使用gtest编写了三个测试用例,分别测试了正数相加、负数相加和零相加的情况。每个测试用例使用TEST宏定义,并在其中调用待测试的函数,并使用EXPECT_EQ宏断言函数来判断结果是否符合预期。 运行测试时,通过调用InitGoogleTest函数初始化gtest,并通过RUN_ALL_TESTS函数运行所有的测试用例。
下面是一个使用gmock编写的简单的模拟对象例子:

1.    #include <gtest/gtest.h>
2.    #include <gmock/gmock.h>
3.    
4.    // 要模拟的接口
5.    class Database {
6.    public:
7.        virtual ~Database() {}
8.        virtual bool connect() = 0;
9.        virtual void disconnect() = 0;
10.        virtual int query(const std::string& sql) = 0;
11.    };
12.    
13.    // 模拟对象
14.    class MockDatabase : public Database {
15.    public:
16.        MOCK_METHOD0(connect, bool());
17.        MOCK_METHOD0(disconnect, void());
18.        MOCK_METHOD1(query, int(const std::string& sql));
19.    };
20.    
21.    // 使用模拟对象的测试用例
22.    TEST(DatabaseTest, QueryTest)
23.    {
24.        // 创建模拟对象
25.        MockDatabase mockDb;
26.        
27.        // 设置对模拟函数的期望调用和返回值
28.        EXPECT_CALL(mockDb, connect())
29.            .WillOnce(::testing::Return(true));
30.        EXPECT_CALL(mockDb, query("SELECT * FROM users"))
31.            .WillOnce(::testing::Return(5));
32.        EXPECT_CALL(mockDb, disconnect());
33.    
34.        // 调用待测试的函数
35.        int result = queryUserCount(&mockDb);
36.    
37.        // 使用断言函数判断结果是否符合预期
38.        EXPECT_EQ(result, 5);
39.    }
40.    
41.    // 待测试的函数
42.    int queryUserCount(Database* db)
43.    {
44.        db->connect();
45.        int count = db->query("SELECT * FROM users");
46.        db->disconnect();
47.        return count;
48.    }
49.    
50.    // 运行所有的测试用例
51.    int main(int argc, char** argv)
52.    {
53.        // 初始化gmock
54.        ::testing::InitGoogleMock(&argc, argv);
55.        
56.        // 运行所有的测试用例
57.        return RUN_ALL_TESTS();
58.    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
上述代码定义了一个Database接口,其中有三个纯虚函数,分别用于连接数据库、断开连接和执行查询操作。然后使用gmock定义了一个MockDatabase类,继承自Database,并使用MOCK_METHODX宏定义了三个模拟函数。
在测试用例中,使用EXPECT_CALL宏来设置对模拟函数的期望调用,并使用WillOnce宏来指定模拟函数的返回值。然后在待测试的函数中调用模拟对象的函数。
运行测试时,通过调用InitGoogleMock函数初始化gmock,并通过RUN_ALL_TESTS函数运行所有的测试用例。

参考和分享链接
[1] Gtest官方文档:https://google.github.io/googletest/
[2] 关于Gmock这里有有一份比较清晰的文章:转一篇小亮同学的google mock分享 - welkinwalker - 博客园 (cnblogs.com)
[3] Gtest和Gmock的github源码地址:GitHub - google/googletest: GoogleTest - Google Testing and Mocking Framework

————————————————
版权声明:本文为CSDN博主「比特熊猫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_46392823/article/details/132622803

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值