GTest源码剖析(四)——TEST_P宏

来源:互联网 发布:南山数据恢复 编辑:程序博客网 时间:2024/06/03 17:41

GTest源码剖析——TEST_P宏

  • GTest源码剖析TEST_P宏
    • TEST_P宏用法
    • TestWithParam 类
      • 1 TestWithParam 类定义
      • 2 WithParamInterface 模版类定义
    • INSTANTIATE_TEST_CASE_P宏
      • 1 INSTANTIATE_TEST_CASE_P宏展开
      • 2 参数生成器
        • 21 参数生成器Values
        • 22 ParamGenerator模版类
        • 23 TestParamInfo struct
        • 24 GetParamNameGen
        • 25 PrintToStringParamName
      • 3 参数化测试用例信息注册
        • 31 ParameterizedTestCaseRegistry类
        • 32 ParameterizedTestCaseInfoBase类
        • 33 ParameterizedTestCaseInfo类
        • 34 TestMetaFactoryBase类
    • TEST_P宏
      • 1 TEST_P宏展开
    • 测试用例信息注册的真正时机
      • 1 UnitTestImplRegisterParameterizedTests
        • 11 UnitTestImplRegisterParameterizedTests
        • 12 ParameterizedTestCaseRegistryRegisterTests
        • 13 ParameterizedTestCaseInfoRegisterTests
    • 参考

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宏展开

  1. 定义一个获取参数生成器函数;
  2. 定义一个生成参数字符串的函数;
  3. 通过UnitTest::GetInstance()获取UnitTest类的单例;
  4. 通过parameterized_test_registry()获取UnitTest单例类的参数注册器ParameterizedTestCaseRegistry;
  5. 通过GetTestCasePatternHolder获取参数化测试用例的信息ParameterizedTestCaseInfo;
  6. 通过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,                    &gtest_VOIPCall_EvalGenerator_,                     &gtest_VOIPCall_EvalGenerateName_,                     __FILE__, __LINE__)

3.2 参数生成器

3.2.1 参数生成器:Values

实际上Values是一组生成器,目前支持参数从1到50。

//Valuestemplate <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,                    &gtest_VOIPCall_EvalGenerator_,                     &gtest_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,并调用TestCaseRegisterTests()函数。  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()>>  }  // RegisterTestsprivate:  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()

  1. 在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 参考

github: googletest


ZhaiPillar
2017-09-16