Google Test(GTest)使用方法和源码解析——断言的使用方法和解析

        在之前博文的基础上,我们将介绍部分断言的使用,同时穿插一些源码。(转载请指明出于breaksoftware的csdn博客)

断言(Assertions)

        断言是GTest局部测试中最简单的使用方法,我们之前博文中举得例子都是使用断言去做判断的。

基础断言

        我们先看一个基础的断言

Fatal assertion Nonfatal assertion Verifies
ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition is true
ASSERT_FALSE(condition); EXPECT_FALSE(condition); condition is false

        GTest中断言都是成对出现的。即分为两个系列:

  1. ASSERT_*系列;
  2. EXPECT_*系列;

        EXPECT_*系列是比较常用的。在一个测试特例中,如果局部测试使用了EXPECT_*系列函数,它将保证本次局部测试结果不会影响之后的流程。但是ASSERT_*系列在出错的情况下,当前测试特例中剩下的流程就不走了。

TEST(BaseCheck, Assert) {
  ASSERT_TRUE(1==1);
  ASSERT_TRUE(2==3);
  ASSERT_TRUE(3==3);
}
TEST(BaseCheck, Expect) {
  EXPECT_TRUE(1==1);
  EXPECT_TRUE(2==3);
  EXPECT_TRUE(3==3);
}

        上面两个测试特例中,第二个局部测试都是不成立的。由于EXPECT_*不会影响执行流程,所以即使第8行出错,之后的流程(第9行)也执行了。但是ASSERT_*会影响,所以第3行出错后,第4行没有执行。那么GTest是如何做到的呢?我们对比下EXPECT_TRUE和ASSERT_TRUE的实现

#define EXPECT_TRUE(condition) \
  GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \
                      GTEST_NONFATAL_FAILURE_)
#define ASSERT_TRUE(condition) \
  GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \
                      GTEST_FATAL_FAILURE_)

        可以见得,他们的区别就是在是出错时调用了GTEST_NONFATAL_FAILURE_还是GTEST_FATAL_FAILURE_

#define GTEST_FATAL_FAILURE_(message) \
  return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure)

#define GTEST_NONFATAL_FAILURE_(message) \
  GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure)

        这儿调用到《Google Test(GTest)使用方法和源码解析——结果统计机制分析》中介绍保存局部测试结果的宏——GTEST_MESSAGE_。但是这个不是重点,重点是GTEST_FATAL_FAILURE_宏调用了return——函数返回了。我们再看下GTEST_TEST_BOOLEAN_的实现

#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_*的else里return了。而EXPECT_*的else没有return。

二进制比较断言

        GTest还提供了二进制对比宏

Fatal assertion Nonfatal assertion Verifies 全称
ASSERT_EQ(val1,val2); EXPECT_EQ(val1,val2); val1 == val2 equal
ASSERT_NE(val1,val2); EXPECT_NE(val1,val2); val1 != val2 not equal
ASSERT_LT(val1,val2); EXPECT_LT(val1,val2); val1 < val2 less than
ASSERT_LE(val1,val2); EXPECT_LE(val1,val2); val1 <= val2 less equal
ASSERT_GT(val1,val2); EXPECT_GT(val1,val2); val1 > val2 big than
ASSERT_GE(val1,val2); EXPECT_GE(val1,val2); val1 >= val2 big equal

        虽然宏很多,但是还是很好记,大家只要记住全称那列,就知道怎么对应关系了。我们再查看下二进制对比系列宏的ASSERT_*和EXPECT_*的区别(以EQ为例)

#define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2)
#define GTEST_ASSERT_EQ(val1, val2) \
  ASSERT_PRED_FORMAT2(::testing::internal:: \
                      EqHelper::Compare, \
                      val1, val2)
#define EXPECT_EQ(val1, val2) \
  EXPECT_PRED_FORMAT2(::testing::internal:: \
                      EqHelper::Compare, \
                      val1, val2)
// Binary predicate assertion macros.
#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \
  GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)
#define EXPECT_PRED2(pred, v1, v2) \
  GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_)
#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \
  GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_)
#define ASSERT_PRED2(pred, v1, v2) \
  GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_)

        可以见得它们和基本断言一样——EXPECT在失败的情况下没有return(失败时调用了GTEST_NONFATAL_FAILURE_),而ASSERT在失败的情况下return掉了(失败时调用了GTEST_FATAL_FAILURE_)。

        一般来说二进制比较,都是对比其结构体所在内存的内容。C++大部分原生类型都是可以使用二进制对比的。但是对于自定义类型,我们就要定义一些操作符的行为,比如=、<等,我这儿就不举例了。

字符串对比断言

        对于string类型,可以使用如下宏

Fatal assertion Nonfatal assertion Verifies 全称
ASSERT_STREQ(str1,str2); EXPECT_STREQ(str1,_str_2); the two C strings have the same content string equal
ASSERT_STRNE(str1,str2); EXPECT_STRNE(str1,str2); the two C strings have different content string not equal
ASSERT_STRCASEEQ(str1,str2); EXPECT_STRCASEEQ(str1,str2); the two C strings have the same content, ignoring case string (ignoring) case equal
ASSERT_STRCASENE(str1,str2); EXPECT_STRCASENE(str1,str2); the two C strings have different content, ignoring case string (ignoring) case not euqal

        在源码上,string对比宏和二进制对比只是在对比函数的选择上有差异,以Equal为例

#define EXPECT_EQ(val1, val2) \
  EXPECT_PRED_FORMAT2(::testing::internal:: \
                      EqHelper::Compare, \
                      val1, val2)
#define EXPECT_STREQ(s1, s2) \
  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2)

浮点对比断言

        在对比数据方面,我们往往会讨论到浮点数的对比。因为在一些情况下,浮点数的计算精度将影响对比结果,所以这块都会单独拿出来说。GTest对于浮点数的对比也是单独的

Fatal assertion Nonfatal assertion Verifies
ASSERT_FLOAT_EQ(val1, val2); EXPECT_FLOAT_EQ(val1, val2); the two float values are almost equal
ASSERT_DOUBLE_EQ(val1, val2); EXPECT_DOUBLE_EQ(val1, val2); the two double values are almost equal

        almost euqal表示两个数只是近似相似,默认的是是指两者的差值在4ULP之内(Units in the Last Place)。我们还可以自己制定精度

Fatal assertion Nonfatal assertion Verifies
ASSERT_NEAR(val1, val2, abs_error); EXPECT_NEAR(val1, val2, abs_error); the difference between val1 and val2 doesn't exceed the given absolute error

        使用方法是

  ASSERT_NEAR(-1.0f, -1.1f, 0.2f);
  ASSERT_NEAR(2.0f, 3.0f, 1.0f);

        对于浮点数的比较,感兴趣的同学可以查看下GTest的源码,还是有点意思的。

成功失败断言

        该类断言用于直接标记是否成功或者失败。可以使用SUCCEED()宏标记成功,使用FAIL()宏标记致命错误(同ASSERT_*),ADD_FAILURE()宏标记非致命错误(同EXPECT_*)。举个例子

if (Check) {
  SUCCEED();
}
else {
  FAIL();
}

        我们直接在自己的判断下设置断言。这儿有个地方需要说一下,SUCCEED()宏会调用GTEST_MESSAGE_AT_宏,从而会影响TestResult的test_part_results结构体,这也是唯一的成功情况下影响该结构体的地方。详细的分析可以见《Google Test(GTest)使用方法和源码解析——结果统计机制分析》。

类型对比断言

        该类断言只有一个::testing::StaticAssertTypeEq()。当类型相同时,它不会执行任何内容。如果不同则会引起编译错误。但是需要注意的是,要使代码触发编译器推导类型,否则也会发生编译错误。如

template  class Foo {
 public:
  void Bar() { ::testing::StaticAssertTypeEq(); }
};

        如下的代码就不会引起编译冲突

void Test1() { Foo foo; }

        但是下面的代码由于引发了编译器的类型推导,所以会触发编译错误

void Test2() { Foo foo; foo.Bar(); }

异常断言

        异常断言是在断言中接收一定类型的异常,并转换成断言形式。它有如下几种

Fatal assertion Nonfatal assertion Verifies
ASSERT_THROW(statement, exception_type); EXPECT_THROW(statement, exception_type); statement throws an exception of the given type
ASSERT_ANY_THROW(statement); EXPECT_ANY_THROW(statement); statement throws an exception of any type
ASSERT_NO_THROW(statement); EXPECT_NO_THROW(statement); statement doesn't throw any exception

        我们举一个例子

void ThrowException(int n) {
    switch (n) {
    case 0:
        throw 0;
    case 1:
        throw "const char*";
    case 2:
        throw 1.1f;
    case 3:
        return;
    }
}

TEST(ThrowException, Check) {
    EXPECT_THROW(ThrowException(0), int);
    EXPECT_THROW(ThrowException(1), const char*);
    ASSERT_ANY_THROW(ThrowException(2)); 
    ASSERT_NO_THROW(ThrowException(3));  
}

        这组测试特例中,我们预期ThrowException在传入0时,会返回int型异常;传入1时,会返回const char*异常。传入2时,会返回异常,但是异常类型我们并不关心。传入3时,不返回任何异常。当然ThrowExeception的实现也是按以上预期设计的。

        我们看下源码,我们只看ASSERT_型的,EXPECT_型和ASSERT_型的区别在前文很多次讲到,所以不再罗列代码了。

#define ASSERT_THROW(statement, expected_exception) \
  GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_)
#define ASSERT_NO_THROW(statement) \
  GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_)
#define ASSERT_ANY_THROW(statement) \
  GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_)

        我们先看最简单的GTEST_TEST_NO_THROW_的实现

#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.\n" \
           "  Actual: it throws.")

        只要表达式抛出异常,就会goto到else中进行错误处理。

        再看下GTEST_TEST_ANY_THROW_的实现

#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.\n" \
           "  Actual: it doesn't.")

        只要抛出异常,就认为是正确的。否则goto到else代码中进行错误处理。

        再看下稍微复杂点的GTEST_TEST_THROW_

#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 ".\n  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 ".\n  Actual: it throws nothing."; \
      goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \
    } \
  } else \
    GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \
      fail(gtest_msg.value)

        只有接收到了传入的特定类型的异常,否则都会goto到else代码中进行错误处理。

参数名输出断言

        在之前的介绍的断言中,如果在出错的情况下,我们会对局部测试相关信息进行输出,但是并不涉及其可能传入的参数。参数名输出断言,可以把参数名和对应的值给输出出来。

Fatal assertion Nonfatal assertion Verifies
ASSERT_PRED1(pred1, val1); EXPECT_PRED1(pred1, val1); pred1(val1) returns true
ASSERT_PRED2(pred2, val1, val2); EXPECT_PRED2(pred2, val1, val2); pred2(val1, val2) returns true
... ... ...

        目前版本的GTest支持5个参数的版本ASSERT/EXPECT_PRED5宏。其使用方法是

template 
bool GreaterThan(T1 x1, T2 x2) {
  return x1 > x2;
}
TEST(PredicateAssertionTest, AcceptsTemplateFunction) {
  int a = 5;
  int b = 6;
  ASSERT_PRED2((GreaterThan), a, b);
}

        其输出是

error: (GreaterThan)(a, b) evaluates to false, where
a evaluates to 5
b evaluates to 6

        其源码也没什么太多奥秘,只是简单的把结果输出

template 
AssertionResult AssertPred2Helper(const char* pred_text,
                                  const char* e1,
                                  const char* e2,
                                  Pred pred,
                                  const T1& v1,
                                  const T2& v2) {
  if (pred(v1, v2)) return AssertionSuccess();

  return AssertionFailure() << pred_text << "("
                            << e1 << ", "
                            << e2 << ") evaluates to false, where"
                            << "\n" << e1 << " evaluates to " << v1
                            << "\n" << e2 << " evaluates to " << v2;
}

        这儿需要注意的是,判断的表达式可以使用if语句判断返回结果,所以最好是bool类型。

子过程中使用断言

        经过之前的分析,我们可以想到,如果子过程中使用了断言,则结果输出只会指向子过程,而不会指向父过程中的某个调用。为了便于阅读我们可以使用SCOPED_TRACE宏去标记下位置

void Sub(int n) {
    ASSERT_EQ(1, n);
}

TEST(SubTest, Test1) {
    {
        SCOPED_TRACE("A");
        Sub(2);
    }
    Sub(3);
}

        其结果输出时标记了下A这行位置,可见如果没有这个标记,是很难区分出是哪个Sub失败的。

..\test\gtest_unittest.cc(87): error:       Expected: 1
To be equal to: n
      Which is: 2
Google Test trace:
..\test\gtest_unittest.cc(92): A
..\test\gtest_unittest.cc(87): error:       Expected: 1
To be equal to: n
      Which is: 3

        我们再注意下Sub的实现,其使用了ASSERT_EQ断言,该断言并不会影响Test1测试特例的运行,其原因我们在之前做过分析了。为了消除这种可能存在的误解,GTest推荐使用在子过程中使用ASSERT/EXPECT_NO_FATAL_FAILURE(statement);

        如果父过程一定要在子过程发生错误时退出怎么办?我们可以使用::testing::Test::HasFatalFailure()去判断当前线程中是否产生过错误。

TEST(SubTest, Test1) {
    {
        SCOPED_TRACE("A");
        Sub(2);
    }
    if (::testing::Test::HasFatalFailure())
        return;
    Sub(3);
}

你可能感兴趣的:(GTest使用方法和源码解析,GTest源码解析)