GTEST的TEST原理分析及自动调度解析
介绍
Gtest是google开发的一个开源的C++测试框架,可在Linux, Windows,Mac多个平台上对C++源码进行测试,它提供了丰富的断言、可进行数值型、bool类型、字符串数据类型、数值检查、异常检查、致命和非致命判断、“死亡测试”等待。
使用gtest时,就是编写断言(assertions),断言语句会检测条件是否为真。一个断言可存在三种结果:success(成功),nonfatal failure(非致命失败),或 fatal failure(致命失败)。当出现致命失败时,终止当前函数;否则程序继续执行。
主要测试套:
TEST , TEST_
主要测试宏:
ASSERT_
EXPECT_

Tests使用断言语句来检测代码的结果。如果一个test出现崩溃或有一个失败的断言,则该test是fails,否则是succeeds。
一个test suite包括一个或多个tests。可以将多个tests分组到test suite中,以此反映所测试代码的结构。当一个test suite中的多个tests需要共享一些通用对象和子程序时,可将其放入一个test fixture class。

一个test program可包含多个test suites.

gtest系列之断言
gtest中断言的宏可以分为两类:一类是ASSERT宏,另一类就是EXPECT宏。

1、ASSERT_系列:如果当前点检测失败则退出当前函数

2、EXPECT_系列:如果当前点检测失败则继续往下执行

如果对自动输出的错误信息不满意的话,也是可以通过operator<<能够在失败的时候打印日志,将一些自定义的信息输出。

ASSERT_系列:

bool值检查

1、 ASSERT_TRUE(参数),期待结果是true

2、ASSERT_FALSE(参数),期待结果是false

数值型数据检查

3、ASSERT_EQ(参数1,参数2),传入的是需要比较的两个数 equal

4、ASSERT_NE(参数1,参数2),not equal,不等于才返回true

5、ASSERT_LT(参数1,参数2),less than,小于才返回true

6、ASSERT_GT(参数1,参数2),greater than,大于才返回true

7、ASSERT_LE(参数1,参数2),less equal,小于等于才返回true

8、ASSERT_GE(参数1,参数2),greater equal,大于等于才返回true

字符串检查

9、ASSERT_STREQ(expected_str, actual_str),两个C风格的字符串相等才正确返回

10、ASSERT_STRNE(str1, str2),两个C风格的字符串不相等时才正确返回

11、ASSERT_STRCASEEQ(expected_str, actual_str)

12、ASSERT_STRCASENE(str1, str2)

EXPECT宏大体相同。

使用cmake进行gtest源码编译安装
下载 gtest 源码包,并解压,如:/home/cj/gtest-1.7.0;

编译 gtest 动态库
进入 gtest-1.7.0 目录,编辑 CMakeLists.txt,修改第10行:

option(BUILD_SHARED_LIBS "Build shared libraries (DLLs)." OFF)
 将  OFF 修改为  ON,保存并退出。

创建 build 目录,并进入该目录;

cmake …

make

生成libgtest.so和libgtest_main.so等其他文件,libgtest_main.so大小为9168 bytes,libgtest.so为848232 bytes

gtest进行单元测试 gtest原理_测试用例


编译生成静态库

进入 gtest-1.7.0 目录,修改第10行:

10 option(BUILD_SHARED_LIBS "Build shared libraries (DLLs)." ON)

将ON改为OFF

新建一个mybuild目录

cd mybuild

cmake …

make

生成相关静态库,生成libgtest.a和libgtest_main.a等其他文件,libgtest_main.a大小为3836 bytes,libgtest.a为1406016 bytes

gtest进行单元测试 gtest原理_Test_02


手动编译

进入 gtest-1.7.0 目录

手动编译共享库

g++ -shared -fPIC -I./include . ./src/gtest-all.cc -o libgtest.so  生成libgtest.so文件

手动编译生成静态库

g++ -I./include  -pthread -c src/gtest-all.cc 生成gtest-all.o文件
 ar -rv libgtest.a gtest-all.o命令生成libgtest.a文件

手动写main入口或连接libgest_main.so库:

int main(int argc, char **argv)
{
    std::cout << "Running main() from gtest_main.cc\n";
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

如果手动写了main函数,同时编译时也链接了gtest_main库,则gtest_main库不会进行链接。

TEST宏源码剖析
分析的文件目录为:gtest-1.7.0\fused-src\gtest
所有的函数声明定义在gtest.h和gtest-all.cc中。
标准测试宏TEST写法:
TEST(test_case_name, test_name){}
其中,test_case_name为测试用例名,test_name为测试用例test_case_name的一个测试特例。
例如,现在要对一个Base类进行单元测试,只有一个测试特例为Base类对象是否为空。

TEST(Base, createInstance) {
	std::unique_ptr<Base> instance = make_unique<Base>("SvenBaseUnique");
	// 测试创建的instance实例是否不为nullptr
	EXPECT_NE(instance, nullptr);
	instance.reset();
	// 测试instance实例是否为nullptr
	EXPECT_EQ(instance, nullptr);
}

将TEST宏依次展开过程:

  1. TEST展开为GTEST_TEST:
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
  1. GTEST_TEST展开为GTEST_TEST_:
#define GTEST_TEST(test_case_name, test_name)\
  GTEST_TEST_(test_case_name, test_name, \
              ::testing::Test, ::testing::internal::GetTestTypeId())
  1. 再将GTEST_TEST_展开为GTEST_TEST_CLASS_NAME_
// Helper macro for defining tests.
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class **GTEST_TEST_CLASS_NAME_**(test_case_name, test_name) : public parent_class {\
 public:\
  **GTEST_TEST_CLASS_NAME_**(test_case_name, test_name)() {}\
 private:\
  virtual void TestBody();\
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\
  GTEST_DISALLOW_COPY_AND_ASSIGN_(\
      **GTEST_TEST_CLASS_NAME_**(test_case_name, test_name));\
};\
\
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
  ::test_info_ =\
    ::testing::internal::MakeAndRegisterTestInfo(\
        #test_case_name, #test_name, NULL, NULL, \
        (parent_id), \
        parent_class::SetUpTestCase, \
        parent_class::TearDownTestCase, \
        new ::testing::internal::TestFactoryImpl<\
            GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()

其中,GTEST_TEST_CLASS_NAME_ 为:

#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \
  test_case_name##_##test_name##_Test

显然,该测试用例通过编译预处理宏展开后,由

gtest进行单元测试 gtest原理_测试用例_03


展开为:

gtest进行单元测试 gtest原理_单元测试_04


可见,利用行连接符"",使得TestBody()紧跟TEST宏代码块实体。

从而可知,对每个TEST宏,googleTest测试框架都会为其生成一个命名为“测试用例名_测试特例名_Test”的类,真正的测试内容则为该类的方法TestBody代码块实体。显然,googleTest测试框架要完成该测试用例的测试任务,必然要在某处实例化“测试用例名_测试特例名_Test”,并执行其TestBody()方法。

我们可以猜测:一个可能的测试流程为:

test_case_name_test_name_Test *unitTesst = new test_case_name_test_name_Test;
unitTesst->TestBody();

我们知道:

TEST(Base, createInstance) {
	std::unique_ptr<Base> instance = make_unique<Base>("SvenBaseUnique");
	// 测试创建的instance实例是否不为nullptr
	EXPECT_NE(instance, nullptr);
	instance.reset();
	// 测试instance实例是否为nullptr
	EXPECT_EQ(instance, nullptr);
}

展开为:

gtest进行单元测试 gtest原理_单元测试_05


每个测试类都有一个TestInfo类的test_info_静态对象,在初始化这个静态对象时,**很巧妙地将自身测试类名(即test_case_name_test_name_Test)**通过::testing::internal::MakeAndRegisterTestInfo函数,进行了注册:

MakeAndRegisterTestInfo()函数实现为:

TestInfo* MakeAndRegisterTestInfo(
    const char* test_case_name,
    const char* name,
    const char* type_param,
    const char* value_param,
    TypeId fixture_class_id,
    SetUpTestCaseFunc set_up_tc,
    TearDownTestCaseFunc tear_down_tc,
    TestFactoryBase* factory) {
  TestInfo* const test_info =
      new TestInfo(test_case_name, name, type_param, value_param,
                   fixture_class_id, factory);
  GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
  return test_info;
}

其中

inline UnitTestImpl* GetUnitTestImpl() {
  return UnitTest::GetInstance()->impl();
}

可见该函数是TestInfo类的的方法,其功能就是使用测试用例名和测试特例名以及TestFactoryBase类工厂基类作为基本信息实例化一个test_info 对象,并让UnitTest的委托类UnitTestImpl()将test_info存入。
我们再看UnitTestImpl类的AddTestInfo函数实现细节:

void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc,
                   Test::TearDownTestCaseFunc tear_down_tc,
                   TestInfo* test_info) {
    if (original_working_dir_.IsEmpty()) {
      original_working_dir_.Set(FilePath::GetCurrentDir());
      GTEST_CHECK_(!original_working_dir_.IsEmpty())
          << "Failed to get the current working directory.";
    }
    GetTestCase(test_info->test_case_name(),
                test_info->type_param(),
                set_up_tc,
                tear_down_tc)->AddTestInfo(test_info);
  }

可见,函数实现主要点在:
GetTestCase(test_info->test_case_name(),
test_info->type_param(),
set_up_tc,
tear_down_tc)->AddTestInfo(test_info);

TestCase* UnitTestImpl::GetTestCase(const char* test_case_name,
                                    const char* type_param,
                                    Test::SetUpTestCaseFunc set_up_tc,
                                    Test::TearDownTestCaseFunc tear_down_tc) {
  // Can we find a TestCase with the given name?
  const std::vector<TestCase*>::const_iterator test_case =
      std::find_if(test_cases_.begin(), test_cases_.end(),
                   TestCaseNameIs(test_case_name));
  if (test_case != test_cases_.end())
    return *test_case;
  // No.  Let's create one.
  TestCase* const new_test_case =
      new TestCase(test_case_name, type_param, set_up_tc, tear_down_tc);

  // Is this a death test case?
  if (internal::UnitTestOptions::MatchesFilter(test_case_name,
                                               kDeathTestCaseFilter)) {
    ++last_death_test_case_;
    test_cases_.insert(test_cases_.begin() + last_death_test_case_,
                       new_test_case);
  } else {
    // No.  Appends to the end of the list.
    test_cases_.push_back(new_test_case);
  }
  test_case_indices_.push_back(static_cast<int>(test_case_indices_.size()));
  return new_test_case;
}

可见,GetTestCase()的主要是根据测试用例名(test_case_name)先检查test_cases_里有没有构造过该测试用例名,如果没有,就说明是新的测试用例名,并进行构造追加,拿到测试用例test_cases_后,调用TestCase类的AddTestInfo()接口:
TestCase类的AddTestInfo()函数实现为:

void TestCase::AddTestInfo(TestInfo * test_info) {
  test_info_list_.push_back(test_info);
  test_indices_.push_back(static_cast<int>(test_indices_.size()));
}

可见,TestCase保留了测试用例的全部测试特例的信息(包含一个TestFactoryBase工厂),总的流程可以用图表示:

gtest进行单元测试 gtest原理_函数实现_06


对应类图为:

gtest进行单元测试 gtest原理_单元测试_07


通过TestInfo类友元函数MakeAndRegisterTestInfo()保存了所有的测试用例及测试用例对应的所有的测试特例信息,UnitTestImpl类管理所有测试用例,TestCase类管理测试用例的所有测试特例,那么现在问题是,他们又是怎么和我们的test_case_name_Test类进行关联呢。

test_case_name_Test类(测试特例类)调度过程:

我们回头看test_case_name_Test类的成员test_info_静态对象初始化过程,他利用工厂TestFactoryImpl实例化了一个test_case_name_Test对象

gtest进行单元测试 gtest原理_测试用例_08


模板工厂TestFactoryImpl 实现为:

template <class TestClass>
class TestFactoryImpl : public TestFactoryBase {
 public:
  virtual Test* CreateTest() { return new TestClass; }
};

也就是说,TestFactoryImpl 工厂构造和管理了所有test_case_name_Test类对象。

那么这个TestFactoryImpl 工厂和TestCase、TestInfo、UnitCaseImpl怎么进行关联呢?

我们可以先从函数入口出发,
在main函数里,可以看到,是通过执行RUN_ALL_TESTS()来执行所有测试用例。

#include <stdio.h>
#include "gtest/gtest.h"

GTEST_API_ int main(int argc, char **argv) {
  printf("Running main() from gtest_main.cc\n");
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

而RUN_ALL_TESTS()函数实现为:

inline int RUN_ALL_TESTS() {
  return ::testing::UnitTest::GetInstance()->Run();
}

可知,是执行单例类UnitTest实例化对象的Run()函数,实现了所有测试用例的执行,所有的test_case_name_test_name_Test类的实例化对象必然和UnitTest关联。
那么,又是怎么进行关联的呢?
我们知道,所有的测试特例都保留了相对应的构造test_case_name_test_name_Test的工厂,那么必然会通过工厂,来和对应Test进行联系。
我们继续看UnitTest的Run()函数,

int UnitTest::Run() {
 return internal::HandleExceptionsInMethodIfSupported(
      impl(),
      &internal::UnitTestImpl::RunAllTests,
      "auxiliary test code (environments or event listeners)") ? 0 : 1;
}

里面只调用HandleExceptionsInMethodIfSupported()函数,该函数实现为:

gtest进行单元测试 gtest原理_单元测试_09


也即执行的是:

impl()->RunAllTests();

即执行的是:

bool UnitTestImpl::RunAllTests()
{
 	if (!Test::HasFatalFailure()) 
 	{
        for (int test_index = 0; test_index < total_test_case_count();
             test_index++) {
          GetMutableTestCase(test_index)->Run();
        }
    }
 }

其中 GetMutableTestCase实现为:

TestCase* GetMutableTestCase(int i) {
    const int index = GetElementOr(test_case_indices_, i, -1);
    return index < 0 ? NULL : test_cases_[index];
  }

可见执行的是TestCase类的Run()函数
其对应实现为:

void TestCase::Run() {
 for (int i = 0; i < total_test_count(); i++) {
    GetMutableTestInfo(i)->Run();
  }
}

又转而执行的是TestInfo类的Run()函数。
其GetMutableTestInfo()函数对应实现为:

TestInfo* TestCase::GetMutableTestInfo(int i) {
  const int index = GetElementOr(test_indices_, i, -1);
  return index < 0 ? NULL : test_info_list_[index];
}

而TestInfo类的Run()函数实现为:

void TestInfo::Run() {
  // Creates the test object.
  Test* const test = internal::HandleExceptionsInMethodIfSupported(
      factory_, &internal::TestFactoryBase::CreateTest,
      "the test fixture's constructor");
  // constructor didn't generate a fatal failure.
  if ((test != NULL) && !Test::HasFatalFailure()) {
    // This doesn't throw as all user code that can throw are wrapped into
    // exception handling code.
    test->Run();
  }

这里就很明朗了,先是生成test_case_name_test_case_Test类,并执行其基类函数Run()函数。
其基类Test的Run()函数实现为:

void Test::Run() {
 if (!HasFatalFailure()) {
    impl->os_stack_trace_getter()->UponLeavingGTest();
    internal::HandleExceptionsInMethodIfSupported(
        this, &Test::TestBody, "the test body");
  }
}

可见,最终执行了TestBody()函数。实现了对应测试用例的测试特例类的测试内容。

---部分函数定义摘自于gtest-1.7.0源码