前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Google Test(GTest)使用方法和源码解析——自动调度机制分析

Google Test(GTest)使用方法和源码解析——自动调度机制分析

作者头像
方亮
发布2019-01-16 14:52:42
1.4K0
发布2019-01-16 14:52:42
举报
文章被收录于专栏:方亮方亮

        在《Google Test(GTest)使用方法和源码解析——概况 》一文中,我们简单介绍了下GTest的使用和特性。从这篇博文开始,我们将深入代码,研究这些特性的实现。(转载请指明出于breaksoftware的csdn博客)

测试用例的自动保存

       当使用一组宏构成测试代码后,我们并没有发现调用它们的地方。GTest框架实际上是通过这些宏,将我们的逻辑保存到类中,然后逐个去执行的。我们先查看TEST宏的实现

代码语言:javascript
复制
#define GTEST_TEST(test_case_name, test_name)\
  GTEST_TEST_(test_case_name, test_name, \
              ::testing::Test, ::testing::internal::GetTestTypeId())

// Define this macro to 1 to omit the definition of TEST(), which
// is a generic name and clashes with some other libraries.
#if !GTEST_DONT_DEFINE_TEST
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
#endif

        可见它只是对GTEST_TEST_宏的再次封装。GTEST_TEST_宏不仅要求传入测试用例和测试实例名,还要传入Test类名和其ID。我们将GTEST_TEST_的实现拆成三段分析

代码语言:javascript
复制
// 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));\
};\
\

        首先使用宏GTEST_TEST_CLASS_NAME_生成类名。该类暴露了一个空的默认构造函数、一个私有的虚函数TestBody、一个静态变量test_info_和一个私有的赋值运算符(将运算符=私有化,限制类对象的赋值和拷贝行为)。

        静态变量test_info的作用非常有意思,它利用”静态变量在程序运行前被初始化“的特性,抢在main函数执行之前,执行一段代码,从而有机会将测试用例放置于一个固定的位置。这个是”自动“保存测试用例的本质所在。

代码语言:javascript
复制
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
  ::test_info_ =\
    ::testing::internal::MakeAndRegisterTestInfo(\
        #test_case_name, #test_name, NULL, NULL, \
        ::testing::internal::CodeLocation(__FILE__, __LINE__), \
        (parent_id), \
        parent_class::SetUpTestCase, \
        parent_class::TearDownTestCase, \
        new ::testing::internal::TestFactoryImpl<\
            GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\

        我们先跳过这段代码,看完GTEST_TEST_宏的实现,其最后一行是

代码语言:javascript
复制
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody()

        这行要在类外提供TestBody函数的实现。我们要注意下,这个只是函数的一部分,即它只是包含了函数返回类型、函数名,而真正的函数实体是在TEST宏之后的{}内的,如

代码语言:javascript
复制
TEST(FactorialTest, Zero) {
  EXPECT_EQ(1, Factorial(0));
}

       这段代码最后应该如下,它实际上是测试逻辑的主体。

代码语言:javascript
复制
……
void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() {
  EXPECT_EQ(1, Factorial(0));
}

        可以说TEST宏的写法只是一种类函数的写法,而实际它“偷梁换柱”,实现了测试的实体。

        我们再看下test_info_的初始化逻辑,它调用了::testing::internal::MakeAndRegisterTestInfo函数。我们先关注下最后一个参数,它是一个模板类,模板是当前类名。同时从名字上看,它也是一个工厂类。该类继承于TestFactoryBase,并重载了CreateTest方法——它只是new出了一个模板类对象,并返回

代码语言:javascript
复制
template <class TestClass>
class TestFactoryImpl : public TestFactoryBase {
 public:
  virtual Test* CreateTest() { return new TestClass; }
};

        MakeAndRegisterTestInfo函数的实现也非常简单:它new出一个TestInfo类对象,并调用UnitTestImpl单例的AddTestInfo方法,将其保存起来。

代码语言:javascript
复制
TestInfo* MakeAndRegisterTestInfo(
    const char* test_case_name,
    const char* name,
    const char* type_param,
    const char* value_param,
    CodeLocation code_location,
    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,
                   code_location, fixture_class_id, factory);
  GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
  return test_info;
}

        AddTestInfo试图通过测试用例名等信息获取测试用例,然后调用测试用例对象去新增一个测试特例——test_info。这样我们在此就将测试用例和测试特例的关系在代码中找到了关联。

代码语言:javascript
复制
 GetTestCase(test_info->test_case_name(),
                test_info->type_param(),
                set_up_tc,
                tear_down_tc)->AddTestInfo(test_info);

        但是如果第一次调用TEST宏,是不会有测试用例类的,那么其中新建测试用例对象,并保存到UnitTestImpl类单例对象的test_cases_中的逻辑是在GetTestCase函数实现中

代码语言:javascript
复制
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 {
    test_cases_.push_back(new_test_case);
  }

  test_case_indices_.push_back(static_cast<int>(test_case_indices_.size()));
  return new_test_case;
}

        正如我们所料,在没有找到测试实例对象指针的情况下,新建了一个TestCase测试用例对象,并将其指针保存到了test_cases_中。如此我们就解释了,测试用例是如何被保存的了。

测试特例的保存

        接着上例的分析,如下代码将测试特例信息通过TestCase类的AddTestInfo方法保存起来

代码语言:javascript
复制
GetTestCase(test_info->test_case_name(),
                test_info->type_param(),
                set_up_tc,
                tear_down_tc)->AddTestInfo(test_info);

        其中AddTestInfo的实现如下

代码语言:javascript
复制
void TestCase::AddTestInfo(TestInfo * test_info) {
  test_info_list_.push_back(test_info);
  test_indices_.push_back(static_cast<int>(test_indices_.size()));
}

        可见test_info_list_中保存了测试特例信息。

调度的实现

        在之前的测试代码中,我们并没有发现main函数。但是C/C++语言要求程序必须要有程序入口,那Main函数呢?其实GTest为了让我们可以更简单的使用它,为我们编写了一个main函数,它位于src目录下gtest_main.cc文件中

代码语言:javascript
复制
GTEST_API_ int main(int argc, char **argv) {
  printf("Running main() from gtest_main.cc\n");
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

        Makefile文件编译了该文件,并将其链接到可执行文件中。这样我们的程序就有了入口。那么这个main函数又是如何将执行流程引到我们的代码中的呢?代码之前了无秘密。短短的这几行,只有04行才可能是我们的代码入口。(03行将程序入参传递给了Gtest库,从而实现了《Google Test(GTest)使用方法和源码解析——概况》中所述的“选择性测试”)。很显然,它的名字——RUN_ALL_TESTS也暴露了它的功能。我们来看下其实现

代码语言:javascript
复制
inline int RUN_ALL_TESTS() {
  return ::testing::UnitTest::GetInstance()->Run();
}

        它最终调用了UnitTest类的单例(GetInstance)的Run方法。UnitTest类的单例是个很重要的对象,它在源码中各处可见,它是连接各个逻辑的重要一环。我们再看下Run方法的核心实现(去除平台差异后)

代码语言:javascript
复制
return internal::HandleExceptionsInMethodIfSupported(
      impl(),
      &internal::UnitTestImpl::RunAllTests,
      "auxiliary test code (environments or event listeners)") ? 0 : 1;

        impl()方法返回了一个UnitTestImpl对象指针impl_,它是在UniTes类的构造函数中生成的(HandleExceptionsInMethodIfSupported函数见《Google Test(GTest)使用方法和源码解析——概况》分析)

代码语言:javascript
复制
UnitTest::UnitTest() {
  impl_ = new internal::UnitTestImpl(this);
}

        UnitTestImpl类的RunAllTest方法中,核心的调度代码只有这几行

代码语言:javascript
复制
for (int test_index = 0; test_index < total_test_case_count(); test_index++) {
     GetMutableTestCase(test_index)->Run();
}

        GetMutableTestCase方法逐个返回UnitTestImpl对象成员变量test_cases_中的元素——各个测试用例对象指针,然后调用测试用例的Run方法。

代码语言:javascript
复制
std::vector<TestCase*> test_cases_;

        测试用例类TestCase的Run方法逻辑也是类似的,它将逐个获取其下的测试特例信息,并调用其Run方法

代码语言:javascript
复制
  for (int i = 0; i < total_test_count(); i++) {
    GetMutableTestInfo(i)->Run();
  }

        测试特例的Run方法其核心是

代码语言:javascript
复制
  Test* const test = internal::HandleExceptionsInMethodIfSupported(
      factory_, &internal::TestFactoryBase::CreateTest,
      "the test fixture's constructor");

  if ((test != NULL) && !Test::HasFatalFailure()) {
    test->Run();
  }

        它通过构造函数传入的工厂类对象指针调用其重载的CreateTest方法,new出TEST宏中定义的使用GTEST_TEST_CLASS_NAME_命名(用例名_实例名_TEST)的类(之后称测试用例特例类)的对象指针,然后调用测试用例特例类的父类中的Run方法。由于测试用例特例类继承::testing::Test类后,并没有重载其Run方法,所以其调用的还是Test类的Run方法,而Test类的Run方法实际上只是调用了测试用例特例类重载了的TestBody方法

代码语言:javascript
复制
    internal::HandleExceptionsInMethodIfSupported(this, &Test::TestBody, "the test body");

        而TestBody就是我们之前在分析TEST宏时讲解通过“偷梁换柱”实现的虚方法。

        如此整个调度的流程就分析清楚了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年04月07日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 测试用例的自动保存
  • 测试特例的保存
  • 调度的实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档