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

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类与Call类进行了关联。
class Call: public ::testing::TestWithParam
{
};

//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类的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 
{
};

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::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 gtest_VOIPCall_EvalGenerator_() { return ValueArray2(); } 

std::string gtest_VOIPCall_EvalGenerateName_(const TestParamInfo& info) 
{ 
  return GetParamNameGen(info); 
} 

int gtest_VOIPCall_dummy_  = 
  UnitTest::GetInstance()->parameterized_test_registry(). 
    GetTestCasePatternHolder(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 Values(T1 v1, T2 v2) {
  return internal::ValueArray2(v1, v2);
}

//ValueArray2 模版类
//重载了operator ParamGenerator() const;
template <typename T1, typename T2>
class ValueArray2 
{
public:
  ValueArray2(T1 v1, T2 v2) : v1_(v1), v2_(v2) {}

  template <typename T>
  operator ParamGenerator() const 
  {
    const T array[] = {static_cast(v1_), static_cast(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 iterator;

  explicit ParamGenerator(ParamGeneratorInterface* 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 > impl_;
};


// ParamGeneratorInterface模版类

template <typename T>
class ParamGeneratorInterface 
{
public:
  typedef T ParamType;

  virtual ~ParamGeneratorInterface() {}

  virtual ParamIteratorInterface* Begin() const = 0;
  virtual ParamIteratorInterface* 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& 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&);
};

template <class ParamType>
typename ParamNameGenFunc::Type *GetParamNameGen() 
{
  return DefaultParamName;
}

3.2.5 PrintToStringParamName

struct PrintToStringParamName 
{
  template <class ParamType>
  std::string operator()(const TestParamInfo& info) const 
  {
    return PrintToString(info.param);
  }
};

3.3 参数化测试用例信息注册

int gtest_VOIPCall_dummy_  = 
  UnitTest::GetInstance()->parameterized_test_registry(). 
    GetTestCasePatternHolder(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* GetTestCasePatternHolder(
      const char* test_case_name,CodeLocation code_location) 
  {
    ParameterizedTestCaseInfo* 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()) 
        {
          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 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(GeneratorCreationFunc)();
  typedef typename ParamNameGenFunc::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(); 
  }

  /*
     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* meta_factory) 
  {
    tests_.push_back(linked_ptr(
      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* 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 > test_meta_factory;
  };

  typedef ::std::vector > 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 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, CodeLocation(__FILE__, __LINE__))->
                AddTestPattern(Call,makeCall,new TestMetaFactory());

      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 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 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 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::iterator param_it = generator.begin();
      for ( ; param_it != generator.end(); ++param_it, ++i) 
      {
        Message test_name_stream;
        std::string param_name = name_func(TestParamInfo(*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

你可能感兴趣的:(GTest)