文档开发者中心
我的

GoogleTest常用宏源码探究(二)

背景简介


前篇介绍了GoogleTest框架的几个重要的API宏,支撑起了整个测试执行的流程。本节主要介绍另一类重要的API宏,即测试中使用的断言宏,是测试的具体实现。


前篇: GoogleTest常用宏源码探究(一)


AssertionResult类


这是Gtest断言机制中非常重要的一个类,顾名思义,它表示断言的结果,然而不仅如此,它还包含失败的原因信息。


它有两个私有成员变量,一个布尔变量表示测试的成败,一个字符串指针指向失败原因信息;其公开成员方法有 = , ! , << 等运算符以及拷贝构造函数的重写,messege()方法来获取信息,并且定义了转换运算符bool(),可当做bool来作为if的条件使用。


该类实例的创建通过工厂方法:AssertionSuccess()和AssertionFailure()。这两个函数的用法如下


定义一个判断偶数的函数

  testing::AssertionResult IsEven(int n) {

    if ((n % 2) == 0)

      return testing::AssertionSuccess();

    else

      return testing::AssertionFailure() << n << ' is odd';

  }


我们断言5为偶数:EXPECT_TRUE(IsEven(Fib(5)))

结果当然为failure,它会显示如下详细信息:


  Value of: IsEven(Fib(5))

    Actual: false (5 is odd)

  Expected: true


而不是这种模糊的信息


  Value of: IsEven(Fib(5))

    Actual: false

  Expected: true


上面这个例子很简单,只是一个布尔判断。

当我们所处理的情况更为复杂时,这种展示错误原因就显得很重要了。


如果我们希望无论是正向断言还是负向断言出错时都提供一些信息

(一般当正向断言和负向断言出现几率大致相当时才这么做)


  testing::AssertionResult IsEven(int n) {

    if ((n % 2) == 0)

      return testing::AssertionSuccess() << n << ' is even';

    else

      return testing::AssertionFailure() << n << ' is odd';

  }


断言Fib(6)是偶数为假,EXPECT_FALSE(IsEven(Fib(6))) 将会显示


  Value of: IsEven(Fib(6))

    Actual: true (8 is even)

  Expected: false


注意:在正向断言中穿插负向断言会导致效率很低,

因此不要在大量(万以上数量级)正向断言中使用负向断言。

原文: Predicates that support negative Boolean assertions have reduced

performance in positive ones so be careful not to use them in tests

that have lots (tens of thousands) of positive Boolean assertions.


在EXPECT_PRED_FORMAT断言中使用该类,例如:


验证某函数Foo() 是否返回一个偶数值

  EXPECT_PRED_FORMAT1(IsEven, Foo());


你需要这样定义:


  testing::AssertionResult IsEven(const char* expr, int n) {

    if ((n % 2) == 0)

      return testing::AssertionSuccess();

    else

      return testing::AssertionFailure()

        << 'Expected: ' << expr << ' is even  Actual: it's ' << n;

  }

如果Foo()返回5,那么将显示:

  Expected: Foo() is even

    Actual: it's 5


ASSERT_XX()、EXPECT_XX()系列宏


ASSERT_XX和EXPECT_XX总是成对出现,区别在于ASSERT_XX失败会中止流程,EXPECT_XX失败流程会继续。


XX在这里可以是:


————————————————————————————————————————————————

THROW,有指定类型的异常抛出

NO_THROW,没有任何异常抛出

ANY_THROW,抛出了任意类型的异常

EQ,相等

NE,不等

LE,小于等于

LT,小于

GE,大于等于

GT,大于

TRUE,为真

False,为假

STREQ,字符串相等

STRNE,字符串不等

STRCASEEQ,字符串相等(不区分大小写)

STRCASENE,字符串不等(不区分大小写)

FLOAT_EQ,浮点数相等

DOUBLE_EQ,双精浮点数相等

NEAR,两个双精浮点数之差在指定范围内

HRESULT_SUCCEEDED,Window数据类型HRESULT值为SUCCEEDED

HRESULT_FAILED,Window数据类型HRESULT值为FAILED

NO_FATAL_FAILURE,当前线程无fatal级错误


————————————————————————————————————————————————


以上这些ASSERT_XX,EXPECT_XX的宏定义在gtest/gtest.h,进行一级展开后可归结为几类,每类以一个定义为例子:


异常类:GTEST_TEST_THROW_,GTEST_TEST_NO_THROW_,GTEST_TEST_ANY_THROW_


#define EXPECT_THROW(statement, expected_exception)

GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_)

#define EXPECT_NO_THROW(statement)

 GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_)

#define EXPECT_ANY_THROW(statement)

 GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_)


布尔类:GTEST_TEST_BOOLEAN_


#define EXPECT_TRUE(condition)

GTEST_TEST_BOOLEAN_(condition, #condition, false, true,

GTEST_NONFATAL_FAILURE_)


EXPECT数值比较类:EXPECT_PRED_FORMAT2


#define EXPECT_EQ(val1, val2)  

EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)


ASSERT数值比较类:ASSERT_PRED_FORMAT2


#define GTEST_ASSERT_EQ(val1, val2)

 ASSERT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)

#if !GTEST_DONT_DEFINE_ASSERT_EQ

#define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2)

#endif


EXPECT HRESULT类:EXPECT_PRED_FORMAT1


#define EXPECT_HRESULT_SUCCEEDED(expr)    

EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr))


ASSETY HRESULT类:ASSERT_PRED_FORMAT1


#define ASSERT_HRESULT_SUCCEEDED(expr)    

ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr))

致命错误类:GTEST_TEST_NO_FATAL_FAILURE_


#define ASSERT_NO_FATAL_FAILURE(statement)  

 GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_)


这些宏的进一步展开定义在gtest/internal/gtest-internal.h或gtest/gtest_pred_impl.h,下面对这些宏按分类进行展开:


共性:

1.       注意到ASSERT_EQ等宏和EXPECT_EQ等宏的定义方式不同,ASSERT首先定义的是GTEST_ASSERT_EQ宏为ASSERT_PRED_FORMAT2,在ASSERT_EQ无冲突的情况下才定义ASSERT_EQ为GTEST_ASSERT_EQ。因为ASSERT很多时候会在别处也有定义,这时我们还可以使用GTEST_ASSERT_EQ来避免冲突。

2.       另外,ASSERT_XX和EXPECT_XX最终都合并到一个宏,二者主要差别在于在展开的宏中的一个参数值不同,ASSERT该参数值为GTEST_FATAL_FAILURE,而EXPECT该参数值为GTEST_NONFATAL_FAILURE。

GTEST_FATAL_FAILURE和GTEST_NONFATAL_FAILURE用于传递错误信息。其相关定义如下:


#define GTEST_MESSAGE_AT_(file, line, message, result_type)  

::testing::internal::AssertHelper(result_type, file, line, message)

   = ::testing::Message()


#define GTEST_MESSAGE_(message, result_type)

 GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type)


#define GTEST_FATAL_FAILURE_(message)

 return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure)


#define GTEST_NONFATAL_FAILURE_(message)

 GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure)


#define GTEST_SUCCESS_(message)

 GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess)


#define GTEST_SKIP_(message)

 return GTEST_MESSAGE_(message, ::testing::TestPartResult::kSkip)


可以看到二者代表两种不同级别错误,最终都扩展为创建一个::testing::internal::AssertHelper对象,并注册一个新的Message对象(这里=运算符被重写,意义并不是赋值或拷贝)。这两个宏分别在ASSERT和EXPECT断言失败时被调用。


AssertHelper类使得信息可传入断言宏,Message类就像是一个输出流缓存,我们将信息流输入Message存储,然后将Messege传到输出流使得信息流输出。


3.在后续的展开中,都会有GTEST_AMBIGUOUS_ELSE_BLOCKER_这个宏,它的作用是关闭gcc编译器对于ambiguous else的warning。该warning出现当嵌套的if语句后跟随else语句,没有{}来明确else绑定哪个if时。其方式是将其放到switch (0) case 0: default:内,定义如下:


// The GNU compiler emits a warning if nested 'if' statements are followed by

// an 'else' statement and braces are not used to explicitly disambiguate the

// 'else' binding.  This leads to problems with code like:

//

//   if (gate)

//     ASSERT_*(condition) << 'Some message';

//

// The 'switch (0) case 0:' idiom is used to suppress this.

#ifdef __INTEL_COMPILER

# define GTEST_AMBIGUOUS_ELSE_BLOCKER_

#else

# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: default:  // NOLINT

#endif


异常类断言宏


#define GTEST_TEST_THROW_(statement, expected_exception, fail)  

GTEST_AMBIGUOUS_ELSE_BLOCKER_

 if (::testing::internal::ConstCharPtr gtest_msg = '') {

   bool gtest_caught_expected = false;

   try {

     GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement);

   }

   catch (expected_exception const&) {

     gtest_caught_expected = true;

   }

   catch (...) {

     gtest_msg.value =

         'Expected: ' #statement ' throws an exception of type '

         #expected_exception '.  Actual: it throws a different type.';

     goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__);

   }

   if (!gtest_caught_expected) {

     gtest_msg.value =

         'Expected: ' #statement ' throws an exception of type '

         #expected_exception '.  Actual: it throws nothing.';

     goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__);

   }

 } else

   GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__):

     fail(gtest_msg.value)


#define GTEST_TEST_NO_THROW_(statement, fail)

 GTEST_AMBIGUOUS_ELSE_BLOCKER_

 if (::testing::internal::AlwaysTrue()) {

   try {

     GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement);

   }

   catch (...) {

     goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__);

   }

 } else

   GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__):

     fail('Expected: ' #statement ' doesn't throw an exception. '

          '  Actual: it throws.')


#define GTEST_TEST_ANY_THROW_(statement, fail)

 GTEST_AMBIGUOUS_ELSE_BLOCKER_

 if (::testing::internal::AlwaysTrue()) {

   bool gtest_caught_any = false;

   try {

     GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement);

   }

   catch (...) {

     gtest_caught_any = true;

   }

   if (!gtest_caught_any) {

     goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__);

   }

 } else

   GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__):

     fail('Expected: ' #statement ' throws an exception. '

          '  Actual: it doesn't.')


上段代码中GTEST_CONCAT_TOKEN_宏是一个跳转标记


#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar)

#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar


可以看到这段代码有些不寻常,if的条件是AlwaysTrue(),该方法在头文件中解释为永远返回true,这将导致else永远不会被执行。然后在if中嵌套一个if,如果真则使用goto跳转到else部分。


为什么使用这种逻辑结构有些令人不解,也许因为AlwaysTrue()的实现暗藏玄机,并非真的永远返回true?或者只是想把else绑定到内嵌的if,出于编译相关的某种原因而这样处理?这个函数定义在internal中,不是开源的,我们无法知晓。


回到代码的主逻辑,可以看到是使用try-catch来捕获异常,try中使用一个宏GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_,该宏展开为:


#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)

 if (::testing::internal::AlwaysTrue()) { statement; }


再次出现了if(AlwaysTrue())的用法,官方解释是为了关闭MSVC warning 4072(unreachable code)。


然后整体逻辑就很简单了,在try中执行方法,捕获到异常后,会调用fail方法注册相关失败信息,fail这里是GTEST_FATAL_FAILURE或GTEST_NONFATAL_FAILURE宏,其展开参见前面谈及的共性2。


布尔类断言宏


这类宏最终展开是一个if-else语句,将表达式的结果(bool型)转为gtest的类型AssertionResult,当其不为true时,调用fail方法注册相关失败信息。


expression在这里是一个由前面传进来的函数表达式,这类函数主要完成比较运算,返回一个AssertionResult对象。


#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail)

 GTEST_AMBIGUOUS_ELSE_BLOCKER_

 if (const ::testing::AssertionResult gtest_ar_ =

     ::testing::AssertionResult(expression))

   ;

 else

   fail(::testing::internal::GetBoolAssertionFailureMessage(

       gtest_ar_, text, #actual, #expected).c_str())


数值比较类断言宏


这类宏无论是ASSERT_XX还是EXPECT_XX最终都展为GTEST_ASSERT_,而GTEST_ASSERT_展为一个if-else语句,同上。


#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)

GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2),

               on_failure)


#define EXPECT_PRED_FORMAT2(pred_format, v1, v2)

 GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)

#define ASSERT_PRED_FORMAT2(pred_format, v1, v2)

 GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_)

#define GTEST_ASSERT_(expression, on_failure)  

GTEST_AMBIGUOUS_ELSE_BLOCKER_

 if (const ::testing::AssertionResult gtest_ar = (expression))

   ;

 else

   on_failure(gtest_ar.failure_message())


HRESULT类断言宏


这类宏也都转为GTEST_ASSERT_(),同上


#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)

GTEST_ASSERT_(pred_format(#v1, v1),

               on_failure)


#define EXPECT_PRED_FORMAT1(pred_format, v1)

 GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_)

#define ASSERT_PRED_FORMAT1(pred_format, v1)

 GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_)


致命错误类断言宏


该宏用于断言一个函数过程中不发生致命错误。其逻辑结构和上面的类似,并且也出现了if-goto-else的结构。


#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail)  

GTEST_AMBIGUOUS_ELSE_BLOCKER_

 if (::testing::internal::AlwaysTrue()) {

   ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker;

   GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement);

   if (gtest_fatal_failure_checker.has_new_fatal_failure()) {

     goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__);

   }

 } else

   GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__):

     fail('Expected: ' #statement ' doesn't generate new fatal '

          'failures in the current thread. '

          '  Actual: it does.')


SUCCEEDED()、FAILURE()系列宏


上一节的ASSERT、EXPECT宏通过一个判断过程来得出AssertionResult,而本节的这些宏将直接指定AssertionResult,用于我们自定义更复杂的测试结果判断方法。


这类宏具体有以下:


————————————————————————————————————————————————


ADD_FAILURE()      产生一个非致命错误,附带通用信息“Failed”

ADD_FAILURE_AT()   同上,并额外指明所在文件和行数

GTEST_FAIL()    产生一个致命错误,附带通用信息“Failed”

GTEST_FAIL_AT() 同上,并额外指明所在文件和行数

FAIL()             同GTEST_FAIL()

GTEST_SUCCEED() 产生一个成功结果,附带通用信息“Succeeded”

SUCCEED()          同GTEST_SUCCEED()


————————————————————————————————————————————————


这类宏最终都展开为GTEST_MESSAGE_AT_,和ASSERT、EXPECT的if语句的条件内容是一样的,参见前面的共性2。


#define GTEST_MESSAGE_AT_(file, line, message, result_type)  

::testing::internal::AssertHelper(result_type, file, line, message)

   = ::testing::Message()

____________________________________________________


BY:HW-HH

点赞
收藏
回复
6
分享
举报
浏览1121 发布于2019-10-24 03:44未知归属地
全部评论
最多点赞
最新发布
最早发布

不错,学到点东西了


6楼回复于2019-10-28 03:48 未知归属地

这答复感觉蛮靠谱的


5楼回复于2019-10-26 05:23 未知归属地

写的蛮好的啊


4楼回复于2019-10-25 05:40 未知归属地

学习了,转走~

3楼回复于2019-10-24 07:23 未知归属地

关联阅读: GoogleTest常用宏源码探究(一)

2楼回复于2019-10-24 03:45 未知归属地

本节主要介绍另一类重要的API宏,即测试中使用的断言宏,是测试的具体实现。

1楼回复于2019-10-24 03:45 未知归属地
写回答
新增插入模板功能
一键使用模板,快速填写内容,轻松发帖~
知道了
  • 为了保障您的信息安全,请勿上传您的敏感个人信息(如您的密码等信息)和您的敏感资产信息(如关键源代码、签名私钥、调试安装包、业务日志等信息),且您需自行承担由此产生的信息泄露等安全风险。
  • 如您发布的内容为转载内容,请注明内容来源。

我要发帖子

了解社区公约,与您携手共创和谐专业的开发者社区。