gtest基本使用演示
# main.cpp
int main() {
testing::InitGoogleTest();
return RUN_ALL_TESTS();
}
# test_suite_1.cpp
TEST(test_suite_1, case1) {
EXPECT_EQ(1, 1);
}
TEST(test_suite_1, case2) {
EXPECT_EQ(1, 1);
}
# test_suite_2.cpp
TEST(test_suite_2, case1) {
EXPECT_EQ(1, 1);
}
TEST(test_suite_2, case2) {
EXPECT_EQ(1, 1);
}
问题:这些分散在不同文件中的测试用例,是怎么关联到测试框架的呢?
源码跟踪
来看看TEST宏的定义,其他TEST_F/TEST_P都类似
#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name)
#define GTEST_TEST(test_suite_name, test_name) \
GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \
::testing::internal::GetTestTypeId())
#define GTEST_TEST_F(test_suite_name, test_name, parent_class, parent_id) \
# ...前面有一些检查以及测试类定义,尽量简写 \
class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
: public parent_class { \
... }; \
TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name, \
test_name)::test_info_ = MakeAndRegisterTestInfo(...) \ #省略了相关
TEST宏展开后,除了根据test_suite_name和test_name生成一个测试类之外,还紧跟着调用了MakeAndRegisterTestInfo这个全局函数,从函数名我们可以看出来,就是它将测试类给注册到了测试框架中,我们继续跟踪下去
#gtest.cc
TestInfo* MakeAndRegisterTestInfo(
const char* test_suite_name, const char* name, const char* type_param,
const char* value_param, CodeLocation code_location,
TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc,
TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory) {
TestInfo* const test_info =
new TestInfo(test_suite_name, name, type_param, value_param,
code_location, fixture_class_id, factory);
GetUnitTestImpl()->addTestInfo(set_up_tc, tear_down_tc, test_info);
return test_info;
}
函数首先创建了一个TestInfo对象,参数都是来自TEST宏定义的一些属性,然后GetUnitTestImpl()这个函数一眼可以看出,UnitTest肯定单例模式,这是获取UnitTest的单例对象,并调用其addTestInfo方法,事实是这样么,我们继续看下去
#gtest-internal-inl.h
inline UnitTestImpl* GetUnitTestImpl() {
return UnitTest::GetInstance()->impl();
}
#gtest.cc
UnitTest* UnitTest::GetInstance() {
static UnitTest instance;
return &instance;
}
果然UnitTest是一个单例模式,这种单例写法也是现在最主流的模板,基本遇到要写单例直接照抄就行;
好了,前面就是最后一步,UnitTestImpl的addTestInfo方法了,也就是咱们这个test会被存在什么地方
#gtest-internal-inl.h
void UnitTestImpl::AddTestInfo(SetUpTestSuiteFunc set_up_tc,
TearDownTestSuiteFunc tear_down_tc,
TestInfo* test_info) {
GetTestSuite(test_info->test_suite_name(), test_info->type_param(),
set_up_tc, tear_down_tc)
->AddTestInfo(test_info);
}
#返回一个TestSuite对象
#UnitTestImpl维护了一个TestSuite数组 std::vector<TestSuite*>
#如果查询的test_suite_name已经存在则返回当前suite,不存在就向数组中添加一个
TestSuite* UnitTestImpl::GetTestSuite(...)
#gtest.cc
void TestSuite::AddTestInfo(TestInfo* test_info) {
test_info_list_.push_back(test_info);
test_idices_.push_back(static_cast<int>(test_indices_.size()));
}
这里代码其实已经很直观了,UnitTestImpl是UnitTest的具体实现,用到的技术点是c++中有名的Impl模式,是一种将实现和接口分离的技术;
UnitTestImpl中维护了一个TestSuite对象的数组,如果我们要添加的这个case的suite_name还不存在,那就新创建一个TestSuite对象,如果存在就直接返回;
而TestSuite才是真正维护case的类,TestSuite中存在一个test_info_list的数组和test_idices数组,前者是所有测试case的集合,后者是用来维护case顺序;
至此,测试用例如何自动注册到测试框架的流程就分析完了;
总结
TEST宏生成了一个Testxxx的测试类,这个类继承了Test基类,同时将其实例化;
UnitTest是一个单例模式,保证了在所有地方都可以通过同一个对象来管理Test类的对象
剩下的MakeAndRegisterTestInfo其实不是必须的,我们也可以直接调用UnitTest类的AddTestInfo,不过这样可能就显得函数比较复杂而已;
这里面用到的技术并不复杂,但是能够给我们提供多对象初始化的思路
例如如下需求:
客户端和服务器是多对一的关系,我们可能存在多种类型的客户端,新增一个客户端类型时,我们的代码除了客户端类的构造外,还涉及到与服务器的添加和删除相关代码,这是很容易遗漏的地方,通过类创建的同时将其自动与服务器关联,则可以解决该问题。