一、简介

Google Test也叫gtest,是一个面向C++的单元测试框架。它在Google公司内部使用了多年,并且每天都在上千个项目中进行着单元测试。现在已经成为了C++单元测试的事实标准之一。gtest以其高效、灵活、可移植和可扩展等优点获得广泛好评。

二、gtest的基本结构

gtest最基本的结构可以分成两部分:测试用例和测试夹具。测试用例是指对某一个功能的测试,通常会包含多个测试点。每个测试点对应一个gtest宏:TEST,通过TEST来定义测试用例并添加测试点。测试夹具是在每次测试运行之前和之后执行的代码,用以准备测试环境和清理测试环境。

下面是一个示例代码:

#include "gtest/gtest.h"
 
TEST(TestCaseName, TestName) {
  ...属于这个测试用例的测试点1...
  ...属于这个测试用例的测试点2...
  ...属于这个测试用例的测试点3...
}

在这个例子中,”TestCaseName”和”TestName”分别是测试用例的名称和测试点的名称。在一个测试用例中可以定义多个测试点,但是测试用例名称必须保持唯一。

三、gtest调试相关

1、gtest调试信息输出

gtest在测试运行过程中,可以输出调试信息,从而帮助我们定位问题。在gtest中,使用GTEST_COUT可以方便地输出调试信息。

下面是一个示例代码:

#include "gtest/gtest.h"
 
TEST(TestCaseName, TestName) {
    GTEST_COUT << "This is debug info!" << std::endl;
}

在这个例子中,代码输出了一条调试信息:”This is debug info!”。输出的位置将在测试运行结果中显示。

2、gtest运行调试器

如果我们需要在某个测试点处运行调试器,可以使用gtest的”–gtest_filter”参数来实现。

下面是一个示例代码:

#include "gtest/gtest.h"
 
TEST(TestCaseName, TestName) {
    GTEST_COUT << "I am going to break point!" << std::endl;
    int a = 1;
    int b = 2;
    EXPECT_EQ(a + b, 3);
}

如果我们想在”EXPECT_EQ(a + b, 3)”处打一个断点来进行调试,可以按下面的命令来执行:

./test --gtest_filter=TestCaseName.TestName:Test.DoDebugBreakHere

在这个例子中,我们通过在测试点后面加上”:Test.DoDebugBreakHere”的方式,在指定的测试点处让程序停止。

3、gtest调式内存泄漏检测

gtest提供了内存泄漏检测,可以帮助我们检测程序运行过程中是否有堆内存泄漏。

下面是一个示例代码:

#include "gtest/gtest.h"
 
class Foo {
public:
    int* data;
    Foo() {
        data = new int[100];
    }
    virtual ~Foo() {
        delete[] data;
    }
};
 
TEST(TestCaseName, TestName) {
    GTEST_COUT << "This is to test memory leak!" <data[99] == 0);
}

在这个例子中,我们故意将Foo的data成员分配了100个整形内存,但是在Foo的析构函数中只释放了1个整形内存。这样就会造成堆内存泄漏。我们可以使用gtest的”–gtest_filter”参数,加上”–gtest_enable_death_test”选项来运行这个测试用例。

./test --gtest_filter=TestCaseName.TestName --gtest_enable_death_test

这样运行测试用例后,gtest会检测到内存泄漏,并在屏幕上输出相关信息。

四、gtest运行方式

gtest有三种运行方式:在命令行运行、在IDE中运行、在自动化构建工具中运行。

1、在命令行运行

在命令行下运行gtest非常简单,只需要将gtest库链接到测试程序中,然后用”./test”来运行测试程序即可。gtest提供了丰富的启动选项,如”–gtest_list_tests”,”–gtest_filter”等。

下面是一个运行gtest的例子:

gtest_main.cc:
#include "gtest/gtest.h"
 
GTEST_API_ int main(int argc, char **argv)
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

下面是编译gtest库的默认Makefile:

GTEST_DIR = ../googletest
CXXFLAGS += -isystem $(GTEST_DIR)/include
CXXFLAGS += -std=c++11 -g -Wall -Wextra -pthread
TESTS = first_test.cpp second_test.cpp
 
all : test
test : $(TESTS) gtest_main.a
    $(CXX) $(CXXFLAGS) -o $@ $^
 
gtest-all.o : $(GTEST_DIR)/src/gtest-all.cc $(GTEST_DIR)/include/gtest/gtest.h
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(GTEST_DIR)/src/gtest-all.cc
 
gtest_main.o : $(GTEST_DIR)/src/gtest_main.cc $(GTEST_DIR)/include/gtest/gtest.h
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $(GTEST_DIR)/src/gtest_main.cc
 
gtest_main.a : gtest-all.o gtest_main.o
    $(AR) $(ARFLAGS) $@ $^

2、在IDE中运行

大多数现代IDE都已经支持gtest的执行,运行gtest的过程就像运行其他可执行程序一样。以Visual Studio为例,可以使用选项”/MDd”将gtest和测试程序链接在一起,然后可以用F5来启动调试会话。

3、在自动化构建工具中运行

gtest可以与许多常见自动化构建工具一起使用,如Make、Ant、CMake、MSBuild等。下面是使用CMake来运行gtest的一个示例代码:

cmake_minimum_required(VERSION 3.10.2)
project(MyTest)
enable_testing()
add_executable(MyTest test.cc)
target_link_libraries(MyTest gtest_main gtest)
add_test(MyTest MyTest)

在这个例子中,我们先使用add_executable来定义要测试的程序,然后再使用target_link_libraries将gtest库链接到程序中。最后,我们使用add_test将MyTest这个测试添加到测试套件中。

五、gtest的扩展功能

gtest提供了许多非常有用的扩展功能,让我们能更好地管理测试用例和测试点,以及对程序进行更细致的验证。

1、在gtest中组织测试用例

如果我们需要对一组相关的测试用例进行分类,可以使用gtest的TEST_F宏定义来组织测试用例。在TEST_F中定义fixture类来提供测试夹具。

下面是一个示例代码:

#include "gtest/gtest.h"
 
class TestFixture: public testing::Test {
public:
    static void SetUpTestCase() {}
    static void TearDownTestCase() {}
    virtual void SetUp() {}
    virtual void TearDown() {}
};
 
TEST_F(TestFixture, TestName) {
    GTEST_COUT << "This is a test!" << std::endl;
}

在这个例子中,TestFixture定义了SetUpTestCase和TearDownTestCase来执行测试夹具的初始化和清理任务,SetUp和TearDown来执行每个测试点的初始化和清理任务。使用TEST_F来定义测试用例和测试点。

2、EXPECT系列验证宏

gtest提供了一系列EXPECT_*和ASSERT_*宏来对程序进行验证。EXPECT_*和ASSERT_*宏有一点区别,虽然它们都会做出判断,但EXPECT_*将验证结果记录下来,不管是否验证成功都继续执行;而ASSERT_*则在验证失败的时候立即停止程序。以下是常用的EXPECT系列验证宏:

  • 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_TRUE(condition) – condition is true.
  • EXPECT_FALSE(condition) – condition is false.

下面是一个示例代码:

#include "gtest/gtest.h"
 
TEST(TestCaseName, TestName) {
    EXPECT_EQ(1, 1) << "1 equals 1";
    EXPECT_NE(1, 2) << "1 not equals 2";
    EXPECT_TRUE(0 == 0) << "0 == 0";
    EXPECT_FALSE(1 == 2) << "1 not equals 2";
}

3、自己编写gtest扩展功能

我们可以通过编写gtest插件的方式来扩展gtest的功能。gtest插件可以实现事件响应、输出扩展、断言扩展和异常捕获等功能。

以下是一个gtest插件的实现示例:

#include "gtest/gtest.h"
 
void MyTestEventListener::OnTestStart(const testing::TestInfo& test_info) {
    std::cout << "Test start: " << test_info.name() << std::endl;
    fflush(stdout); //刷新输出缓冲区,即将缓冲区的内容立即输出
}
 
void MyTestEventListener::OnTestEnd(const testing::TestInfo& test_info) {
    std::cout << "Test end: " << test_info.name() <listeners();
    listeners.Append(new MyTestEventListenerFactory);
 
    EXPECT_TRUE(1 == 1);
}

在这个示例中,我们编写了一个gtest插件,用来在测试开始和测试结束时打印一条信息。我们使用listeners.Append将这个插件添加到事件监听器列表中。在TestName测试点中,我们使用了EXPECT_TRUE宏来对程序进行验证。

六、结论

Google Test是一个非常好用的C++单元测试框架,具有高效、灵活、可移植和可扩展等优点。通过增强gtest的认识和学习如何使用gtest将有助于我们编写更高效、更健壮的C++程序。