距离上次更新本文已经过去了 200 天,文章部分内容可能已经过时,请注意甄别

gtest 模块的安装参考站内教程 ubuntu 安装 google gtest

本文使用的 gtest 版本为 1.14.0;

1.gtest 是用来干嘛的?

google gtest 是一个 c++ 的单元测试模块,它提供了一系列规范化的宏,来帮助我们进行函数的单元测试。

image.png

单元测试你可以理解为测试我们编写好的每个函数模块,确保这些函数的功能不会影响其他函数,并保证函数能依照预期的功能进行工作。

要知道,绝大部分的软件 bug 都是在设计、初次代码编写的过程中产生的,比如你写了一个 add 函数,但是错误的将运算符写成了 -,add 就变成 sub 函数了,如果这个 add 函数在其他功能模块完成之后才发现被写错,就闹大笑话了。

当然,上面这个 add 的例子并不合适,因为它太简单了。但实际项目中,就是由多个很简单的代码聚合而成的一个大软件模块。每一个看上去简单、不可能写错的地方,都有可能隐藏的出错的危险。

所以,这就需要我们在完成每个功能函数的编写后,通过单元测试来判断函数是否有问题。

2.gtest 的代码基本框架

一般情况下,多模块的软件项目都会用 cmake 来实现批量化的编译和单元测试的运行。但本文只是对 gtest 模块使用的最基本教程,再加上我并没有学习 cmake 的使用,所以暂时使用 g++ 直接对单模块进行编译并介绍 gtest 的测试宏。

Testing Reference | GoogleTest

比较常用的是 TEST 和 TEST_F 这两个测试宏,更多测试宏请参考官方文档中的介绍。

2.1 TEST 单元测试模块

在 gtest 中,一个单元测试模块长下面这样

cpp
1
2
3
TEST(TestSuiteName, TestName) {
... statements ...
}

你可以理解为,左侧是被测模块的名字,右侧是在这个被测模块中的某个测试的名字,statements 是任意被测模块的代码;gtest 框架建议使用大驼峰的命名方式,TEST 的命名中不要带有_下划线。

比如我有一个模块 A 的单元测试,那么左侧可以填写为模块ATest,右侧填写为类中某个函数的测试。这两个名字可以随便起,但是在同一个 TestSuiteName 中不能有两个相同的 TestName;

一个单元测试模块的成功与失败取决于内部定义的 gtest 断言宏,参考后文的介绍。

2.2 TEST_F 类测试模块

上述的 TEST 方式是用于测试普通函数的,还有一个 TEST_F 可以用于对类进行测试

cpp
1
2
3
TEST_F(TestFixtureName, TestName) {
... statements ...
}

此时左侧的 TestFixtureName 不再是随便起的了,你必须定义一个继承于 testing::Test 的测试类,该类可以定义成员变量或对被测目标进行初始化、销毁操作。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClassTest : public testing::Test {
protected:
void SetUp() // 初始化,在每个TEST_F中都会被调用
{}

void TearDown() // 销毁,在每个TEST_F结束时都会调用
{}

// 可以定义一些成员变量,在TEST_F中能访问
// 这些变量的声明周期是和SetUp/TearDown一致的
// 每一个TEST_F里面这些变量的构造/析构都会被调用
int _a;
};

最终的测试代码应该是下面这样的

cpp
1
2
3
4
5
6
class MyClassTest : public testing::Test {
...
};

TEST_F(MyClassTest, HasPropertyA) { ... }
TEST_F(MyClassTest, HasPropertyB) { ... }

2.3 简单示例

下面是一个简单的 gtest 单元测试编写的示例,包含测试单元体和 main 函数。一个文件里面可以写 N 个 TEST 或 TEST_F,它们会按顺序执行。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <gtest/gtest.h>

// 一个函数测试
// 左侧是测试模块的名字,右侧是该模块测试的目的
TEST(ADDTEST, ADDTEST_TRUE)
{}

int main(int argc, char **argv)
{
// 主函数
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

使用如下 g++ 命令编译该代码,注意 gtest 是一个动态库,需要使用 -lgtest 进行链接。

plaintext
1
g++ test.cpp -o test -lgtest

3.gtest 提供的断言宏

官方文档:Assertions Reference | GoogleTest

gtest 中提供的宏分为 ASSERT 和 EXPECT 两种,其中 ASSERT 宏会在检查到错误的时候直接终止单元测试用例的运行(注意是单个 TEST/TEST_F 单元测试),而 EXPECT 不会。

cpp
1
2
3
4
5
6
7
8
9
10
11
TEST(EXP,EXP1)
{
ASSERT_EQ(1,1); // 如果这个出错了,后续不会执行
// 这里不会被执行
}

TEST(EXP,EXP2)
{
EXPECT_EQ(1,1); // 如果这个出错了,还是会继续往后执行这个模块的其他代码。
// 这里的代码能被执行
}

单元测试运行结束后,gtest 会给出测试失败的模块汇总。

3.1 相等 / 大小判断

ASSERT 宏 EXPECT 宏功能参数个数
ASSERT_TRUEEXPECT_TRUE 判真 1
ASSERT_FALSEEXPECT_FALSE 判假 1
ASSERT_EQEXPECT_EQ 相等 2
ASSERT_NEEXPECT_NE 不相等 2
ASSERT_GTEXPECT_GT 第一个参数是否大于第二个参数 2
ASSERT_LTEXPECT_LT 小于(原理同上)2
ASSERT_GEEXPECT_GE 大于等于 2
ASSERT_LEEXPECT_LE 小于等于 2
ASSERT_FLOAT_EQEXPECT_FLOAT_EQ 单精度浮点数相等 2
ASSERT_DOUBLE_EQEXPECT_DOUBLE_EQ 双精度浮点数相等 2
ASSERT_NEAREXPECT_NEAR 浮点数是否接近(第三个参数为允许的误差值)3
ASSERT_STREQEXPECT_STREQC 字符串相等 2
ASSERT_STRNEEXPECT_STRNEC 字符串不相等 2
ASSERT_STRCASEEQEXPECT_STRCASEEQC 字符串相等(忽略大小写)2
ASSERT_STRCASENEEXPECT_STRCASENEC 字符串不相等(忽略大小写)2
ASSERT_PRED1EXPECT_PRED1 自定义谓词测试(有 1 到 5 级,对应不同参数个数的自定义函数)2
ASSERT_THATEXPECT_THAT 判断函数返回值是否符合给定的 matcher(gtest 提供的)2

3.2 异常相关

除了上述这种判断大小和相等的宏,还有和异常以及程序崩溃相关的宏

ASSERT 宏 EXPECT 宏作用参数个数
ASSERT_THROWEXPECT_THROW 期待抛出指定异常,第一个参数是目标函数,第二个参数是异常类型 2
ASSERT_ANY_THROWEXPECT_ANY_THROW 期待抛出任何异常 1
ASSERT_NO_THROWEXPECT_NO_THROW 不希望抛出任何异常 1
ASSERT_EXITEXPECT_EXIT 期望程序以指定错误码 exit,且标准错误输出符合第三个参数的 regex 表达式 3
ASSERT_DEATHEXPECT_DEATH 期望程序错误退出(退出码非 0),且标准错误输出符合第二个参数的 regex 表达式 2
ASSERT_DEBUG_DEATHEXPECT_DEBUG_DEATH 同上,但是在调试模式下测试;非调试模式下只会执行函数,不做判断。2
ASSERT_DEATH_IF_SUPPORTEDEXPECT_DEATH_IF_SUPPORTED 同 DEATH,但是只有在支持的时候才会被调用,如果不支持则什么都不做。2

3.3 直接表明成功和失败

还有两个宏是直接表明该单元测试是否成功,以及是否失败的。主动调用这两个宏会提前终止该单元测试用例。

c
1
2
SUCCEED(); // 成功
FAIL(); // 失败

3.4 添加失败信息

有的时候 gtest 默认提供的 ASSERT 宏不够我们的使用,你可以用自定义的判断,并在不符合预期的时候将这个错误信息添加进去,Gtest 在最后汇总的时候也会显示出来。

c
1
2
3
4
5
// Generates a nonfatal failure, which allows the current function to continue running.
ADD_FAILURE();

// Generates a nonfatal failure at the file and line number specified.
ADD_FAILURE_AT(file_path,line_number);

这两个宏的效果和 EXPECT 类似,都允许继续往后执行该单元测试用例。

4. 使用示例

大部分的使用都是一样的,下面只对几个有代表性的做使用示例;为了方便,只对 ASSERT 做示例,因为它的效果和 EXPECT 完全一致,上文已经提到了二者的区别了。

4.1 ASSERT_EXIT

ASSERT_EXIT 有三个参数,分别为待测函数、退出码或退出信号、错误信息 regex;待测函数必须以指定的错误码或错误信号退出程序,并在 stderr 中打印能被这个 regex 匹配的错误信息。

c
1
ASSERT_EXIT(statement,predicate,matcher);

第二个参数的可选项,分别代表错误退出码和收到的错误信号

cpp
1
2
3
4
5
6
// Returns true if the program exited normally with the given exit status code.
::testing::ExitedWithCode(exit_code);

// Returns true if the program was killed by the given signal.
// Not available on Windows.
::testing::KilledBySignal(signal_number);

第三个参数在官网上的描述是这样的

The parameter matcher is either a matcher for a const std::string&, or a regular expression (see Regular Expression Syntax)—a bare string s (with no matcher) is treated as ContainsRegex(s), not Eq(s).

如果传入一个普通字符串,则会判断 stderr 输出的内容是否包含该字符串。

下面是一个简单的示例,我们的函数调用了 exit(1),使用 ASSERT_EXIT 来判断它是否以预期的错误码 1 退出程序。第三个参数.* 是正则表达式,代表任意错误信息的匹配。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>


void test_exit()
{
std::cerr << "test exit\n";
exit(1);
// int ret = 10/0;
}

TEST(EXPTEST, EXPTEST_EXIT)
{
// 期望以错误码1退出
ASSERT_EXIT(test_exit(),testing::ExitedWithCode(1),".*");
}

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

编译后的执行效果如下,我们的单元测试成功了,因为函数的确是以错误码 1 退出的。

plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
[ OK ] EXPTEST.EXPTEST_EXIT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

那么如果换一个退出方式呢?比如因为除 0 错误退出

plaintext
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
❯ g++ test.cpp -o test -lgtest && ./test
test.cpp: In function 'void test_exit()':
test.cpp:11:17: warning: division by zero [-Wdiv-by-zero]
11 | int ret = 10/0;
| ~~^~
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
test.cpp:17: Failure
Death test: test_exit()
Result: died but not with expected exit code:
Terminated by signal 8
Actual msg:
[ DEATH ] test exit
[ DEATH ]

[ FAILED ] EXPTEST.EXPTEST_EXIT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] EXPTEST.EXPTEST_EXIT

1 FAILED TEST

这个时候单元测试就失败了,并会打印出失败行的位置和失败的原因。这里提到失败是因为受到了信号 8,但我们预期是错误码 1。

将预期修改为信号 8,就能通过测试

cpp
1
2
3
4
5
6
7
8
9
10
11
12
void test_exit()
{
std::cerr << "test exit\n";
// exit(1);
int ret = 10/0;
}

TEST(EXPTEST, EXPTEST_EXIT)
{
// 期望以收到信号8退出
ASSERT_EXIT(test_exit(),testing::KilledBySignal(8),".*");
}

注意这里的警告是 g++ 编译器检测到除 0 错误后提供的,并非是运行时的错误。

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ g++ test.cpp -o test -lgtest && ./test
test.cpp: In function 'void test_exit()':
test.cpp:11:17: warning: division by zero [-Wdiv-by-zero]
11 | int ret = 10/0;
| ~~^~
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
[ OK ] EXPTEST.EXPTEST_EXIT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

将第三个参数改为一个普通字符串,gtest 会进行 stderr 输出是否包含该字符串的检查;

cpp
1
2
3
4
5
6
7
8
9
10
11
void test_exit()
{
std::cerr << "test exit\n";
int ret = 10/0;
}

TEST(EXPTEST, EXPTEST_EXIT)
{
// 期望以收到信号退出
ASSERT_EXIT(test_exit(),testing::KilledBySignal(8),"happy");
}

可以看到,单元测试失败的原因是 “错误退出但是没有提供期望的 error 输出”。

plaintext
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
❯ g++ test.cpp -o test -lgtest && ./test
test.cpp: In function 'void test_exit()':
test.cpp:10:17: warning: division by zero [-Wdiv-by-zero]
10 | int ret = 10/0;
| ~~^~
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
test.cpp:16: Failure
Death test: test_exit()
Result: died but not with expected error.
Expected: contains regular expression "happy"
Actual msg:
[ DEATH ] test exit
[ DEATH ]

[ FAILED ] EXPTEST.EXPTEST_EXIT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] EXPTEST.EXPTEST_EXIT

1 FAILED TEST

如果在错误输出中包含 happy 字符串,则正常通过测试。

cpp
1
2
3
4
5
6
7
8
9
10
11
void test_exit()
{
std::cerr << "test exit happy\n"; // 包含happy字符串
int ret = 10/0;
}

TEST(EXPTEST, EXPTEST_EXIT)
{
// 期望以收到信号退出
ASSERT_EXIT(test_exit(),testing::KilledBySignal(8),"happy");
}
plaintext
1
2
3
4
5
6
7
8
9
10
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
[ OK ] EXPTEST.EXPTEST_EXIT (1 ms)
[----------] 1 test from EXPTEST (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (1 ms total)
[ PASSED ] 1 test.

注意:函数内的 assert 错误对应的是信号 6,如果需要用 ASSERT_EXIT 来捕捉 assert 错误,则需要使用 testing::KilledBySignal(6)

4.2 ASSERT_DEATH

ASSERT_DEATH 和 ASSERT_EXIT 宏的作用基本一致,只不过 ASSERT_DEATH 不需要我们传入期望退出的错误码或信号。此时任意非 0 错误码退出和任意信号退出都会被视为 ASSERT_DEATH 的测试成功情况。

plaintext
1
2
EXPECT_DEATH(statement,matcher);
ASSERT_DEATH(statement,matcher);

这里的第二个参数和 ASSERT_EXIT 的第三个参数一致,可以是一个字符串,也可以是一个 regex 表达式。

示例代码和测试结果如下:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>


void test_exit()
{
std::cerr << "test exit\n";
exit(1);
}

TEST(EXPTEST, EXPTEST_EXIT)
{
ASSERT_DEATH(test_exit(),".*");
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
[ OK ] EXPTEST.EXPTEST_EXIT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

ASSERT_DEATH 只有在被测函数没有错误退出,或者以 exit(0) 退出的时候会出错。因为 0 号在操作系统中是进程退出的正常情况,非 0 才是错误信号。

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
test.cpp:15: Failure
Death test: test_exit()
Result: died but not with expected exit code:
Exited with exit status 0
Actual msg:
[ DEATH ] test exit
[ DEATH ]

[ FAILED ] EXPTEST.EXPTEST_EXIT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] EXPTEST.EXPTEST_EXIT

1 FAILED TEST

注意,抛出异常并没有归结到 DEATH 和 EXIT 的判定范围内。

cpp
1
2
3
4
5
6
7
8
9
10
11
void test_exit()
{
std::cerr << "test exit happy\n";
// int ret = 10/0;
throw std::runtime_error("123");
}

TEST(EXPTEST, EXPTEST_EXIT)
{
ASSERT_DEATH(test_exit(),"happy");
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
test.cpp:16: Failure
Death test: test_exit()
Result: threw an exception.
Error msg:
[ DEATH ] test exit happy
[ DEATH ]
[ DEATH ] test.cpp:16:: Caught std::exception-derived exception escaping the death test statement. Exception message: 123
[ DEATH ]

[ FAILED ] EXPTEST.EXPTEST_EXIT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] EXPTEST.EXPTEST_EXIT

1 FAILED TEST

4.3 ASSERT_DEBUG_DEATH

对于 ASSERT_DEBUG_DEATH,官方文档是这么描述的:

In debug mode, behaves the same as EXPECT_DEATH. When not in debug mode (i.e. NDEBUG is defined), just executes statement.

这里补充一下 NDEBUG 宏的作用,在标准库里面,它会控制 assert 是否起效果。如果一个程序 define 了 NDEBUG 宏(注意必须在引用 <assert.h> 头文件之前定义),那么 assert 将什么都不做。

可以看 assert.h 的源代码,当定义了 NDEBUG 宏后,assert 会调用__ASSERT_VOID_CAST (0);在 C++ 中这个 cast 是一个 static_cast<void>,在 C 语言中是一个)void) 的强转,反正都是啥都不干。

cpp
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
#if defined __cplusplus && __GNUC_PREREQ (2,95)
## define __ASSERT_VOID_CAST static_cast<void>
#else
## define __ASSERT_VOID_CAST (void)
#endif

/* void assert (int expression);

If NDEBUG is defined, do nothing.
If not, and EXPRESSION is zero, print an error message and abort. */

#ifdef NDEBUG

## define assert(expr) (__ASSERT_VOID_CAST (0))

/* void assert_perror (int errnum);

If NDEBUG is defined, do nothing. If not, and ERRNUM is not zero, print an
error message with the error text for ERRNUM and abort.
(This is a GNU extension.) */

## ifdef __USE_GNU
## define assert_perror(errnum) (__ASSERT_VOID_CAST (0))
## endif

#else /* Not NDEBUG. */

下面是一个简单的测试示例,当我们没有定义 NDEBUG 的时候,ASSERT_DEBUG_DEATH 和 ASSERT_DEATH 做的是相同的操作。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>


void test_exit()
{
std::cerr << "test exit happy\n";
exit(1);
}

TEST(EXPTEST, EXPTEST_EXIT)
{
ASSERT_DEBUG_DEATH(test_exit(),"happy");
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
[ OK ] EXPTEST.EXPTEST_EXIT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

如果在文件开头定义了 NDEBUG,那么 ASSERT_DEBUG_DEATH 则只会调用函数,并不会做错误信息的判断,如下所示,我们将函数中的 exit 删除,gtest 也没有报告错误。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define NDEBUG 1 // 一定要在开头定义
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>


void test_exit()
{
std::cerr << "test exit happy\n";
// exit(1); // 没有错误退出
}

TEST(EXPTEST, EXPTEST_EXIT)
{
ASSERT_DEBUG_DEATH(test_exit(),"happy");
}

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

可以看到,我们的函数并没有退出,但是由于定义了 NDEBUG,ASSERT_DEBUG_DEATH 宏没有报告错误。(去掉该宏,则会和 ASSERT_DEATH 一样提示出错)

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EXIT
test exit happy
[ OK ] EXPTEST.EXPTEST_EXIT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

4.4 ASSERT_THROW

调用对象应该抛出异常,并判断异常类型是否为期待类型。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>

void test_throw()
{
throw std::runtime_error("test");
}

TEST(EXPTEST, EXPTEST_THROW)
{
ASSERT_THROW(test_throw(),std::runtime_error);
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_THROW
[ OK ] EXPTEST.EXPTEST_THROW (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

当抛出的异常类型不一致的时候会出错

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_THROW
test.cpp:12: Failure
Expected: test_throw() throws an exception of type std::runtime_error.
Actual: it throws std::length_error with description "123".

[ FAILED ] EXPTEST.EXPTEST_THROW (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] EXPTEST.EXPTEST_THROW

1 FAILED TEST

异常相关的还有两个

  • ASSERT_ANY_THROW 不需要传入第二个参数,只关注目标函数应该抛出异常;
  • ASSERT_NO_THROW 也只有一个参数,目标函数不应该抛出异常;

因为它们很简单,这里就不做演示了。

4.5 ASSERT_EQ/NE

相等和不相等的比较。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>


TEST(EXPTEST, EXPTEST_EQ)
{
int ret = 10;
ASSERT_EQ(ret,10);
ASSERT_NE(ret,29);
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EQ
[ OK ] EXPTEST.EXPTEST_EQ (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

ASSERT_FLOAT_EQ 和 ASSERT_DOUBLE_EQ 是在比较的基础上允许一定的浮点数精度误差值。如果你需要比较两个浮点数,请使用对应的浮点数类型比较宏,而不要直接使用 ASSERT_EQ;

4.6 ASSERT_GE/LE

注意,大小写比较都是左和右直接按顺序比较的,比如 GE 是判断左边是否大于右边(不要搞反顺序了)

cpp
1
2
3
4
5
6
TEST(EXPTEST, EXPTEST_GE)
{
int ret = 10;
ASSERT_GE(ret,5);
ASSERT_LE(ret,29);
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_EQ
[ OK ] EXPTEST.EXPTEST_EQ (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

4.7 ASSERT_NEAR

该宏提供了第三个参数,在浮点数比较时允许一定的误差值。官网文档说明是保证 val1 和 val2 不超过 abs_error 的误差边界。

c
1
2
3
EXPECT_NEAR(val1,val2,abs_error)
ASSERT_NEAR(val1,val2,abs_error)
// Verifies that the difference between val1 and val2 does not exceed the absolute error bound abs_error.

下面是一个示例

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>


TEST(EXPTEST, EXPTEST_NEAR)
{
ASSERT_NEAR(3.14,3.15,0.01);
}

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

因为两个数字的差距的确在 0.01 的误差区间,所以可以通过测试。

plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_NEAR
[ OK ] EXPTEST.EXPTEST_NEAR (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

如果将第三个参数的误差改为 0.001,则无法通过测试。

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_NEAR
test.cpp:8: Failure
The difference between 3.14 and 3.15 is 0.0099999999999997868, which exceeds 0.001, where
3.14 evaluates to 3.1400000000000001,
3.15 evaluates to 3.1499999999999999, and
0.001 evaluates to 0.001.

[ FAILED ] EXPTEST.EXPTEST_NEAR (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] EXPTEST.EXPTEST_NEAR

1 FAILED TEST

4.8 ASSERT_STREQ/STRCASEEQ

字符串相等比较,一个不忽略大小写,一个忽略大小写;

注意,这两个宏的入参都是 const char*,不能直接传入 std::string 进行比较。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>
#include <string>

TEST(EXPTEST, EXPTEST_STR)
{
std::string str1 = "hello";
std::string str2 = "hello";
std::string str3 = "Hello";
ASSERT_STREQ(str1.c_str(),str2.c_str());
ASSERT_STRCASEEQ(str1.c_str(),str3.c_str());
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_STR
[ OK ] EXPTEST.EXPTEST_STR (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

4.9 ASSERT_TRUE/FALSE

bool 值真假的判断。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>
#include <string>

TEST(EXPTEST, EXPTEST_TRUE)
{
int a, b;
a = 1;
b = 2;
ASSERT_TRUE(a != b);
ASSERT_FALSE(a == b);
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_TRUE
[ OK ] EXPTEST.EXPTEST_TRUE (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

注意,这两个宏只接受 bool 值或者 int 类型,诸如 nullptr 这类值都是不能被直接接受的(除非你强转)。

  • 如果你需要判断一个函数的结果是否为空指针 nullptr,请使用 ASSERT_EQ 来判断;
  • 如果你的函数返回值用了 int 且用 0 来标识错误的情况,也建议用 ASSERT_EQ 来更可读的判断,而不要用 ASSERT_TRUE/FALSE;
plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
In file included from /usr/local/include/gtest/gtest-printers.h:122,
from /usr/local/include/gtest/gtest-matchers.h:49,
from /usr/local/include/gtest/internal/gtest-death-test-internal.h:47,
from /usr/local/include/gtest/gtest-death-test.h:43,
from /usr/local/include/gtest/gtest.h:65,
from test.cpp:1:
test.cpp: In member function 'virtual void EXPTEST_EXPTEST_TRUE_Test::TestBody()':
test.cpp:8:5: error: converting to 'bool' from 'std::nullptr_t' requires direct-initialization [-fpermissive]
8 | ASSERT_FALSE(nullptr);
| ^~~~~~~~~~~~

这里也能看出 C++ 中 nullptr 和 C 语言中 NULL 的不同,使用 ASSERT_FALSE 来判断 NULL 是能通过编译的,因为它本质只是一个 define 的数字 0,和 bool 值的本质是一样的。

plaintext
1
ASSERT_FALSE(NULL);

4.10 ADD_FAILURE

添加失败信息的效果示例

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <gtest/gtest.h>
#include <iostream>
#include <unistd.h>
#include <string>

TEST(EXPTEST, EXPTEST_TRUE)
{
int a, b;
a = 1;
b = 2;
if (a!=b)
{
ADD_FAILURE();
}
// 假设我知道被测函数的目标文件和行号
ADD_FAILURE_AT("add.hpp",20);
}

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

如果你知道出错的被测函数的文件和所在行数,使用 ADD_FAILURE_AT 能更好的帮助定位问题。因为 ADD_FAILURE 只会显示出错单元测试的文件和位置。

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_TRUE
test.cpp:13: Failure
Failed

add.hpp:20: Failure
Failed

[ FAILED ] EXPTEST.EXPTEST_TRUE (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] EXPTEST.EXPTEST_TRUE

1 FAILED TEST

这里也能看到,ADD_FAILURE 后,单元测试会像 EXPECT 宏一样继续往后运行。

4.11 ASSERT_THAT

和 ASSERT_THAT 相关的 matcher 选项在 gtest 中提供了,注意需要引用 gmock 头文件

cpp
1
2
3
4
5
6
7
#include <gmock/gmock.h>

using ::testing::AllOf;
using ::testing::Gt;
using ::testing::Lt;
using ::testing::MatchesRegex;
using ::testing::StartsWith;

更多 matcher 可以查看官方文档:Matchers

这里还有个小细节,你会发现 gtest 官网文档中的所有 testing 命名空间前面都带了一个::,这样不管你在什么自定义的命名空间里面编写 gtest 的代码,都可以通过:: 先回到全局命名空间,再访问 testing 命名空间。即不会和用户自定义空间中的冲突。

另外,如果你需要使用 gtest 框架,应该避免自己的命名空间和 gtest 的命名空间重名。

示例代码如下,我们的字符串的确是以 he 开头的,能通过检查。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <iostream>
#include <unistd.h>
#include <string>

TEST(EXPTEST, EXPTEST_THAT)
{
std::string str = "hello";
ASSERT_THAT(str.c_str(),testing::StartsWith("he"));
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_THAT
[ OK ] EXPTEST.EXPTEST_THAT (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

4.12 ASSERT_PRED

谓词测试宏一共有 5 个,分别接受一个自定义函数和该函数对应的参数,并判断返回值是否为 bool 值的 true。数字就代表函数的参数个数,最多支持 5 个参数的自定义函数。

cpp
1
2
3
4
5
ASSERT_PRED1(pred,val1)
ASSERT_PRED2(pred,val1,val2)
ASSERT_PRED3(pred,val1,val2,val3)
ASSERT_PRED4(pred,val1,val2,val3,val4)
ASSERT_PRED5(pred,val1,val2,val3,val4,val5)

我们定义一个测试函数,再把两个参数传入该函数,就能得到一个结果

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <iostream>
#include <unistd.h>
#include <string>

bool is_eq(int a, int b)
{
return a == b;
}

TEST(EXPTEST, EXPTEST_PRED)
{
ASSERT_PRED2(is_eq, 1, 2);
// 等价于 ASSERT_TRUE(is_eq(1,2));
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from EXPTEST
[ RUN ] EXPTEST.EXPTEST_PRED
test.cpp:14: Failure
is_eq(1, 2) evaluates to false, where
1 evaluates to 1
2 evaluates to 2

[ FAILED ] EXPTEST.EXPTEST_PRED (0 ms)
[----------] 1 test from EXPTEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] EXPTEST.EXPTEST_PRED

1 FAILED TEST

5.TEST_F

介绍完毕断言宏了,TEST 的作用想必大家也明白了,下面对 TEST_F 给出一个简单的示例。

在上文介绍 TEST_F 中提到过编写的基本框架应该如下所示

cpp
1
2
3
4
5
6
class MyClassTest : public testing::Test {
...
};

TEST_F(MyClassTest, HasPropertyA) { ... }
TEST_F(MyClassTest, HasPropertyB) { ... }

假设我们需要对 MyClass 类进行测试,那么 MyClassTest 类就是对 MyClass 进行初始化和销毁操作的,每一个 TEST_F 都是对 MyClass 中的某个成员变量进行测试。

下面是一个简单的对类的单元测试的示例,目的是确认 AddX 和 AddY 的情况符合我们的预期,能正常给成员变量添加值。

cpp
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
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <iostream>
#include <unistd.h>
#include <memory>
#include <string>

class MyClass
{
public:
MyClass(int x = 0, int y = 0) : _x(x), _y(y) {}

void AddX(int val)
{
_x += val;
}

void AddY(int val)
{
_y += val;
}

int GetX() { return _x; }
int GetY() { return _y; }

private:
int _x;
int _y;
};

class MyClassTest : public testing::Test
{
protected:
void SetUp()
{
std::cout << "set up for MyClass\n";
_mc = std::make_shared<MyClass>();
}

void TearDown()
{
std::cout << "tear down for MyClass\n";
_mc.reset();
}

std::shared_ptr<MyClass> _mc;
};

TEST_F(MyClassTest, MyClassTestAddX)
{
_mc->AddX(10);
EXPECT_EQ(_mc->GetX(), 10);
}

TEST_F(MyClassTest, MyClassTestAddY)
{
_mc->AddY(20);
EXPECT_EQ(_mc->GetY(), 20);
}

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

在代码中,TEST_F 体内可以访问 MyClassTest 的成员变量_mc,并调用它的成员函数。这样就避免了我们在每个单元测试中对一些公用的类进行多次初始化程序编写的操作。

编译运行这个代码,能看到 SetUp 和 TearDown 在每次 TEST_F 之前和之后都会被执行,并非只执行一次!

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
❯ g++ test.cpp -o test -lgtest && ./test
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from MyClassTest
[ RUN ] MyClassTest.MyClassTestAddX
set up for MyClass
tear down for MyClass
[ OK ] MyClassTest.MyClassTestAddX (0 ms)
[ RUN ] MyClassTest.MyClassTestAddY
set up for MyClass
tear down for MyClass
[ OK ] MyClassTest.MyClassTestAddY (0 ms)
[----------] 2 tests from MyClassTest (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 2 tests.

如果你需要测试一个类的私有成员,可以使用 g++ 的编译命令 -fno-access-control 直接取消访问限定符

plaintext
1
g++ test.cpp -o test -fno-access-control -lgtest

此时在类外对私有成员和私有函数的直接访问不会出现编译报错。但需要注意的是,你只可以在单元测试中使用该编译命令,在生产环境中不应该这么做!

The end

对于 Gtest 的宏的介绍到这里就结束了。

google gtest 框架中还包含了一个 gmock 模块,这个模块的主要作用是继承一个父类,重写虚函数,并可以在测试夹具中用 EXPECT_CALL 自定义重写后函数的返回值和调用次数。在我遇到的实际项目中,这个 gmock 模块用到的次数很少,因为它的适用范围实在是太窄了。