Google C++测试框架系列高级篇:第一章 更多关于断言的知识
原始链接:More Assertions
现在你应该已经读完了入门篇并且会使用GTest来写测试。是时候来学一些新把戏了。这篇文档将教会你更多知识:用断言构造复杂的失败信息,传递致命失败,重用和加速你的test fixtures,以及在你的测试中使用不同的标志位。
版本号:v_0.1
更多关于断言的知识
这一章节将覆盖一些较少被使用,但非常重要的关于断言的知识。
1. 明确的声明成功或失败
以下我们提到的几个断言实际上并不对值或表达式进行测试。取而代之的是它们直接产生一个通过或失败。像大多数做做测试的宏一样,我们可以定制失败信息并且流定向到它们。
SUCCESS()直接产生一个通过。但这并不表明整个测试通过。仅当所有断言都通过这个测试才被认为是通过。
注:这个宏目前只存在于文档中,是一个空操作,不会产生任何用户可见的输出。但是以后我们会考虑添加有关信息。
| FAIL(); | ADD_FAILURE(); | ADD_FAILURE_AT("file_path", line_number); |
FAIL()产生一个致命失败,而ADD_FAILURE和ADD_FAILURE_AT生成非致命失败。在类似于switch-case的控制流程中决定测试是否通过,用这些宏比用bool表达式更直接方便。例如以下代码:
switch(expression) {
case : ... some checks ...
case : ... some other checks
...
default: FAIL() << "We shouldn't get here.";
}
在Linux,Windows和Mac上可用。
2. 异常断言
以下断言用来判断代码是否抛出异常。
| 致命断言 | 非致命断言 | 通过条件 |
| ASSERT_THROW(statement, exception_type); | EXPECT_THROW(statement, exception_type); | 抛出指定类型的异常 |
| ASSERT_ANY_THROW(statement); | EXPECT__ANY_THROW(statement); | 抛出任意类型的异常 |
| ASSERT_NO_THROW(statement); | EXPECT_NO_THROW(statement); | 没有抛出异常 |
例子:
ASSERT_THROW(Foo(), bar_exception);
EXPECT_NO_THROW({
int n = ;
Bar(&n);
});
在Linux,Windows和Mac上可用。
3. 通过谓词断言提供更好的错误信息
尽管GTest提供了丰富的断言,但还是不能覆盖我们在现实中所能遇到的场景,而且也不可能预见所有的情况(这不是一个好主意)。有时用户想使用EXPECT_TRUE来测试一个复杂的表达式,但是却没有合适的宏。这使得你无法显示表达式某一部分的值,当出错之后你很难找到问题到底出在哪里。某些用户选择自己构造失败信息,然后流定向到EXPECT_TRUE()。但这么做问题非常多,特别是当这个表达式有副作用或者开销巨大。
GTest提供了三个选项来解决这个问题。
3.1 使用已有的bool类型函数
如果你的函数或函数体返回bool类型(或者可以被隐式的转换为bool类型),在谓词断言中使用这些函数的话,参数将自动被打印。
| 致命断言 | 非致命断言 | 通过条件 |
| ASSERT_PRED1(pred1, val1); | EXPECT_PRED1(pred1, val1); | pred1(val1)返回true |
| ASSERT_PRED2(pred1, val1, val2); | EXPECT_PRED2(pred1, val1, val2); | pred2(val1, val2)返回true |
| ... | ... | ... |
在上表中,predn是一个有n个参数的谓词函数或函数体,val1, val2, ...直到valn代表它的参数。断言当谓词判断在给定参数返回true的情况下通过,不然失败。当断言失败后,它就会打印每一个参数。不管通过还是失败,这些参数的值只会被计算一次。
请看以下例子:
// Returns true if m and n have no common divisors except 1.
bool MutuallyPrime(int m, int n) { ... }
const int a = ;
const int b = ;
const int c = ;
断言EXPECT_PRED2(MutuallyPrime, a, b)会通过,而断言EXPECT_PRED2(MutuallyPrime, b, c)会失败并打印以下信息:
!MutuallyPrime(b, c) is false, where
b is 4
c is 10
注:
- 当使用ASSERT_PRED*或EXPECT_PRED*遇到编译错误"no matching function to call"时,请参考这篇文章如何解决。
- 当前我们支持的参数数量小于等于5。如果你希望支持更多的参数,请让我们知道。
在Linux,Windows和Mac上可用。
3.2 使用返回AssertionResult对象的函数
虽然EXPECT_PRED*和它的小伙伴们用起来很方便,但是语法却不能让人满意。对于不同数量的参数你必须使用不同的宏,这看上去更向Lisp而不是C++。现在::testing::AssertionResult来帮助你解决这个问题。
一个AssertionResult对象代表一次断言的结果(通过或失败,以及相关的信息)。你可以用以下任意一个工厂函数来创建一个AssertionResult对象。
namespace testing {
// Returns an AssertionResult object to indicate that an assertion has
// succeeded.
AssertionResult AssertionSuccess();
// Returns an AssertionResult object to indicate that an assertion has
// failed.
AssertionResult AssertionFailure();
}
你可以使用"<<"操作符把信息流定向到AssertionResult对象。
为了在bool断言(例如EXPECT_TRUE())中提供可读性更好的信息,你可以实现一个返回AssertionResult的函数而不是仅仅返回bool类型。例如,你可以这样定义IsEven()函数:
::testing::AssertionResult IsEven(int n) {
if ((n % ) == )
return ::testing::AssertionSuccess();
else
return ::testing::AssertionFailure() << n << " is odd";
}
而不是原来返回bool类型的:
bool IsEven(int n) {
return (n % ) == ;
}
断言EXPECT_TRUE(IsEven(Fib(4)))会失败并且打印以下信息:
Value of: !IsEven(Fib(4))
Actual: false (*3 is odd*)
Expected: true
如果用原来返回bool类型的函数,打印信息就比较简单:
Value of: !IsEven(Fib(4))
Actual: false
Expected: true
如果你希望EXPECT_FALSE和ASSERT_FALSE也能提供这样有意义的信息,并且不在乎谓词执行效率的话,再多打加一条信息好了:
::testing::AssertionResult IsEven(int n) {
if ((n % ) == )
return ::testing::AssertionSuccess() << n << " is even";
else
return ::testing::AssertionFailure() << n << " is odd";
}
现在调用EXPECT_FALSE(IsEven(Fib(6)))将打印以下信息:
Value of: !IsEven(Fib(Fib(6)))
Actual: true (8 is even)
Expected: false
在Linux,Windows和Mac上可用。
3.3 使用格式化谓词
如果你发现(ASSERT|EXPECT)_PRED*和(ASSERT|EXPECT)_(TRUE|FALSE)提供的默认信息不能满足要求,或者谓词的某些参数不能被流定向到ostream,请使用格式化谓词断言来完全定制信息:
| 致命断言 | 非致命断言 | 通过条件 |
| ASSERT_PRED_FORMAT1(pred_format1, val1); | EXPECT_PRED1(pred_format1, val1); | pred_format1(val1)通过 |
| ASSERT_PRED_FPRMAT22(pred_format2, val1, val2); | EXPECT_PRED2(pred_format2, val1, val2); | pred_format2(val1, val2)通过 |
| ... | ... | ... |
与前面提到的两组谓词不同,(ASSERT|EXPECT)_PRED_FORMAT*提供了一个谓词格式化程序(pred_formatn),它是以下形式的函数或函数体:
::testing::AssertionResult PredicateFormattern(const char* expr1, const char* expr2, ... const char* exprn, T1 val1, T2 val2, ... Tn valn);
其中val1,val2,...直到valn表示谓词的参数,而expr1,expr2,...直到exprn是参数valn显示在代码中的形式(如果第n个参数我们使用变量valn,那么exprn对应的值就是"valn")。类型T1,T2,...直到Tn可以是任意值类型或引用类型。例如,某个参数的类型是Foo,你可以根据需要声明为Foo或者const Foo&。
格式化谓词返回一个::testing::AssertionResult对象表示断言通过或失败。
下面我们将展示如何基于前面使用EXPECT_PRED2()的例子改进失败信息。
// Returns the smallest prime common divisor of m and n,
// or 1 when m and n are mutually prime.
int SmallestPrimeCommonDivisor(int m, int n) { ... } // A predicate-formatter for asserting that two integers are mutually prime.
::testing::AssertionResult AssertMutuallyPrime(const char* m_expr,
const char* n_expr,
int m,
int n) {
if (MutuallyPrime(m, n))
return ::testing::AssertionSuccess(); return ::testing::AssertionFailure()
<< m_expr << " and " << n_expr << " (" << m << " and " << n
<< ") are not mutually prime, " << "as they have a common divisor "
<< SmallestPrimeCommonDivisor(m, n);
}
使用格式化谓词:
EXPECT_PRED_FORMAT2(AssertMutuallyPrime, b, c);
我们可以获得以下信息
b and c (4 and 10) are not mutually prime, as they have a common divisor 2.
现在可能你已经意识到了,之前我们介绍的大多数断言是(EXPECT|ASSERT)_PRED_FORMAT*的特例。事实上也确实如此,大多数断言的实现依赖于(EXPECT|ASSERT)_PRED_FORMAT*。
在Linux,Windows和Mac上可用。
4. 浮点数比较
浮点数比较的坑比较多。因为四舍五入的关系,很难精确的比较两个浮点数。所以我们常用的ASSERT_EQ通常就不能工作了。因为浮点数的范围之大,没有一个固定的误差范围可以适应所有情况。除非要比较的值因为四舍五入非常接近0,用一个固定误差范围来做比较还是一个不错的选择。
通常你必须手工指定一个误差范围来做浮点数比较。如果你嫌麻烦也不要紧,使用默认的ULPs(Units in the Last Place)也能得到足够好的效果,GTest提供基于它实现的断言。关于ULPS的详细信息请查询这篇文章。
4.1 宏
| 致命断言 | 非致命断言 | 通过条件 |
| ASSERT_FLOAT_EQ(expected, actual); | EXPECT_FLOAT_EQexpected, actual); | 两个浮点数基本相等 |
| ASSERT_DOUBLE_EQ(expected, actual); | EXPECT_DOUBLE_EQexpected, actual); | 两个双精度浮点数基本相等 |
“基本相等”表示两个数的差距在4个ULPs以内。
以下断言允许你手工指定可接受的误差范围:
| 致命断言 | 非致命断言 | 通过条件 |
| ASSERT_NEAR(val1, val2, abs_error); | EXPECT_NEAR(val1, val2, abs_error); | 两个数差的绝对值不超过给定的绝对误差 |
在Linux,Windows和Mac上可用。
4.2 浮点数格式化谓词函数
有些浮点数操作虽然有用,但是使用频率很低。为了避免宏的数量爆炸,我们以格式化谓词函数的形式提供了一组功能,你可以方便的用在谓词断言里(例如EXPECT_PRED_FORMAT2)。
EXPECT_PRED_FORMAT2(::testing::FloatLE, val1, val2);
EXPECT_PRED_FORMAT2(::testing::DoubleLE, val1, val2);
在上面我们判断val1小于,或几乎等于val2。你也可以把上面的EXPECT_PRED_FORMAT2替换成ASSERT_PRED_FORMAT2。
在Linux,Windows和Mac上可用。
5. Windows平台处理HRESULT的断言
以下断言用于测试HRESULT类型值的通过或失败:
| 致命断言 | 非致命断言 | 通过条件 |
| ASSERT_HRESULT_SUCCEEDED(expression); | EXPECT_HRESULT_SUCCEEDED(expression); | expression代表成功的HRESULT |
| ASSERT_HRESULT_FAILED(expression); | EXPECT_HRESULT_FAILED(expression); | expression代表失败的HRESULT |
断言的输出中包含与expression返回的HRESULT代码相关的可读的错误信息。
你可以这样使用:
CComPtr shell;
ASSERT_HRESULT_SUCCEEDED(shell.CoCreateInstance(L"Shell.Application"));
CComVariant empty;
ASSERT_HRESULT_SUCCEEDED(shell->ShellExecute(CComBSTR(url), empty, empty, empty, empty));
在Windows上可用。
6. 类型断言
你可以调用以下函数
::testing::StaticAssertTypeEq<T1, T2>();
来判断类型T1和T2是否相同。如果断言通过这个函数什么都不会做。如果类型不一样,调用这个函数会导致编译错误,并且编译错误信息(不同编译器会有所变化)会告诉你T1和T2的实际类型。这个宏主要针对模板代码。
警告:如果在类模板的成员函数或者函数模板内使用,仅当函数被实例化后StaticAssertTypeEq<T1, T2>()才会生效。例如以下代码:
template <typename T> class Foo {
public:
void Bar() { ::testing::StaticAssertTypeEq<int, T>(); }
};
代码
void Test1() { Foo<bool> foo; }
不会产生编译错误,因为Foo::Bar()实际上没有被实例话。但是如果修改代码
void Test2() { Foo<bool> foo; foo.Bar(); }
就会产生一个编译错误。
在Linux,Windows和Mac上可用。
7. 断言的放置
你可以在任何C++函数内使用断言。它没有必要成为test fixture类的一个方法。唯一的限制条件是生成致命错误的断言(比如FAIL*和ASSERT_*)只能用于无返回值(void)的函数。这是因为GTest没有使用异常来实现断言。如果你在非void函数使用致命失败断言的话会收到一个很困惑的编译错误,比如"error: void value not ignored as it ought to be"。
如果你非要在非void函数内使用会导致致命失败的断言,一个解决方案就是用输出参数来取代函数的返回值。比如你可以把T2 Foo(T1 x)改造成void Foo(T1 x, T2 *result)。你必须保证即使函数意外返回,*result里的数据必须是有意义的(其实是什么不重要,不把程序搞崩溃才是真的)。因为现在函数返回void,所以任何断言都可以用了。
如果不能改变函数的形式,那么你就只能使用产生非致命失败的断言,比如ADD_FAULURE*和EXPECT_*。
注:构造函数和析构函数根据C++语言规范不被认为是void类型的函数,所以在它们内部你不能使用会产生致命失败的断言。如果尝试的话必然会得到编译错误。一个简单的解决方法是把构造函数和析构函数的内容提取出来放到单独的void类型函数中去。但是要牢记,构造函数中发生的致命失败并不会停止整个测试,它只是导致构造函数提前返回,而且还会使某些对象处于部分初始化状态。类似的,析构函数中的致命失败会导致某些对象处于部分析构状态。总之在这种情况下小心为上。
Google C++测试框架系列高级篇:第一章 更多关于断言的知识的更多相关文章
- Google C++测试框架系列高级篇:第二章 让GTest学习打印自定义对象
上一篇:更多关于断言的知识 原始链接:Teaching Google Test How to Print Your Values 词汇表 版本号:v_0.1 让GTest学习打印自定义对象 当一个断言 ...
- Google C++测试框架系列入门篇:第二章 开始一个新项目
上一篇:Google C++测试框架系列入门篇:第一章 介绍:为什么使用GTest? 原始链接:Setting up a New Test Project 词汇表 版本号:v_0.1 开始一个新项目 ...
- Google C++测试框架系列入门篇:第三章 基本概念
上一篇:Google C++测试框架系列入门篇:第二章 开始一个新项目 原始链接:Basic Concepts 词汇表 版本号:v_0.1 基本概念 使用GTest你肯定会接触到断言这个概念.断言是用 ...
- Google C++测试框架系列入门篇:第一章 介绍:为什么使用GTest?
原始链接:Introduction: Why Google C++ Testing Framework? 词汇表 版本号:v_0.1 介绍:为什么使用GTest? GTest帮助你写更好的C++测试代 ...
- Google C++测试框架系列:入门
Google C++测试框架系列:入门 原始链接:V1_6_Primer 注 GTest或者Google Test: Google的C++测试框架. Test Fixtures: 这个词实在找不到对应 ...
- 《进击吧!Blazor!》系列入门教程 第一章 8.部署
<进击吧!Blazor!>是本人与张善友老师合作的Blazor零基础入门教程视频,此教程能让一个从未接触过Blazor的程序员掌握开发Blazor应用的能力. 视频地址:https://s ...
- 16第一章 ASP.Net编程基础知识
第一章 ASP.Net编程基础知识 第一章 ASP.Net编程基础知识 本章首先介绍用ASP.Net技术编制服务器端动态网页所需的网络和HTML标记语言方面的有关知识.然后 ...
- C#高级知识点&(ABP框架理论学习高级篇)——白金版
前言摘要 很早以前就有要写ABP高级系列教程的计划了,但是迟迟到现在这个高级理论系列才和大家见面.其实这篇博客很早就着手写了,只是楼主一直写写停停.看看下图,就知道这篇博客的生产日期了,谁知它的出厂日 ...
- ASP.NET自定义控件组件开发 第一章 第三篇 第一章的完结篇
ASP.NET自定义控件组件开发 第一章 第三篇 第三篇:第一章的完结篇 系列文章链接: ASP.NET自定义控件组件开发 第一章 待续 ASP.NET自定义控件组件开发 第一章 第二篇 接着待续 ...
随机推荐
- memcache redis mogodb 分别适用在什么样的场景?
memcache 与 redis 都是key-value存储系统,相对来说redis可能比memcache适应场景多些,存储的value类型也更多些,而redis也支持主从同步.而mongo是一种文档 ...
- 解决 ubuntu 14.04.1 下一个sublime text3 3065 中国输入的问题
你看今天 sublime text3 我以前有没有3059 的 它有支持3065该. 因此,为了支持subl 对中国输入法的实现 ,下面的操作步骤把我的记录供大家使用 有一个完整的教程: htt ...
- 初探js
第一章 1.JS的位置 1-1.行间 1-2.内嵌 1-3.外联 2.JS的标签位置 页面中的代码在一般情况下会按从上到下的顺序,从左往右的顺序执行. 因此当JS放在了元素上面的时候,就不能正常执 ...
- LaTeX —— 特殊符号与数学字体
1. 特殊符号 ℓ(\ell):用于和大小的 I 和 数字 1 相区分 R(\Re) ∇(\nabla):微分算子 2. 数学字体 mathbb:blackboard bold,黑板粗体 mathca ...
- Unity3d报告奇怪的错误CompareBaseObjectsInternal can only be called from the main thread.
其中使用了该项目.NET的Async Socket代码.后来不知道什么时候这个奇怪的错误的出现: CompareBaseObjectsInternal can only be called from ...
- c语言学习笔记(8)——函数
学完c语言的函数可以理解面向过程的语言 函数是c语言的重点 一.为什么需要函数? 1.避免了重复性操作 2.有利于程序的模块化(每一个功能可以用不同函数去实现) 二.什么叫做函数? 逻辑上:能够完成特 ...
- 利用tcpdump分析工具来验证tcp连接的建立和关闭过程
本文要求读者在阅读之前应该对TCP通过三次握手建立和关闭连接有一定的了解,本文并没有详细讲解三次握手,只是通过一个实例对三次握手进行了一下验证. tcp连接的建立和关闭想必大家都已经非常熟悉了!通过三 ...
- Spring Assert.notNull--IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException: Source must not be null at ...
- HBase 数据备份
HBase提供了备份API,直接使用shell脚本可以叫它.如下面的命令的详细信息: hbase org.apache.hadoop.hbase.mapreduce.Export 'user' /hb ...
- sdutoj1225--编辑距离(dp:字符串转换)
编辑距离 nid=24#time" style="padding-bottom:0px; margin:0px; padding-left:0px; padding-right:0 ...