GoogleTest使用教程_googletest教程

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

add_executable(hello_test hello_test.cc)
target_link_libraries(hello_test GTest::gtest_main)

include(GoogleTest)
gtest_add_tests(TARGET hello_test)


其中`GTest::gtest_main`是GoogleTest定义的构建目标(源代码[gtest\_main.cc](https://bbs.csdn.net/topics/618679757)),包含测试程序入口,因此不需要自己编写`main()`函数,只需与该目标链接即可。


项目目录结构如下:



googletest-demo/
CMakeLists.txt
hello_test.cc


最后构建并运行测试,在项目根目录下执行以下命令:



$ cmake -S . -B build
– The C compiler identification is GNU 7.4.0
– The CXX compiler identification is GNU 7.4.0

– Build files have been written to: …/googletest-demo/build

$ cmake --build build

[100%] Built target gmock_main

$ cd build && ctest
Test project …/googletest-demo/build
Start 1: HelloTest.BasicAssertions
1/1 Test #1: HelloTest.BasicAssertions … Passed 0.02 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) = 0.13 sec


其中第一行命令用于配置构建系统,解析CMakeLists.txt并生成构建文件,同时自动克隆googletest仓库到build/\_deps/googletest-src/目录下,头文件<gtest/gtest.h>位于该目录下的googletest/include/目录中。第二行命令用于执行编译链接操作,生成构建目标。第三行命令用于执行测试程序并报告测试结果。


注:以上方法通过CMake提供的`FetchContent`模块自动管理GoogleTest源代码,也可以先单独安装GoogleTest再手动添加到CMake项目中,参考[googletest README - Standalone CMake Project](https://bbs.csdn.net/topics/618679757)


## 4.断言


GoogleTest断言是类似于函数调用的宏,用于测试类或函数的行为。当断言失败时,GoogleTest将打印断言所在的源文件、行数以及错误信息。


每个断言都有两种版本:`ASSERT_*`版本的失败是致命失败,`EXPECT_*`版本的失败是非致命失败。


GoogleTest提供了一组断言,用于检查布尔值、使用比较运算符比较两个值、比较字符串以及浮点数等。所有断言都定义在头文件<gtest/gtest.h>中。


常用断言如下(每个断言都有对应的`ASSERT_*`版本,这里省略):




| 断言 | 验证条件 |
| --- | --- |
| `EXPECT_TRUE(condition)` | `condition`为真 |
| `EXPECT_FALSE(condition)` | `condition`为假 |
| `EXPECT_EQ(val1, val2)` | `val1 == val2` |
| `EXPECT_NE(val1, val2)` | `val1 != val2` |
| `EXPECT_LT(val1, val2)` | `val1 < val2` |
| `EXPECT_LE(val1, val2)` | `val1 <= val2` |
| `EXPECT_GT(val1, val2)` | `val1 > val2` |
| `EXPECT_GE(val1, val2)` | `val1 >= val2` |
| `EXPECT_STREQ(str1, str2)` | C字符串`str1`和`str2`相等 |
| `EXPECT_STRNE(str1, str2)` | C字符串`str1`和`str2`不相等 |
| `EXPECT_STRCASEEQ(str1, str2)` | C字符串`str1`和`str2`相等,忽略大小写 |
| `EXPECT_STRCASENE(str1, str2)` | C字符串`str1`和`str2`不相等,忽略大小写 |
| `EXPECT_FLOAT_EQ(val1, val2)` | 两个`float`值`val1`和`val2`近似相等 |
| `EXPECT_DOUBLE_EQ(val1, val2)` | 两个`double`值`val1`和`val2`近似相等 |
| `EXPECT_NEAR(val1, val2, abs_error)` | `val1`和`val2`之差的绝对值不超过`abs_error` |
| `EXPECT_THROW(statement, exception_type)` | `statement`抛出`exception_type`类型的异常 |
| `EXPECT_ANY_THROW(statement)` | `statement`抛出任何类型的异常 |
| `EXPECT_NO_THROW(statement)` | `statement`不抛出任何异常 |
| `EXPECT_THAT(val, matcher)` | `val`满足匹配器`matcher` |


完整参考列表:[Assertions Reference](https://bbs.csdn.net/topics/618679757)


断言宏返回一个`ostream`对象,可以使用`<<`运算符输出自定义的失败信息。例如:



EXPECT_TRUE(my_condition) << “My condition is not true”;


## 5.简单测试


[Simple Tests](https://bbs.csdn.net/topics/618679757)


`TEST()`宏用于定义一个测试,语法如下:



TEST(TestSuiteName, TestName) {
test body
}


其中第一个参数是测试套件名称,第二个参数是测试用例名称,二者都必须是合法的C++标识符,并且不应该包含下划线。


测试体可以包含断言和任何C++语句。如果任何断言失败或者崩溃,则整个测试失败,否则成功。


注:`TEST()`宏实际上定义了一个名为`TestSuiteName_TestName_Test`的类,该类继承了`::testing::Test`类并覆盖了成员函数`TestBody()`,测试体就是其函数体。其(简化的)定义如下:



#define TEST(TestSuiteName, TestName)
class TestSuiteName##_##TestName##_Test : public ::testing::Test {
private:
void TestBody() override;
};
void TestSuiteName##_##TestName##_Test::TestBody()


### 5.1 示例


假设有一个计算阶乘的函数:


factorial.h



#pragma once

// Returns the factorial of n
int Factorial(int n);


factorial.cc



#include “factorial.h”

int Factorial(int n) {
int p = 1;
for (int i = 1; i <= n; ++i)
p *= i;
return p;
}


可以针对该函数编写测试:


factorial\_test.cc



#include <gtest/gtest.h>

#include “factorial.h”

// Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(Factorial(0), 1);
}

// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(Factorial(1), 1);
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(3), 6);
EXPECT_EQ(Factorial(8), 40320);
}


上述示例定义了一个名为`FactorialTest`的测试套件,其中包含`HandlesZeroInput`和`HandlesPositiveInput`两个测试用例。


在第3节目录结构的基础上,将上述三个文件放在factorial目录下,在根目录下的CMakeLists.txt文件结尾添加一行:



add_subdirectory(factorial)


factorial/CMakeLists.txt内容如下:



add_library(factorial factorial.cc)
add_executable(factorial_test factorial_test.cc)
target_link_libraries(factorial_test factorial GTest::gtest_main)
gtest_add_tests(TARGET factorial_test)


目录结构如下:



googletest-demo/
CMakeLists.txt
factorial/
CMakeLists.txt
factorial.h
factorial.cc
factorial_test.cc


测试结果如下:



$ cmake -S . -B build
$ cmake --build build
$ cd build && ctest -R FactorialTest
Test project …/googletest-demo/build
Start 1: FactorialTest.HandlesZeroInput
1/2 Test #1: FactorialTest.HandlesZeroInput … Passed 0.00 sec
Start 2: FactorialTest.HandlesPositiveInput
2/2 Test #2: FactorialTest.HandlesPositiveInput … Passed 0.00 sec

100% tests passed, 0 tests failed out of 2

Total Test time (real) = 0.01 sec


## 6.测试夹具


[Test Fixtures](https://bbs.csdn.net/topics/618679757)


测试夹具(text fixture)可以让多个测试用例共用相同的对象或数据。要创建一个fixture,只需继承`::testing::Test`类,在类中定义要使用的对象,在默认构造函数或`SetUp()`函数中进行初始化,在析构函数或`TearDown()`函数中进行清理(释放资源),此外还可以定义需要共用的函数。如下所示:



// The fixture for testing class Foo.
class FooTest : public ::testing::Test {
protected:
FooTest() {
// You can do set-up work for each test here.
}

~FooTest() override {
    // You can do clean-up work that doesn't throw exceptions here.
}

void SetUp() override {
    // Code here will be called immediately after the constructor (right before each test).
}

void TearDown() override {
    // Code here will be called immediately after each test (right before the destructor).
}

// Class members declared here can be used by all tests in the test suite for Foo.

};


要使用fixture类,使用`TEST_F()`宏而不是`TEST()`来定义测试:



TEST_F(TestFixtureName, TestName) {
test body
}


其中`TestFixtureName`既是fixture类名,也是测试套件名,在测试体中可以使用fixture类定义的数据成员。


对于每一个使用`TEST_F()`定义的测试,GoogleTest都会创建一个**新的** fixture对象,调用`SetUp()`初始化,运行测试,调用`TearDown()`清理,最后删除fixture对象。同一个测试套件中的不同测试使用不同的fixture对象,因此一个测试所做的改变不影响其他测试。


注:`TEST_F()`宏与`TEST()`唯一的区别是定义的类继承fixture类而不是`::testing::Test`:



#define TEST_F(TestFixtureName, TestName)
class TestFixtureName##_##TestName##_Test : public TestFixtureName {
private:
void TestBody() override;
};
void TestFixtureName##_##TestName##_Test::TestBody()


### 6.1 示例


首先编写一个队列类`Queue`(文档中并未给出实现,这里直接使用标准库`deque`类实现):


queue.h



#pragma once

#include
#include

template
class Queue {
public:
~Queue();
void Enqueue(const E& element);
E* Dequeue();
size_t size() const;
private:
std::deque<E*> q_;
};

template
Queue::~Queue() {
while (!q_.empty()) {
delete q_.front();
q_.pop_front();
}
}

template
void Queue::Enqueue(const E& element) {
q_.push_back(new E(element));
}

template
E* Queue::Dequeue() {
if (q_.empty())
return nullptr;
E* e = q_.front();
q_.pop_front();
return e;
}

template
size_t Queue::size() const {
return q_.size();
}


queue.cc



#include “queue.h”


注:类模板的成员函数必须在头文件中定义,本来不需要源文件,但在CMakeLists.txt的`add_library()`命令中使用头文件会报错:



CMake Error: Cannot determine link language for target “queue”.
CMake Error: CMake can not determine linker language for target: queue


下面针对`Queue`类编写fixture和测试:


queue\_test.cc



#include <gtest/gtest.h>

#include “queue.h”

class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}

Queue<int> q0_, q1_, q2_;

};

TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(q0_.size(), 0);
}

TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(n, nullptr);

n = q1_.Dequeue();
ASSERT\_NE(n, nullptr);
EXPECT\_EQ(\*n, 1);
EXPECT\_EQ(q1_.size(), 0);
delete n;

n = q2_.Dequeue();
ASSERT\_NE(n, nullptr);
EXPECT\_EQ(\*n, 2);
EXPECT\_EQ(q2_.size(), 1);
delete n;

}


在第3节目录结构的基础上,将上述三个文件放在queue目录下,在根目录下的CMakeLists.txt文件结尾添加一行:



add_subdirectory(queue)


queue/CMakeLists.txt内容如下:



add_library(queue queue.cc)
add_executable(queue_test queue_test.cc)
target_link_libraries(queue_test queue GTest::gtest_main)
gtest_add_tests(TARGET queue_test)


目录结构如下:



googletest-demo/
CMakeLists.txt
queue/
CMakeLists.txt
queue.h
queue.cc
queue_test.cc


测试结果如下:



$ cmake -S . -B build
$ cmake --build build
$ cd build && ctest -R QueueTest
Test project …/googletest-demo/build
Start 1: QueueTest.IsEmptyInitially
1/2 Test #1: QueueTest.IsEmptyInitially … Passed 0.00 sec
Start 2: QueueTest.DequeueWorks
2/2 Test #2: QueueTest.DequeueWorks … Passed 0.01 sec

100% tests passed, 0 tests failed out of 2

Total Test time (real) = 0.01 sec


## 7.模拟对象


[gMock for Dummies](https://bbs.csdn.net/topics/618679757)


在测试中使用真实对象有时是不可行的,因为真实对象依赖昂贵的、不可靠的资源(例如数据库、网络连接等)使得测试变慢或不稳定。**模拟对象**(mock object)与真实对象实现相同的接口,但可以在运行时指定**它将被如何使用**(调用什么方法、以什么参数、调用多少次、以什么顺序)以及**它应该做什么**(返回什么值)。


**假对象**(fake object)与模拟对象的区别:


* 假对象具有可用的实现,但采取了一些捷径(为了降低操作成本),例如内存文件系统;
* 模拟对象预先设定了期望接收到的调用,同时也可以指定调用时执行的动作。


二者最重要的区别是模拟对象允许你验证它和使用它的代码之间的交互方式。


个人理解:


* 假对象的作用是“替换行为”,而模拟对象既可以“替换行为”又可以“验证调用”。
* “验证调用”的基本思想是:“**相信底层接口是正确的,只需验证是否调用了正确的接口**”,这里的“底层接口”可能是系统调用、其他模块、第三方库等已经被测试或不需要在这里测试的代码。


**gMock**是一个C++ mock框架,用于解决C++中使用模拟对象困难的问题,类似于Java的jMock/EasyMock、Python的`unittest.mock`、Go的gomock。GoogleTest已经包含了gMock。


### 7.1 示例


假设正在开发一个画图程序。要想测试程序是否正确,可以对比屏幕上的绘制结果和正确的屏幕截图,但这种方式太繁琐、难以维护。实际上,在测试中不需要真正调用系统接口在屏幕上绘制图形,只需验证是否调用了正确的接口即可。


#### 7.1.1 接口定义


假设程序使用的画图接口`Turtle`如下(类似于Python的[turtle](https://bbs.csdn.net/topics/618679757)模块):


turtle.h



#pragma once

class Turtle {
public:
virtual ~Turtle() = default;

virtual void PenUp() = 0;
virtual void PenDown() = 0;
virtual void Forward(int distance) = 0;
virtual void Circle(int radius) = 0;
virtual void Turn(int degrees) = 0;
virtual void GoTo(int x, int y) = 0;
virtual void Head(int angle) = 0;
virtual int GetX() const = 0;
virtual int GetY() const = 0;

};


注意:`Turtle`的析构函数**必须**是虚函数,否则通过基类指针删除对象时派生类的析构函数不会被调用,从而对象无法被正确销毁。


该接口提供了控制一支画笔(可以想像为一只乌龟)的方式,可以使用`PenUp()`和`PenDown()`绘制轨迹,使用`Forward()`、`Circle()`、`Turn()`、`GoTo()`和`Head()`控制移动,使用`GetX()`和`GetY()`获取当前位置。


画图程序会使用该接口的真实实现(需要调用底层图形库接口),但在测试中使用mock实现,从而可以验证画图程序是否以正确的参数、正确的顺序调用了正确的接口,而不需要真正调用底层接口,使得测试更快、更加健壮、易于维护。


#### 7.1.2 编写mock类


为`Turtle`接口编写mock类的步骤如下:


* 从`Turtle`派生一个类`MockTurtle`;
* 选择想要mock的**虚函数**;
* 在子类的`public:`部分对每个要mock的函数编写一个`MOCK_METHOD()`宏;
* 将函数签名复制粘贴到宏参数中,并分别在返回类型和函数名之间以及函数名和参数表之间添加一个逗号;
* 对于`const`成员函数,添加第4个宏参数`(const)`;
* 覆盖虚函数建议添加`override`关键字:对于非`const`成员函数,第4个宏参数为`(override)`;对于`const`成员函数,第4个宏参数为`(const, override)`。


`MockTurtle`类的完整定义如下:


mock\_turtle.h



#pragma once

#include <gmock/gmock.h>

#include “turtle.h”

class MockTurtle : public Turtle {
public:
~MockTurtle() override = default;

MOCK\_METHOD(void, PenUp, (), (override));
MOCK\_METHOD(void, PenDown, (), (override));
MOCK\_METHOD(void, Forward, (int distance), (override));
MOCK\_METHOD(void, Circle, (int radius), (override));
MOCK\_METHOD(void, Turn, (int degrees), (override));
MOCK\_METHOD(void, GoTo, (int x, int y), (override));
MOCK\_METHOD(void, Head, (int angle), (override));
MOCK\_METHOD(int, GetX, (), (const, override));
MOCK\_METHOD(int, GetY, (), (const, override));

};


`MOCK_METHOD()`宏会生成函数的定义。


#### 7.1.3 在测试中使用mock类


在测试中使用mock类的典型方式如下:


* 创建mock对象;
* 指定期望的调用方式(什么方法、以什么参数、被调用几次等),同时也可以指定方法被调用时的行为;
* 在被测函数中使用mock对象,同时也可以使用断言检查函数结果;
* 当mock对象被销毁时(测试函数返回前),gMock会自动检查期望的调用是否满足,如果不满足测试将会失败。


假设画图程序的一部分`Painter`类利用`Turtle`接口实现了画直线、画长方形和画圆的三个功能:


painter.h



#pragma once

#include “turtle.h”

class Painter {
public:
explicit Painter(Turtle* turtle): turtle_(turtle) {}
bool DrawLine(int x1, int y1, int x2, int y2);
bool DrawRectangle(int x, int y, int length, int width);
bool DrawCircle(int x, int y, int r);
private:
Turtle* turtle_;
};


painter.cc



#include “painter.h”

bool Painter::DrawLine(int x1, int y1, int x2, int y2) {
turtle_->GoTo(x1, y1);
turtle_->PenDown();
turtle_->GoTo(x2, y2);
turtle_->PenUp();
return true;
}

bool Painter::DrawRectangle(int x, int y, int length, int width) {
if (length <= 0 || width <= 0)
return false;
turtle_->GoTo(x, y);
turtle_->Head(270);
turtle_->PenDown();
turtle_->Forward(width);
turtle_->Turn(90);
turtle_->Forward(length);
turtle_->Turn(90);
turtle_->Forward(width);
turtle_->Turn(90);
turtle_->Forward(length);
turtle_->PenUp();
return true;
}

bool Painter::DrawCircle(int x, int y, int r) {
if (r <= 0)
return false;
turtle_->GoTo(x, y - r);
turtle_->Head(0);
turtle_->PenDown();
turtle_->Circle®;
turtle_->PenUp();
return true;
}


下面针对`DrawCircle()`函数编写一个简单的测试:


painter\_test.cc



#include <gmock/gmock.h>

#include “painter.h”
#include “mock_turtle.h”

using ::testing::AtLeast;

TEST(PainterTest, DrawCircle) {
MockTurtle turtle;
EXPECT_CALL(turtle, PenDown()).Times(AtLeast(1));

Painter painter(&turtle);
EXPECT\_TRUE(painter.DrawCircle(0, 0, 10));

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

true;
}


下面针对`DrawCircle()`函数编写一个简单的测试:


painter\_test.cc



#include <gmock/gmock.h>

#include “painter.h”
#include “mock_turtle.h”

using ::testing::AtLeast;

TEST(PainterTest, DrawCircle) {
MockTurtle turtle;
EXPECT_CALL(turtle, PenDown()).Times(AtLeast(1));

Painter painter(&turtle);
EXPECT\_TRUE(painter.DrawCircle(0, 0, 10));

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
[外链图片转存中…(img-HTLzHkae-1715881701037)]
[外链图片转存中…(img-utGdb9Ld-1715881701037)]

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Google Test(简称gtest)是由Google开发的一个C++测试框架,用于编写和运行单元测试、集成测试和功能测试。下面是一个简单的使用教程: 步骤1:下载和安装Google Test库 你可以从Google Test的官方仓库(https://github.com/google/googletest)下载最新的源代码。将源代码放在你的项目目录中,并将其编译为可执行文件或库。 步骤2:创建测试文件 在你的项目中创建一个新的测试文件,通常以"_test.cc"为后缀。例如,你可以创建一个名为"mytest_test.cc"的文件。 步骤3:编写测试用例 在测试文件中,你可以使用TEST宏定义来定义一个测试用例。一个测试用例可以包含多个测试。例如: ```cpp #include <gtest/gtest.h> TEST(MyTestSuite, Test1) { // 测试代码 ASSERT_EQ(2 + 2, 4); } TEST(MyTestSuite, Test2) { // 测试代码 ASSERT_TRUE(true); } ``` 步骤4:编写测试代码 在每个测试用例中,你可以使用Google Test提供的断言宏来编写测试代码。常用的断言有ASSERT_EQ、ASSERT_TRUE、ASSERT_FALSE等,用于对表达式进行断言判断。 步骤5:运行测试 编译并运行你的测试代码。Google Test会自动运行所有的测试用例,并输出测试结果。 以上是Google Test的基本使用教程,你可以根据需要深入学习和使用更多的功能和特性。更详细的文档和示例可以在Google Test的官方网站(https://github.com/google/googletest)上找到。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值