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

Google Test(GTest)使用方法和源码解析——Listener技术分析和应用

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

        在《Google Test(GTest)使用方法和源码解析——结果统计机制分析》文中,我么分析了GTest如何对测试结果进行统计的。本文我们将解析其结果输出所使用到的Listener机制。(转载请指明出于breaksoftware的csdn博客)

解析

       源码中,我们经常要和UnitTest类打交道。它提供了一个单例方法返回自己的一个对象,然后各处代码都在调用这个单例的方法。所以说它是GTest框架中非常重要的衔接环。而在其内部,实际工作的却是一个UnitTestImpl对象

代码语言:javascript
复制
internal::UnitTestImpl* impl_;

        该指针在UnitTest构造函数中被新建出来

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

        之后,我们调用UnitTest单例的方法,很多都是直接调用该对象的方法。其构造函数是这么写的

代码语言:javascript
复制
UnitTestImpl::UnitTestImpl(UnitTest* parent)
    : parent_(parent),
      GTEST_DISABLE_MSC_WARNINGS_PUSH_(4355 /* using this in initializer */)
      default_global_test_part_result_reporter_(this),
      default_per_thread_test_part_result_reporter_(this),
      GTEST_DISABLE_MSC_WARNINGS_POP_()
      global_test_part_result_repoter_(
          &default_global_test_part_result_reporter_),
      per_thread_test_part_result_reporter_(
          &default_per_thread_test_part_result_reporter_),
      .......
      catch_exceptions_(false) {
  listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter);
}

        本文要讲解的内容将和上面的代码有很大的关系。

        在GTest测试框架中,它提出了一个Listener的概念,以供开发者监听执行过程。GTest框架就是使用Listener机制实现了结果输出。我们看下listener基类的设计

代码语言:javascript
复制
class TestEventListener {
 public:
  virtual ~TestEventListener() {}

  // Fired before any test activity starts.
  virtual void OnTestProgramStart(const UnitTest& unit_test) = 0;

  // Fired before each iteration of tests starts.  There may be more than
  // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration
  // index, starting from 0.
  virtual void OnTestIterationStart(const UnitTest& unit_test,
                                    int iteration) = 0;

  // Fired before environment set-up for each iteration of tests starts.
  virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0;

  // Fired after environment set-up for each iteration of tests ends.
  virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0;

  // Fired before the test case starts.
  virtual void OnTestCaseStart(const TestCase& test_case) = 0;

  // Fired before the test starts.
  virtual void OnTestStart(const TestInfo& test_info) = 0;

  // Fired after a failed assertion or a SUCCEED() invocation.
  virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0;

  // Fired after the test ends.
  virtual void OnTestEnd(const TestInfo& test_info) = 0;

  // Fired after the test case ends.
  virtual void OnTestCaseEnd(const TestCase& test_case) = 0;

  // Fired before environment tear-down for each iteration of tests starts.
  virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0;

  // Fired after environment tear-down for each iteration of tests ends.
  virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0;

  // Fired after each iteration of tests finishes.
  virtual void OnTestIterationEnd(const UnitTest& unit_test,
                                  int iteration) = 0;

  // Fired after all test activities have ended.
  virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0;
};

        它暴露了很多接口,每个都对应于执行过程的一个状态,比如OnTestCaseStart,字面意思就是测试用例执行开始处(要执行自定义逻辑),此处比较适合输出测试用例的基本信息;再比如OnTestCaseEnd,是测试用例执行结束处(要执行自定义逻辑),此处比较适合输出测试用例的执行结果。

        在UnitTestImpl构造函数中有个listeners()函数,其返回了UnitTestImpl类的TestEventListeners成员变量指针。从名字上看可以看出它是一个Listener的集合,因为用户可以新增自定义的Listener,所以要将其设计为一个集合。但是实际上它只是集合的封装

代码语言:javascript
复制
class GTEST_API_ TestEventListeners {
 private:
 .........
  // The actual list of listeners.
  internal::TestEventRepeater* repeater_;
  // Listener responsible for the standard result output.
  TestEventListener* default_result_printer_;
  // Listener responsible for the creation of the XML output file.
  TestEventListener* default_xml_generator_;
}

        TestEventRepeater才是Listener的集合,同时它也是继承于TestEventListener接口类。

代码语言:javascript
复制
class TestEventRepeater : public TestEventListener {
 public:
  TestEventRepeater() : forwarding_enabled_(true) {}
  virtual ~TestEventRepeater();
  void Append(TestEventListener *listener);
  TestEventListener* Release(TestEventListener* listener);

  // Controls whether events will be forwarded to listeners_. Set to false
  // in death test child processes.
  bool forwarding_enabled() const { return forwarding_enabled_; }
  void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; }

  virtual void OnTestProgramStart(const UnitTest& unit_test);
  virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration);
  virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test);
  virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test);
  virtual void OnTestCaseStart(const TestCase& test_case);
  virtual void OnTestStart(const TestInfo& test_info);
  virtual void OnTestPartResult(const TestPartResult& result);
  virtual void OnTestEnd(const TestInfo& test_info);
  virtual void OnTestCaseEnd(const TestCase& test_case);
  virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test);
  virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test);
  virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);
  virtual void OnTestProgramEnd(const UnitTest& unit_test);

 private:
  // Controls whether events will be forwarded to listeners_. Set to false
  // in death test child processes.
  bool forwarding_enabled_;
  // The list of listeners that receive events.
  std::vector<TestEventListener*> listeners_;

  GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventRepeater);
};

        这个类的设计也非常有意思,它既在成员变量listeners_中保存了一系列用户自定义的监听者(TestEventListener*对象指针),又让自身继承于TestEventListener,那就是说它自己也是一个Listener。这点比较像大内总管,自己是个太监,手下也是太监。小太监要干的活,大内总管也要能去做(继承于TestEventListener),只是大内总管不用自己亲手去做,而是调度小太监去做。这样的功能传递是通过类似下面代码的调用去实现的

代码语言:javascript
复制
// Since most methods are very similar, use macros to reduce boilerplate.
// This defines a member that forwards the call to all listeners.
#define GTEST_REPEATER_METHOD_(Name, Type) \
void TestEventRepeater::Name(const Type& parameter) { \
  if (forwarding_enabled_) { \
    for (size_t i = 0; i < listeners_.size(); i++) { \
      listeners_[i]->Name(parameter); \
    } \
  } \
}
// This defines a member that forwards the call to all listeners in reverse
// order.
#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \
void TestEventRepeater::Name(const Type& parameter) { \
  if (forwarding_enabled_) { \
    for (int i = static_cast<int>(listeners_.size()) - 1; i >= 0; i--) { \
      listeners_[i]->Name(parameter); \
    } \
  } \
}

        TestEventRepeater从TestEventListener继承来的方法便如此定义

代码语言:javascript
复制
GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest)
GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest)
GTEST_REPEATER_METHOD_(OnTestCaseStart, TestCase)
GTEST_REPEATER_METHOD_(OnTestStart, TestInfo)
GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult)
GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest)
GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest)
GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest)
GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo)
GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestCase)
GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest)

        可以见得这个大内总管,对于上面的指令只是做了一个简单的判断就抛给下面每个小太监去做了。

        说个题外话,个人觉得TestEventRepeater中repeater的翻译,不要叫做“重复者”,叫“中继者”比较好。         我们再回顾下监听者的传递过程

代码语言:javascript
复制
listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter);
代码语言:javascript
复制
void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) {
  if (default_result_printer_ != listener) {
    // It is an error to pass this method a listener that is already in the
    // list.
    delete Release(default_result_printer_);
    default_result_printer_ = listener;
    if (listener != NULL)
      Append(listener);
  }
}
代码语言:javascript
复制
void TestEventListeners::Append(TestEventListener* listener) {
  repeater_->Append(listener);
}

        SetDefaultResultPrinter方法看着是传递一个“结果打印者”,但是它实际要接受一个Listener。这个命名虽然很直观,但是也让阅读代码的人一下子转不过弯来:Listener和Printer是一回事啊!

代码语言:javascript
复制
class PrettyUnitTestResultPrinter : public TestEventListener {

        PrettyUnitTestResultPrinter各个事件处理函数我就不罗列了,它们就是一些结果输出。有兴趣的同学可以自己查看。

        然后我们再来看框架中是如何“触发”这些事件的。

        首先是UnitTestImpl::RunAllTests()函数,它处理了几个比较大级别的事件,比如程序启动和结束

代码语言:javascript
复制
bool UnitTestImpl::RunAllTests() {
  ......
  TestEventListener* repeater = listeners()->repeater();
  ......
  repeater->OnTestProgramStart(*parent_);
  ......
  for (int i = 0; forever || i != repeat; i++) {  
    ......  
    repeater->OnTestIterationStart(*parent_, i);  
    ......  	
    if (has_tests_to_run) {
      // Sets up all environments beforehand.
      repeater->OnEnvironmentsSetUpStart(*parent_);
      ForEach(environments_, SetUpEnvironment);
      repeater->OnEnvironmentsSetUpEnd(*parent_);	
      
	  // Runs the tests only if there was no fatal failure during global
      // set-up.
      if (!Test::HasFatalFailure()) {
        for (int test_index = 0; test_index < total_test_case_count();
             test_index++) {
          GetMutableTestCase(test_index)->Run();
        }
      }	  
	  
      // Tears down all environments in reverse order afterwards.
      repeater->OnEnvironmentsTearDownStart(*parent_);
      std::for_each(environments_.rbegin(), environments_.rend(),
                    TearDownEnvironment);
      repeater->OnEnvironmentsTearDownEnd(*parent_);	  
    }
    ...... 
    repeater->OnTestIterationEnd(*parent_, i);	
	...... 
  }
  ...... 
  repeater->OnTestProgramEnd(*parent_);
  ...... 
}

        然后GetMutableTestCase(test_index)->Run();进入每个测试用例的运行,它只处理了OnTestCaseStart和OnTestCaseEnd两个事件

代码语言:javascript
复制
void TestCase::Run() {
  ...... 
  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
  ...... 
  TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();
  ...... 
  repeater->OnTestCaseStart(*this);
  ...... 

  const internal::TimeInMillis start = internal::GetTimeInMillis();
  for (int i = 0; i < total_test_count(); i++) {
    GetMutableTestInfo(i)->Run();
  }
  ...... 
  repeater->OnTestCaseEnd(*this);
  ...... 
}

        GetMutableTestInfo(i)->Run();方法进入测试特例运行,它只处理了OnTestStart和OnTestEnd

代码语言:javascript
复制
void TestInfo::Run() {
  ...... 
  // Tells UnitTest where to store test result.
  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
  ...... 
  TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();
  // Notifies the unit test event listeners that a test is about to start.
  repeater->OnTestStart(*this);
  ...... 
  // Runs the test only if the test object was created and its
  // constructor didn't generate a fatal failure.
  if ((test != NULL) && !Test::HasFatalFailure()) {
    // This doesn't throw as all user code that can throw are wrapped into
    // exception handling code.
    test->Run();
  }
  ...... 
  // Notifies the unit test event listener that a test has just finished.
  repeater->OnTestEnd(*this);
  ...... 
}

        test->Run();进入了我们自定义的测试实体,其内部通过层层传导UnitTestImpl的global_test_part_result_repoter_的函数中

代码语言:javascript
复制
void DefaultGlobalTestPartResultReporter::ReportTestPartResult(
    const TestPartResult& result) {
  unit_test_->current_test_result()->AddTestPartResult(result);
  unit_test_->listeners()->repeater()->OnTestPartResult(result);
}

        如此,我们便将listener的运行机制给讲完了。顺便我们也分析了GTest默认结果输出的实现。

应用

        要使用Listener技术,我们需要实现一个继承于 testing::TestEventListener 或testing::EmptyTestEventListener的类。如果继承于testing::TestEventListener,则需要我们实现所有纯虚方法;而如果继承于testing::EmptyTestEventListener,则我们只要关注于部分我们关心的函数实现即可——因为它已经把所有纯虚方法实现为空方法了。

代码语言:javascript
复制
class MinimalistPrinter : public ::testing::EmptyTestEventListener {
    // Called before a test starts.
    virtual void OnTestStart(const ::testing::TestInfo& test_info) {
        printf("*** Test %s.%s starting.\n",
            test_info.test_case_name(), test_info.name());
    }

    // Called after a failed assertion or a SUCCEED() invocation.
    virtual void OnTestPartResult(
        const ::testing::TestPartResult& test_part_result) {
            printf("%s in %s:%d\n%s\n",
                test_part_result.failed() ? "*** Failure" : "Success",
                test_part_result.file_name(),
                test_part_result.line_number(),
                test_part_result.summary());
    }

    // Called after a test ends.
    virtual void OnTestEnd(const ::testing::TestInfo& test_info) {
        printf("*** Test %s.%s ending.\n",
            test_info.test_case_name(), test_info.name());
    }
};

        然后我们就需要在main函数中将该Listener的对象加入到框架中。此处有个地方需要注意下,由于Listener是个列表,那就意味着一堆Listener将会被执行,其中包括GTest默认的Listener——之前结果输出的实现者。如果我们只想让我们自定义的Listener执行,则要先将默认Listener去掉(下面代码第3行)。

代码语言:javascript
复制
  ::testing::TestEventListeners& listeners =
      ::testing::UnitTest::GetInstance()->listeners();
  delete listeners.Release(listeners.default_result_printer());
  listeners.Append(new MinimalistPrinter);

        这儿有个一个要注意的是:除了OnTestPartResult()之外的函数,都可以使用GTest判断类宏进行数据判断。唯独OnTestPartResult()里不可以,否则会造成OnTestPartResult()被递归调用。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 解析
  • 应用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档