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中断言都是成对出现的。即分为两个系列:
- 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: nWhich is: 2
Google Test trace:
..\test\gtest_unittest.cc(92): A
..\test\gtest_unittest.cc(87): error: Expected: 1
To be equal to: nWhich 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);
}
Google Test(GTest)使用方法和源码解析——断言的使用方法和解析相关推荐
- Google Test(GTest)使用方法和源码解析——模板类测试技术分析和应用
写C++难免会遇到模板问题,如果要针对一个模板类进行测试,似乎之前博文中介绍的方式只能傻乎乎的一个一个特化类型后再进行测试.其实GTest提供了两种测试模板类的方法,本文我们将介绍方法的使用,并分析其 ...
- Google Test(GTest)使用方法和源码解析——参数自动填充技术分析和应用
在我们设计测试用例时,我们需要考虑很多场景.每个场景都可能要细致地考虑到到各个参数的选择.比如我们希望使用函数IsPrime检测10000以内字的数字,难道我们要写一万行代码么?(转载请指明出于bre ...
- Google Test(GTest)使用方法和源码解析——私有属性代码测试技术分析
有些时候,我们不仅要测试类暴露出来的公有方法,还要测试其受保护的或者私有方法.GTest测试框架提供了一种方法,让我们可以测试类的私有方法.但是这是一种侵入式的,会破坏原来代码的结构,所以我觉得还是谨 ...
- Google Test(GTest)使用方法和源码解析——预处理技术分析和应用
预处理 在<Google Test(GTest)使用方法和源码解析--概况>最后一部分,我们介绍了GTest的预处理特性.现在我们就详细介绍该特性的使用和相关源码.(转载请指明出于brea ...
- Google Test(GTest)使用方法和源码解析——Listener技术分析和应用
在<Google Test(GTest)使用方法和源码解析--结果统计机制分析>文中,我么分析了GTest如何对测试结果进行统计的.本文我们将解析其结果输出所使用到的Listener机制. ...
- Google Test(GTest)使用方法和源码解析——结果统计机制分析
在分析源码之前,我们先看一个例子.以<Google Test(GTest)使用方法和源码解析--概况 >一文中最后一个实例代码为基准,修改最后一个"局部测试"结果为错误 ...
- Google Test(GTest)使用方法和源码解析——自动调度机制分析
在<Google Test(GTest)使用方法和源码解析--概况 >一文中,我们简单介绍了下GTest的使用和特性.从这篇博文开始,我们将深入代码,研究这些特性的实现.(转载请指明出于b ...
- Google Mock(Gmock)简单使用和源码分析——源码分析
源码分析 通过<Google Mock(Gmock)简单使用和源码分析--简单使用>中的例子,我们发现被mock的相关方法在mock类中已经被重新实现了,否则它们也不会按照我们的期待的行为 ...
- Google Test(GTest)使用方法和源码解析——概况
GTest是很多开源工程的测试框架.虽然介绍它的博文非常多,但是我觉得可以深入到源码层来解析它的实现原理以及使用方法.这样我们不仅可以在开源工程中学习到实用知识,还能学习到一些思想和技巧.我觉得有时候 ...
最新文章
- 汇编语言随笔(6)-大小写转换与实验6(双重循环:需要保存cx值)
- 敏捷测试理论以及实践(2)
- volatile学习(可见性,不保证原子性,禁止指令重排(双端检索机制))
- 写给想要做自动化测试的人
- 通过配置XML,使用TpiSyntaxAnalyzer语法分析,快速生成网页
- Illustrator中文版教程,如何在 Illustrator中设置图标项目?
- Julia: PostgreSQL数据库.......
- 深度去除WinRAR广告
- python 关联矩阵_创建关联矩阵
- 震网蠕虫中的一个Bug差点令其“出师未捷身先死”
- 多线程练习(龟兔赛跑)
- 删除控制文件中的磁带备份信息
- Room数据库添加字段遇到的问题记录
- 如何理解return paddle.reader.xmap_readers(train_mapper,reader,cpu_count(),102400)?
- 基于谷歌GKE使用Kubernetes
- [Tree Breadth First Search] 二叉树的最大深度
- 帝国cms自动生成3图
- HTTP2指纹识别(一种相对不为人知的网络指纹识别方法)
- 计算机设备管理系统合同,设备管理系统招标
- OpenWrt quilt patch 方法
热门文章
- HDU - 1269迷宫城堡 -强连通tanjar算法
- OpenCV(十六)边缘检测2 -- Laplace(拉普拉斯)二阶微分算子
- 解决方案:__init__() got an unexpected keyword argument ‘kill_previous‘
- CornerNet:实现demo、可视化heatmap、测试各类别精度
- php yii orm,Yii中的sql查询的位置(或任何支持ORM的框架)?
- Hololens开发示例(不断补充)
- VS+Eigen+CUDA compile error: C2244 and MSB3721
- Learn OpenGL (六):坐标系统
- Udacity机器人软件工程师课程笔记(六)-样本搜索和找回-基于漫游者号模拟器-优化和样本找回
- ceph-kvstore-tool 工具使用详解