在之前博文的基础上,我们将介绍部分断言的使用,同时穿插一些源码。(转载请指明出于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中断言都是成对出现的。即分为两个系列:
- ASSERT_*系列;
- 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<GTEST_IS_NULL_LITERAL_(val1)>::Compare, \
val1, val2)
#define EXPECT_EQ(val1, val2) \
EXPECT_PRED_FORMAT2(::testing::internal:: \
EqHelper<GTEST_IS_NULL_LITERAL_(val1)>::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<GTEST_IS_NULL_LITERAL_(val1)>::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<T, T>()。当类型相同时,它不会执行任何内容。如果不同则会引起编译错误。但是需要注意的是,要使代码触发编译器推导类型,否则也会发生编译错误。如
template <typename T> class Foo {
public:
void Bar() { ::testing::StaticAssertTypeEq<int, T>(); }
};
如下的代码就不会引起编译冲突
void Test1() { Foo<bool> foo; }
但是下面的代码由于引发了编译器的类型推导,所以会触发编译错误
void Test2() { Foo<bool> 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 <typename T1, typename T2>
bool GreaterThan(T1 x1, T2 x2) {
return x1 > x2;
}
TEST(PredicateAssertionTest, AcceptsTemplateFunction) {
int a = 5;
int b = 6;
ASSERT_PRED2((GreaterThan<int, int>), a, b);
}
其输出是
error: (GreaterThan<int, int>)(a, b) evaluates to false, where
a evaluates to 5
b evaluates to 6
其源码也没什么太多奥秘,只是简单的把结果输出
template <typename Pred,
typename T1,
typename T2>
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);
}