GTest源码剖析——TEST_P宏
1 TEST_P宏用法
TEST_P宏在用法上比TEST/TEST_F宏强大很多。事实上,TEST_P宏的实现也复杂非常多。
在这里先简单介绍下TEST_P宏的使用,然后再根据它进行展开来分析源码。
//Step1:申明一个呼叫参数类,该类主要用于TEST_P宏中实现的测试逻辑使用
class CallArgs
{
public:
CallArgs(bool hasAudio,bool hasVideo):
_hasAudio(hasAudio),_hasVideo(hasVideo){}
bool audio(){ return _hasAudio;}
bool video(){ return _hasVideo;}
private:
bool _hasAudio;
bool _hasVideo;
};
//Step2:申明一个呼叫类,该类同时也是TEST_P宏的第一个参数test_case_name
// 该类继承了TestWithParam<CallArgs>模版类,从而使得CallArgs类与Call类进行了关联。
class Call: public ::testing::TestWithParam<CallArgs>
{
};
//Step3: 使用INSTANTIATE_TEST_CASE_P宏,对Call类进行类相关多个的参数设置
// 这里只添加了两个参数CallArgs(true,true)和CallArgs(true,false),事实上,可以添加多大50个参数。
// 这里使用参数生成器::testing::Values,GTest定义了了很多参数生成器。
INSTANTIATE_TEST_CASE_P(VOIP, Call, ::testing::Values(
CallArgs(true,true),
CallArgs(true,false)
) );
//Step4: 编写了使用TEST_P宏实现的测试用例
// 使用了TestWithParam<CallArgs>类的GetParam()接口获取参数CallArgs
// 实际上这是两个测试用例,即该代码段会执行两个,参数分别为CallArgs(true,true)和CallArgs(true,false)
TEST_P( Call, makeCall)
{
CallArgs args = GetParam();
ASSERT_TRUE( makeCall(args.audio(),args.video()) );
}
2 TestWithParam 类
2.1 TestWithParam 类定义
该类仅仅是继承了Test类和WithParamInterface类,这里主要介绍一下WithParamInterface模版类。
template <typename T>
class TestWithParam : public Test, public WithParamInterface<T>
{
};
2.2 WithParamInterface 模版类定义
该类定义了ParamType,用于参数型别推导(这方面可以参考STL源码剖析)。
提供了GetParam()函数,用于TST_P宏里的实现逻辑获取参数。
template <typename T>
class WithParamInterface
{
public:
typedef T ParamType;
virtual ~WithParamInterface() {}
const ParamType& GetParam() const
{
GTEST_CHECK_(parameter_ != NULL)
<< "GetParam() can only be called inside a value-parameterized test "
<< "-- did you intend to write TEST_P instead of TEST_F?";
return *parameter_;
}
private:
static void SetParam(const ParamType* parameter)
{
parameter_ = parameter;
}
//不太理解为何声明为static,可能是为了节约内存的考虑?
//申明为static后,需要保证每次运行到不同的测试用例时,其值能够匹配。
static const ParamType* parameter_;
//申明了友元类ParameterizedTestFactory,使得其可以修改调用SetParam()
template <class TestClass> friend class internal::ParameterizedTestFactory;
};
template <typename T>
const T* WithParamInterface<T>::parameter_ = NULL;
3 INSTANTIATE_TEST_CASE_P宏
3.1 INSTANTIATE_TEST_CASE_P宏展开
- 定义一个获取参数生成器函数;
- 定义一个生成参数字符串的函数;
- 通过UnitTest::GetInstance()获取UnitTest类的单例;
- 通过parameterized_test_registry()获取UnitTest单例类的参数注册器ParameterizedTestCaseRegistry;
- 通过GetTestCasePatternHolder获取参数化测试用例的信息ParameterizedTestCaseInfo;
- 通过AddTestCaseInstantiation()添加TestCase的信息到GTest中。
#define INSTANTIATE_TEST_CASE_P(VOIP, Call, Values,... ) )
ParamGenerator<CallArgs> gtest_VOIPCall_EvalGenerator_() { return ValueArray2(); }
std::string gtest_VOIPCall_EvalGenerateName_(const TestParamInfo<CallArgs>& info)
{
return GetParamNameGen<CallArgs>(info);
}
int gtest_VOIPCall_dummy_ =
UnitTest::GetInstance()->parameterized_test_registry().
GetTestCasePatternHolder<Call>(Call, CodeLocation(__FILE__, __LINE__))->
AddTestCaseInstantiation(VOIP,
>est_VOIPCall_EvalGenerator_,
>est_VOIPCall_EvalGenerateName_,
__FILE__, __LINE__)
3.2 参数生成器
3.2.1 参数生成器:Values
实际上Values是一组生成器,目前支持参数从1到50。
//Values
template <typename T1, typename T2>
internal::ValueArray2<T1, T2> Values(T1 v1, T2 v2) {
return internal::ValueArray2<T1, T2>(v1, v2);
}
//ValueArray2 模版类
//重载了operator ParamGenerator<T>() const;
template <typename T1, typename T2>
class ValueArray2
{
public:
ValueArray2(T1 v1, T2 v2) : v1_(v1), v2_(v2) {}
template <typename T>
operator ParamGenerator<T>() const
{
const T array[] = {static_cast<T>(v1_), static_cast<T>(v2_)};
return ValuesIn(array);
}
private:
void operator=(const ValueArray2& other);
const T1 v1_;
const T2 v2_;
};
3.2.2 ParamGenerator模版类
template<typename T>
class ParamGenerator
{
public:
typedef ParamIterator<T> iterator;
explicit ParamGenerator(ParamGeneratorInterface<T>* impl) : impl_(impl) {}
ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {}
ParamGenerator& operator=(const ParamGenerator& other)
{
impl_ = other.impl_;
return *this;
}
iterator begin() const { return iterator(impl_->Begin()); }
iterator end() const { return iterator(impl_->End()); }
private:
linked_ptr<const ParamGeneratorInterface<T> > impl_;
};
// ParamGeneratorInterface模版类
template <typename T>
class ParamGeneratorInterface
{
public:
typedef T ParamType;
virtual ~ParamGeneratorInterface() {}
virtual ParamIteratorInterface<T>* Begin() const = 0;
virtual ParamIteratorInterface<T>* End() const = 0;
};
3.2.3 TestParamInfo struct
主要是固定参数及其index
template <class ParamType>
struct TestParamInfo
{
TestParamInfo(const ParamType& a_param, size_t an_index) :
param(a_param),
index(an_index) {}
ParamType param;
size_t index;
};
3.2.4 GetParamNameGen
//默认生成包含索引的字符串
template <class ParamType>
std::string DefaultParamName(const TestParamInfo<ParamType>& info) {
Message name_stream;
name_stream << info.index;
return name_stream.GetString();
}
template <class ParamType, class ParamNameGenFunctor>
ParamNameGenFunctor GetParamNameGen(ParamNameGenFunctor func) {
return func;
}
template <class ParamType>
struct ParamNameGenFunc
{
typedef std::string Type(const TestParamInfo<ParamType>&);
};
template <class ParamType>
typename ParamNameGenFunc<ParamType>::Type *GetParamNameGen()
{
return DefaultParamName;
}
3.2.5 PrintToStringParamName
struct PrintToStringParamName
{
template <class ParamType>
std::string operator()(const TestParamInfo<ParamType>& info) const
{
return PrintToString(info.param);
}
};
3.3 参数化测试用例信息注册
int gtest_VOIPCall_dummy_ =
UnitTest::GetInstance()->parameterized_test_registry().
GetTestCasePatternHolder<Call>(Call, CodeLocation(__FILE__, __LINE__))->
AddTestCaseInstantiation(VOIP,
>est_VOIPCall_EvalGenerator_,
>est_VOIPCall_EvalGenerateName_,
__FILE__, __LINE__)
3.3.1 ParameterizedTestCaseRegistry类
class ParameterizedTestCaseRegistry
{
public:
ParameterizedTestCaseRegistry() {}
//析构时销毁资源,而这些资源是在GetTestCasePatternHolder函数中创建的
~ParameterizedTestCaseRegistry()
{
TestCaseInfoContainer::iterator it = test_case_infos_.begin();
for ( ; it != test_case_infos_.end(); ++it)
{
delete *it;
}
}
template <class TestCase>
ParameterizedTestCaseInfo<TestCase>* GetTestCasePatternHolder(
const char* test_case_name,CodeLocation code_location)
{
ParameterizedTestCaseInfo<TestCase>* typed_test_info = NULL;
//遍历test_case_infos_查找test_case_name
TestCaseInfoContainer::iterator it = test_case_infos_.begin();
for ( ; it != test_case_infos_.end(); ++it)
{
if ( (*it)->GetTestCaseName() == test_case_name)
{
//如果找到,对比TypeID是否一致;如果不一致则终止程序;
if ((*it)->GetTestCaseTypeId() != GetTypeId<TestCase>())
{
ReportInvalidTestCaseType(test_case_name, code_location);
posix::Abort();
}
else
{
// 强制类型转换
typed_test_info = CheckedDowncastToActualType<ParameterizedTestCaseInfo<TestCase> >(*it);
}
break;
}
}
//如果找不到对应的test_case_name,则创建一个新的typed_test_info,并插入test_case_infos_中
if (typed_test_info == NULL)
{
typed_test_info = new ParameterizedTestCaseInfo<TestCase>(test_case_name, code_location);
test_case_infos_.push_back(typed_test_info);
}
return typed_test_info;
}
//循环遍历test_case_infos,并调用TestCase的RegisterTests()函数。
void RegisterTests()
{
TestCaseInfoContainer::iterator it = test_case_infos_.begin();
for ( ; it != test_case_infos_.end(); ++it)
{
(*it)->RegisterTests();
}
}
private:
typedef ::std::vector<ParameterizedTestCaseInfoBase*> TestCaseInfoContainer;
TestCaseInfoContainer test_case_infos_;
GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseRegistry);
};
3.3.2 ParameterizedTestCaseInfoBase类
class ParameterizedTestCaseInfoBase
{
public:
virtual ~ParameterizedTestCaseInfoBase() {}
virtual const std::string& GetTestCaseName() const = 0;
virtual TypeId GetTestCaseTypeId() const = 0;
virtual void RegisterTests() = 0;
protected:
ParameterizedTestCaseInfoBase() {}
private:
GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfoBase);
};
3.3.3 ParameterizedTestCaseInfo类
template <class TestCase>
class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase
{
public:
typedef typename TestCase::ParamType ParamType;
typedef ParamGenerator<ParamType>(GeneratorCreationFunc)();
typedef typename ParamNameGenFunc<ParamType>::Type ParamNameGeneratorFunc;
explicit ParameterizedTestCaseInfo(const char* name, CodeLocation code_location):
test_case_name_(name), code_location_(code_location) {}
virtual const std::string& GetTestCaseName() const
{
return test_case_name_;
}
//获取TestCase的ID,用于唯一标识作用,
//在ParameterizedTestCaseRegistry::GetTestCasePatternHolder处有使用其进行容错判断
virtual TypeId GetTestCaseTypeId() const
{
return GetTypeId<TestCase>();
}
/*
TEST_P macro uses AddTestPattern() to record information
about a single test in a LocalTestInfo structure.
test_case_name is the base name of the test case (without invocation
prefix). test_base_name is the name of an individual test without
parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is
test case base name and DoBar is test base name.
*/
void AddTestPattern(const char* test_case_name,
const char* test_base_name,
TestMetaFactoryBase<ParamType>* meta_factory)
{
tests_.push_back(linked_ptr<TestInfo>(
new TestInfo(test_case_name,test_base_name,meta_factory)));
}
//仅仅是把信息插入nstantiations_中
int AddTestCaseInstantiation(const std::string& instantiation_name,
GeneratorCreationFunc* func,
ParamNameGeneratorFunc* name_func,
const char* file, int line)
{
instantiations_.push_back(InstantiationInfo(instantiation_name, func, name_func, file, line));
return 0;
}
virtual void RegisterTests()
{
//这部分代码比较多,这里先不展开。
//此处的作用是真正的注册测试用例信息。
//其在InitGoogleTest接口里才会得到调用。
//详细参考 <<5.1.3 ParameterizedTestCaseInfo::RegisterTests()>>
} // RegisterTests
private:
struct TestInfo
{
TestInfo(const char* a_test_case_base_name,
const char* a_test_base_name,
TestMetaFactoryBase<ParamType>* a_test_meta_factory) :
test_case_base_name(a_test_case_base_name),
test_base_name(a_test_base_name),
test_meta_factory(a_test_meta_factory) {}
const std::string test_case_base_name;
const std::string test_base_name;
const scoped_ptr<TestMetaFactoryBase<ParamType> > test_meta_factory;
};
typedef ::std::vector<linked_ptr<TestInfo> > TestInfoContainer;
struct InstantiationInfo
{
InstantiationInfo(const std::string &name_in,
GeneratorCreationFunc* generator_in,
ParamNameGeneratorFunc* name_func_in,
const char* file_in,
int line_in)
: name(name_in),
generator(generator_in),
name_func(name_func_in),
file(file_in),
line(line_in) {}
std::string name;
GeneratorCreationFunc* generator;
ParamNameGeneratorFunc* name_func;
const char* file;
int line;
};
typedef ::std::vector<InstantiationInfo> InstantiationContainer;
static bool IsValidParamName(const std::string& name)
{
if (name.empty())
{
return false;
}
for (std::string::size_type index = 0; index < name.size(); ++index)
{
if (!isalnum(name[index]) && name[index] != '_')
{
return false;
}
}
return true;
}
const std::string test_case_name_;
CodeLocation code_location_;
TestInfoContainer tests_;
InstantiationContainer instantiations_;
GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfo);
};
3.3.4 TestMetaFactoryBase类
简单的工厂类
template <class ParamType>
class TestMetaFactoryBase
{
public:
virtual ~TestMetaFactoryBase() {}
virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0;
};
4 TEST_P宏
4.1 TEST_P宏展开
eg 如前所述:
TEST_P( Call, makeCall)
{
CallArgs args = GetParam();
ASSERT_TRUE( makeCall(args.audio(),args.video()) );
}
展开如下:
定义了一个继承Call的Call_makeCall_Test拼接类。
与TEST宏不同的是:
1. TEST_P宏中的TestBody()函数为public;而TEST宏中的TestBody()函数为private;
2. TEST_P宏中定义了AddToRegistry()类成员用于注册信息,
而TEST宏是使用MakeAndRegisterTestInfo()接口进行信息注册。
# define TEST_P(Call, makeCall)
// Step1:申明一个拼接类
class Call_makeCall_Test : public Call
{
public:
Call_makeCall_Test {}
virtual void TestBody();
private:
//1: 通过UnitTest::GetInstance()获取UnitTest类的单例;
//2: 通过parameterized_test_registry()获取UnitTest单例类的ParameterizedTestCaseRegistry;
//3: 通过GetTestCasePatternHolder获取参数化测试用例的信息ParameterizedTestCaseInfo;
// 因为在INSTANTIATE_TEST_CASE_P处调用时已创建ParameterizedTestCaseInfo,故此次仅是获取;
//4: 通过ParameterizedTestCaseInfo类的AddTestPattern函数添加TestCase的信息到GTest中。
// 与INSTANTIATE_TEST_CASE_P宏调用AddTestCaseInstantiation函数不同的是,
// 这里调用AddTestPattern函数添加了具体的测试用例信息,即如“makeCall”。
static int AddToRegistry()
{
::testing::UnitTest::GetInstance()->parameterized_test_registry().
GetTestCasePatternHolder<Call>(
Call, CodeLocation(__FILE__, __LINE__))->
AddTestPattern(Call,makeCall,new TestMetaFactory<Call_makeCall_Test>());
return 0;
}
static int gtest_registering_dummy_ ;
GTEST_DISALLOW_COPY_AND_ASSIGN_(Call_makeCall_Test);
};
// Step2:注册测试用例信息
int Call_makeCall_Test::gtest_registering_dummy_ = Call_makeCall_Test::AddToRegistry();
// Step3:对TestBody()进行实现
void Call_makeCall_Test::TestBody()
{
CallArgs args = GetParam();
ASSERT_TRUE( makeCall(args.audio(),args.video()) );
}
5 测试用例信息注册的真正时机
事实上,到目前为止,测试用例的信息仍未真正的注册。
TEST宏比较分立,所以它是有一个用例注册一次。与TEST/TEST_P宏不同的是,TEST_P宏需要和INSTANTIATE_TEST_CASE_P宏进行合作,所以其实际上真正注册信息的时机在initGoogleTest函数里实现的。因为initGoogleTest函数在main函数里面调用,此时TEST_P宏和INSTANTIATE_TEST_CASE_P宏的信息已经全部加载完毕,此时再统一进行信息注册。
InitGoogleTest();
==>InitGoogleTestImpl();
==>GetUnitTestImpl()->PostFlagParsingInit();
==>UnitTestImpl::RegisterParameterizedTests();
5.1 UnitTestImpl::RegisterParameterizedTests()
5.1.1 UnitTestImpl::RegisterParameterizedTests()
通过parameterized_tests_registered_确保该函数只调用一次
其实现是通过调用ParameterizedTestCaseRegistry类的RegisterTests()函数实现。
void UnitTestImpl::RegisterParameterizedTests()
{
#if GTEST_HAS_PARAM_TEST
if (!parameterized_tests_registered_)
{
parameterized_test_registry_.RegisterTests();
parameterized_tests_registered_ = true;
}
#endif
}
5.1.2 ParameterizedTestCaseRegistry::RegisterTests()
- 在ParameterizedTestCaseRegistry类中,所有测试用例信息都保存在test_case_infos_,是INSTANTIATE_TEST_CASE_P宏调用GetTestCasePatternHolder函数,创建并保存的信息数据。
ParameterizedTestCaseRegistry::RegisterTests()的实现是,循环遍历test_case_infos_数组,并逐次调用ParameterizedTestCaseInfoBase::RegisterTests()函数进行注册。
void ParameterizedTestCaseRegistry::RegisterTests()
{
//typedef ::std::vector<ParameterizedTestCaseInfoBase*> TestCaseInfoContainer;
TestCaseInfoContainer::iterator it = test_case_infos_.begin();
for ( ; it != test_case_infos_数组,.end(); ++it)
{
(*it)->RegisterTests();
}
}
5.1.3 ParameterizedTestCaseInfo::RegisterTests()
事实上,ParameterizedTestCaseInfoBase是个虚类,提供相关接口,在GTest主要是ParameterizedTestCaseInfo类进行实现。我们真实的注册也是通过ParameterizedTestCaseInfo::RegisterTests()接口完成的。
virtual void ParameterizedTestCaseInfo::RegisterTests()
{
//循环遍历tests_,tests_值是在TEST_P调用AddTestPattern函数添加的。
typename TestInfoContainer::iterator test_it = tests_.begin();
for ( ; test_it != tests_.end(); ++test_it)
{
linked_ptr<TestInfo> test_info = *test_it;
typename InstantiationContainer::iterator gen_it = instantiations_.begin();
//循环遍历instantiations_
//其值是INSTANTIATE_TEST_CASE_P宏调用AddTestCaseInstantiation()函数添加的。
for ( ; gen_it != instantiations_.end(); ++gen_it)
{
const std::string& instantiation_name = gen_it->name;
ParamGenerator<ParamType> generator((*gen_it->generator)());
ParamNameGeneratorFunc* name_func = gen_it->name_func;
const char* file = gen_it->file;
int line = gen_it->line;
//此处进行拼接test_case_name;
//eg:instantiation_name :"VOIP"
// :test_info->test_case_base_name: "Call"
// ===>test_case_name = "VOIP/Call"
std::string test_case_name;
if ( !instantiation_name.empty() )
{
test_case_name = instantiation_name + "/";
}
test_case_name += test_info->test_case_base_name;
size_t i = 0;
std::set<std::string> test_param_names;
//循环遍历参数生成器,并拼接测试用例名
//eg: ==> "makeCall/0"
==> "makeCall/1"
typename ParamGenerator<ParamType>::iterator param_it = generator.begin();
for ( ; param_it != generator.end(); ++param_it, ++i)
{
Message test_name_stream;
std::string param_name = name_func(TestParamInfo<ParamType>(*param_it, i));
GTEST_CHECK_(IsValidParamName(param_name))
<< "Parameterized test name '" << param_name
<< "' is invalid, in " << file
<< " line " << line << std::endl;
GTEST_CHECK_(test_param_names.count(param_name) == 0)
<< "Duplicate parameterized test name '" << param_name
<< "', in " << file << " line " << line << std::endl;
test_param_names.insert(param_name);
test_name_stream << test_info->test_base_name << "/" << param_name;
//调用MakeAndRegisterTestInfo进行信息注册,该函数分析详见《GTest源码剖析——TEST宏》处
MakeAndRegisterTestInfo(
test_case_name.c_str(),
test_name_stream.GetString().c_str(),
NULL, // No type parameter.
PrintToString(*param_it).c_str(),
code_location_,
GetTestCaseTypeId(),
TestCase::SetUpTestCase,
TestCase::TearDownTestCase,
test_info->test_meta_factory->CreateTestFactory(*param_it));
}
}
}
} // RegisterTests
如上,正式完成了TEST_P宏的信息注册。
6 参考
ZhaiPillar
2017-09-16