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-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-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宏依次展开过程:
- 将TEST展开为GTEST_TEST:
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
- 将GTEST_TEST展开为GTEST_TEST_:
#define GTEST_TEST(test_case_name, test_name)\
GTEST_TEST_(test_case_name, test_name, \
::testing::Test, ::testing::internal::GetTestTypeId())
- 再将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
显然,该测试用例通过编译预处理宏展开后,由
展开为:
可见,利用行连接符"",使得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);
}
展开为:
每个测试类都有一个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工厂),总的流程可以用图表示:
对应类图为:
通过TestInfo类友元函数MakeAndRegisterTestInfo()保存了所有的测试用例及测试用例对应的所有的测试特例信息,UnitTestImpl类管理所有测试用例,TestCase类管理测试用例的所有测试特例,那么现在问题是,他们又是怎么和我们的test_case_name_Test类进行关联呢。
test_case_name_Test类(测试特例类)调度过程:
我们回头看test_case_name_Test类的成员test_info_静态对象初始化过程,他利用工厂TestFactoryImpl实例化了一个test_case_name_Test对象
模板工厂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()函数,该函数实现为:
也即执行的是:
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源码