【C++】Google Gtest测试框架的使用

本文首发于 ❄️慕雪的寒舍

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

本文使用的gtest版本为1.14.0;

1.gtest是用来干嘛的?

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

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

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

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

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

2.gtest的代码基本框架

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

Testing Reference | GoogleTest

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

2.1 TEST 单元测试模块

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

cpp 复制代码
TEST(TestSuiteName, TestName) {
  ... statements ...
}

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

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

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

2.2 TEST_F 类测试模块

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

cpp 复制代码
TEST_F(TestFixtureName, TestName) {
  ... statements ...
}

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

cpp 复制代码
class MyClassTest : public testing::Test {
protected:  
    void SetUp() // 初始化,在每个TEST_F中都会被调用
    {}

    void TearDown() // 销毁,在每个TEST_F结束时都会调用
    {}
    
	// 可以定义一些成员变量,在TEST_F中能访问
    int _a;
};

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

cpp 复制代码
class MyClassTest : public testing::Test {
  ...
};

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

2.3 简单示例

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

cpp 复制代码
#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进行链接。

g++ test.cpp -o test -lgtest

3.gtest提供的断言宏

官方文档:Assertions Reference | GoogleTest

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

cpp 复制代码
TEST(EXP,EXP1)
{
	ASSERT_EQ(1,1); // 如果这个出错了,后续不会执行
	// 这里不会被执行	
}

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

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

3.1 相等/大小判断

ASSERT宏 EXPECT宏 功能 参数个数
ASSERT_TRUE EXPECT_TRUE 判真 1
ASSERT_FALSE EXPECT_FALSE 判假 1
ASSERT_EQ EXPECT_EQ 相等 2
ASSERT_NE EXPECT_NE 不相等 2
ASSERT_GT EXPECT_GT 第一个参数是否大于第二个参数 2
ASSERT_LT EXPECT_LT 小于(原理同上) 2
ASSERT_GE EXPECT_GE 大于等于 2
ASSERT_LE EXPECT_LE 小于等于 2
ASSERT_FLOAT_EQ EXPECT_FLOAT_EQ 单精度浮点数相等 2
ASSERT_DOUBLE_EQ EXPECT_DOUBLE_EQ 双精度浮点数相等 2
ASSERT_NEAR EXPECT_NEAR 浮点数是否接近(第三个参数为允许的误差值) 3
ASSERT_STREQ EXPECT_STREQ C字符串相等 2
ASSERT_STRNE EXPECT_STRNE C字符串不相等 2
ASSERT_STRCASEEQ EXPECT_STRCASEEQ C字符串相等(忽略大小写) 2
ASSERT_STRCASENE EXPECT_STRCASENE C字符串不相等(忽略大小写) 2
ASSERT_PRED1 EXPECT_PRED1 自定义谓词测试(有1到5级,对应不同参数个数的自定义函数) 2
ASSERT_THAT EXPECT_THAT 判断函数返回值是否符合给定的matcher(gtest提供的) 2

3.2 异常相关

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

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

3.3 直接表明成功和失败

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

c 复制代码
SUCCEED(); // 成功
FAIL();    // 失败

3.4 添加失败信息

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

c 复制代码
// 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 复制代码
ASSERT_EXIT(statement,predicate,matcher);

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

cpp 复制代码
// 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 复制代码
#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退出的。

❯ 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错误退出

❯ 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 复制代码
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错误后提供的,并非是运行时的错误。

❯ 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 复制代码
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输出"。

❯ 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 复制代码
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");
}
[==========] 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的测试成功情况。

EXPECT_DEATH(statement,matcher);
ASSERT_DEATH(statement,matcher);

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

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

cpp 复制代码
#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();
}
❯ 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才是错误信号。

❯ 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 复制代码
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");
}
❯ 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 复制代码
#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 复制代码
#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();
}
❯ 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 复制代码
#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一样提示出错)

❯ 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 复制代码
#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();
}
❯ 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.

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

❯ 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 复制代码
#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();
}
❯ 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 复制代码
TEST(EXPTEST, EXPTEST_GE)
{
    int ret = 10;
    ASSERT_GE(ret,5);
    ASSERT_LE(ret,29);
}
❯ 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 复制代码
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 复制代码
#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的误差区间,所以可以通过测试。

❯ 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,则无法通过测试。

❯ 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 复制代码
#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();
}
❯ 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 复制代码
#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();
}
❯ 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;

    ❯ 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值的本质是一样的。

ASSERT_FALSE(NULL);

4.10 ADD_FAILURE

添加失败信息的效果示例

cpp 复制代码
#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只会显示出错单元测试的文件和位置。

❯ 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 复制代码
#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 复制代码
#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();
}
❯ 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 复制代码
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 复制代码
#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();
}
❯ 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 复制代码
class MyClassTest : public testing::Test {
  ...
};

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

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

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

cpp 复制代码
#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之前和之后都会被执行,并非只执行一次!

❯ 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直接取消访问限定符

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

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

The end

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

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

相关推荐
蜜獾云8 分钟前
docker 安装雷池WAF防火墙 守护Web服务器
linux·运维·服务器·网络·网络安全·docker·容器
小屁不止是运维9 分钟前
麒麟操作系统服务架构保姆级教程(五)NGINX中间件详解
linux·运维·服务器·nginx·中间件·架构
bitcsljl22 分钟前
Linux 命令行快捷键
linux·运维·服务器
ac.char25 分钟前
在 Ubuntu 下使用 Tauri 打包 EXE 应用
linux·运维·ubuntu
yuyanjingtao40 分钟前
CCF-GESP 等级考试 2023年9月认证C++四级真题解析
c++·青少年编程·gesp·csp-j/s·编程等级考试
Cachel wood44 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Youkiup1 小时前
【linux 常用命令】
linux·运维·服务器
闻缺陷则喜何志丹1 小时前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
qq_297504611 小时前
【解决】Linux更新系统内核后Nvidia-smi has failed...
linux·运维·服务器
charlie1145141911 小时前
C++ STL CookBook
开发语言·c++·stl·c++20