单元测试是什么

单元测试是开发人员编写的、用于检测在特定条件下目标代码正确性的代码。单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

单元测试位于测试金字塔的最底层,越向上反馈的时间越长,实现的成本也越高。

测试的好处不仅仅是在编码时可以快速验证我们的程序是否满足预期,更大的好处是未来修改另一个功能时,可以帮助我们快速回归之前的所有测试,以确定此修改的影响范围。

单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。

单元测试是由程序员自己来完成,最终受益的也是程序员自己。程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。

其实我们每天都在做单元测试。你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要想办法输出些数据,如弹出信息窗口什么的,这,也是单元测试,把这种单元测试称为临时单元测试。只进行了临时单元测试的软件,针对代码的测试很不完整,代码覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当BUG暴露出来的时候难于调试,大幅度提高后期测试和维护成本。进行充分的单元测试,是提高编码质量,降低开发成本的必由之路。

要进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。比较简单的办法是为产品工程建立对应的测试工程,为每个类建立对应的测试类,为每个函数(很简单的除外)建立测试函数。

有一种看法是,只测试类的接口(公有函数),不测试其他函数,从面向对象角度来看,确实有其道理,但是,测试的目的是找错并最终排错,因此,只要是包含错误的可能性较大的函数都要测试,跟函数是否私有没有关系。对于C++来说,可以用一种简单的方法区隔需测试的函数:简单的函数如数据读写函数的实现在头文件中编写(inline函数),所有在源文件编写实现的函数都要进行测试(构造函数和析构函数除外)。

使用效果

单元测试会为我们的承诺做保证。编写单元测试就是用来验证这段代码的行为是否与我们期望的一致。有了单元测试,我们可以自信的交付自己的代码。

什么时候测试?单元测试越早越好,早到什么程度?极限编程(Extreme Programming,或简称XP)讲究TDD,即测试驱动开发,先编写测试代码,再进行开发。在实际的工作中,可以不必过分强调先什么后什么,重要的是高效和感觉舒适。从经验来看,先编写产品函数的框架,然后编写测试函数,针对产品函数的功能编写测试用例,然后编写产品函数的代码,每写一个功能点都运行测试,随时补充测试用例。所谓先编写产品函数的框架,是指先编写函数空的实现,有返回值的直接返回一个合适值,编译通过后再编写测试代码,这时,函数名、参数表、返回类型都应该确定下来了,所编写的测试代码以后需修改的可能性比较小。

由谁测试?单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成,也就是说,经过了单元测试的代码才是已完成的代码,提交产品代码时也要同时提交测试代码。

关于桩代码,单元测试应避免编写桩代码。桩代码就是用来代替某些代码的代码,例如,产品函数或测试函数调用了一个未编写的函数,可以编写桩函数来代替该被调用的函数,桩代码也用于实现测试隔离。采用由底向上的方式进行开发,底层的代码先开发并先测试,可以避免编写桩代码,这样做的好处有:减少了工作量;测试上层函数时,也是对下层函数的间接测试;当下层函数修改时,通过回归测试可以确认修改是否导致上层函数产生错误。

误解

1、它浪费了太多的时间

一旦编码完成,开发人员总是会迫切希望进行软件的集成工作,这样他们就能够看到实际的系统开始启动工作了。单元测试这样的活动也许会被看作是通往这个阶段点的道路上的障碍, 推迟了对整个系统进行联调这种真正有意思的工作启动的时间。

系统能够正常工作的可能性是很小的,更多的情况是充满了各式各样的Bug。而且当这个系统投入使用时也无法确保它能够可靠运行。

在实践工作中,进行了完整计划的单元测试和编写实际的代码所花费的精力大致上是相同的。一旦完成了这些单元测试工作,很多Bug将被纠正,在确信他们手头拥有稳定可靠的部件的情况下,开发人员能够进行更高效的系统集成工作。这才是真实意义上的进步,所以说完整计划下的单元测试是对时间的更高效的利用。

2、它仅仅是证明这些代码做了什么

这是那些没有首先为每个单元编写一个详细的规格说明而直接跳到编码阶段的开发人员提出的一条普遍的抱怨, 当编码完成以后并且面临代码测试任务的时候,他们就阅读这些代码并找出它实际上做了什么,把他们的测试工作基于已经写好的代码的基础上。

如果他们首先写好一个详细的规格说明,测试能够以规格说明为基础。代码就能够针对它的规格说明,而不是针对自身进行测试。这样的测试能找到更多的编码错误,甚至是一些规格说明中的错误。好的规格说明可以使测试的质量更高,所以最后的结论是高质量的测试需要高质量的规格说明。

这个过程的主要输入条件是要阅读那些程序代码和注释, 主要针对这个单元, 及调用它和被它调用的相关代码。画出流程图是非常有帮助的,你可以用手工或使用某种工具。可以组织对这个概要规格说明的走读(Review),以确保对这个单元的说明没有基本的错误, 有了这种最小程度的代码深层说明,就可以用它来设计单元测试了。

3、我是个很棒的程序员, 我是不是可以不进行单元测试?

每个人都会犯错误。真正的软件系统是非常复杂的。真正的软件系统不可以寄希望于没有进行广泛的测试和Bug修改过程就可以正常工作。

编码不是一个可以一次性通过的过程。软件产品必须进行维护以对操作需求的改变作出反应, 并且要对最初的开发工作遗留下来的Bug进行修改。依靠那些原始作者进行修改吗?在开发人员做出修改后进行可重复的单元测试可以避免产生那些令人不快的负作用。

4、集成测试将会抓住所有的Bug。

规模越大的代码集成意味着复杂性就越高。如果软件的单元没有事先进行测试,开发人员很可能会花费大量的时间仅仅是为了使软件能够运行,而任何实际的测试方案都无法执行。

一旦软件可以运行了,开发人员又要面对这样的问题:在考虑软件全局复杂性的前提下对每个单元进行全面的测试。这是一件非常困难的事情。

最后的结果是测试将无法达到它所应该有的全面性。一些缺陷将被遗漏,并且很多Bug将被忽略过去。

让我们类比一下,假设我们要清洗一台已经完全装配好的食物加工机器!无论你喷了多少水和清洁剂,一些食物的小碎片还是会粘在机器的死角位置,只有任其腐烂并等待以后再想办法。但如果这台机器是拆开的, 这些死角也许就不存在或者更容易接触到了,并且每一部分都可以毫不费力的进行清洗。

成本效率

无论什么时候作出修改都需要进行完整的回归测试,在生命周期中尽早地对软件产品进行测试将使效率和质量都得到最好的保证。Bug发现的越晚,修改它所需的费用就越高,因此从经济角度来看, 应该尽可能早的查找和修改Bug。在修改费用变的过高之前,单元测试是一个在早期抓住Bug的机会。

相比后阶段的测试,单元测试的创建更简单,维护更容易,并且可以更方便的进行重复。

结论

经验表明一个尽责的单元测试方法将会在软件开发的某个阶段发现很多的Bug,并且修改它们的成本也很低。无论什么时候作出修改都要进行完整的回归测试,在生命周期中尽早地对软件产品进行测试将使效率和质量得到最好的保证。在提供了经过测试的单元的情况下,系统集成过程将会大大地简化。开发人员可以将精力集中在单元之间的交互作用和全局的功能实现上,而不是陷入充满很多Bug的单元之中不能自拔。

优点

1、它是一种验证行为。

程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支援。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。

2、它是一种设计行为。

编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。

3、它是一种编写文档的行为。

单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。

4、它具有回归性。

自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。

范畴

如果要给单元测试定义一个明确的范畴,指出哪些功能是属于单元测试,这似乎很难。但下面讨论的四个问题,基本上可以说明单元测试的范畴,单元测试所要做的工作。

1、 它的行为和我期望的一致吗?

这是单元测试最根本的目的,我们就是用单元测试的代码来证明它所做的就是我们所期望的。

2、 它的行为一直和我期望的一致吗?

编写单元测试,如果只测试代码的一条正确路径,让它正确走一遍,并不算是真正的完成。软件开发是一项复杂的工程,在测试某段代码的行为是否和你的期望一致时,你需要确认:在任何情况下,这段代码是否都和你的期望一致;譬如参数很可疑、硬盘没有剩余空间、缓冲区溢出、网络掉线的时候。

3、 我可以依赖单元测试吗?

不能依赖的代码是没有多大用处的。既然单元测试是用来保证代码的正确性,那么单元测试也一定要值得依赖。

4、 单元测试说明我的意图了吗?

单元测试能够帮我们充分了解代码的用法,从效果上而言,单元测试就像是能执行的文档,说明了在你用各种条件调用代码时,你所能期望这段代码完成的功能。

不写的借口

到这里,我们已经列举了使用单元测试的种种理由。也许,每个人都同意,是的,该做更多的测试。这种人人同意的事情还多着呢,是的,该多吃蔬菜,该戒烟,该多休息,该多锻炼……这并不意味着我们中的所有人都会这么去做,不是吗?

1、 编写单元测试太花时间了。

我们知道,在开发时越早发现BUG,就能节省更多的时间,降低更多的风险。

如果你仍然认为在编写产品代码的时候,还是没有时间编写测试代码,那么请先考虑下面这些问题:

1)、对于所编写的代码,你在调试上面花了多少时间。

2)、对于以前你自认为正确的代码,而实际上这些代码却存在重大的bug,你花了多少时间在重新确认这些代码上面。

3)、对于一个别人报告的bug,你花了多少时间才找出导致这个bug 的源码位置。

回答完这些问题,你一定不再以“太花时间”作为拒绝单元测试的借口。

2、 运行测试的时间太长了。

合适的测试是不会让这种情况发生的。实际上,大多数测试的执行都是非常快的,因此你在几秒之内就可以运行成千上万个测试。但是有时某些测试会花费很长的时间。这时,需要把这些耗时的测试和其他测试分开。通常可以每天运行这种测试一次,或者几天一次。

3、 测试代码并不是我的工作。

你的工作就是保证代码能够正确的完成你的行为,恰恰相反,测试代码正是你不可缺少的工作。

4、 但是这些代码都能够编译通过。

我们前面已经说过,代码通过编译只是验证它的语法通过。但并不能保证它的行为就一定正确。

5、 公司请我来是为了写代码,而不是写测试。

公司付给你薪水是为了让你编写产品代码,而单元测试大体上是一个工具,是一个和编辑器、开发环境、编译器等处于同一位置的工具。

6、 我的公司并不会让我在真实系统中运行单元测试。

我们所讨论的只是针对开发者的单元测试。也就是说,如果你可以在其他的环境下(例如在正式的产品系统中)运行这些测试的话,那么它们就不再是单元测试,而是其他类型的测试了。实际上,你可以在你的本机运行单元测试,使用你自己的数据库,或者使用mock 对象。

代码编写

本文以C++为例,后半部分所介绍的单元测试工具也只介绍C++单元测试工具。下面的示例代码的开发环境是VC6.0。

产品类

class CMyClass{

public:

int Add(int i,int j);

CMyClass();

virtual ~CMyClass();

private:

int mAge; //年龄

CString mPhase; //年龄阶段,如"少年","青年"

};

建立对应的测试类CMyClassTester,为了节约编幅,只列出源文件的代码:

void CMyClassTester::CaseBegin()

{

//pObj是CMyClassTester类的成员变量,是被测试类的对象的指针,

//为求简单,所有的测试类都可以用pObj命名被测试对象的指针。

pObj = new CMyClass();

}

void CMyClassTester::CaseEnd()

{

delete pObj;

}

测试类的函数CaseBegin()和CaseEnd()建立和销毁被测试对象,每个测试用例的开头都要调用CaseBegin(),结尾都要调用CaseEnd()。

产品函数

接下来,我们建立示例的产品函数:

int CMyClass::Add(int i,int j)

{

return i+j;

}

和对应的测试函数:

void CMyClassTester::Add_int_int()

{

}

把参数表作为函数名的一部分,这样当出现重载的被测试函数时,测试函数不会产生命名冲突。下面添加测试用例:

void CMyClassTester::Add_int_int()

{

//第一个测试用例

CaseBegin();{ //1

int i = 0; //2

int j = 0; //3

int ret = pObj->Add(i,j); //4

ASSERT(ret == 0); //5

}CaseEnd(); //6

}

测试用例

下面说说测试用例、输入数据及预期输出。输入数据是测试用例的核心,对输入数据的定义是:被测试函数所读取的外部数据及这些数据的初始值。外部数据是对于被测试函数来说的,实际上就是除了局部变量以外的其他数据,老纳把这些数据分为几类:参数、成员变量、全局变量、IO媒体。IO媒体是指文件、数据库或其他储存或传输数据的媒体,例如,被测试函数要从文件或数据库读取数据,那么,文件或数据库中的原始数据也属于输入数据。一个函数无论多复杂,都无非是对这几类数据的读取、计算和写入。预期输出是指:返回值及被测试函数所写入的外部数据的结果值。返回值就不用说了,被测试函数进行了写操作的参数(输出参数)、成员变量、全局变量、IO媒体,它们的预期的结果值都是预期输出。一个测试用例,就是设定输入数据,运行被测试函数,然后判断实际输出是否符合预期。下面举一个与成员变量有关的例子:

产品函数

void CMyClass::Grow(int years)

{

mAge += years;

if(mAge < 10)

mPhase = "儿童";

else if(mAge <20)

mPhase = "少年";

else if(mAge <45)

mPhase = "青年";

else if(mAge <60)

mPhase = "中年";

else

mPhase = "老年";

}

测试函数中的一个测试用例:

CaseBegin();{

int years = 1;

pObj->mAge = 8;

pObj->Grow(years);

ASSERT( pObj->mAge == 9 );

ASSERT( pObj->mPhase == "儿童" );

}CaseEnd();

在输入数据中对被测试类的成员变量mAge进行赋值,在预期输出中断言成员变量的值。现在可以看到老纳所推荐的格式的好处了吧,这种格式可以适应很复杂的测试。在输入数据部分还可以调用其他成员函数,例如:执行被测试函数前可能需要读取文件中的数据保存到成员变量,或需要连接数据库,老纳把这些操作称为初始化操作。例如,上例中 ASSERT( ...)之前可以加pObj->OpenFile();。为了访问私有成员,可以将测试类定义为产品类的友元类。例如,定义一个宏:

#define UNIT_TEST(cls) friend class cls##Tester;

然后在产品类声明中加一行代码:UNIT_TEST(ClassName)。

测试用例设计

测试用例的核心是输入数据。预期输出是依据输入数据和程序功能来确定的,也就是说,对于某一程序,输入数据确定了,预期输出也就可以确定了,至于生成/销毁被测试对象和运行测试的语句,是所有测试用例都大同小异的,因此,我们讨论测试用例时,只讨论输入数据。

前面说过,输入数据包括四类:参数、成员变量、全局变量、IO媒体,这四类数据中,只要所测试的程序需要执行读操作的,就要设定其初始值,其中,前两类比较常用,后两类较少用。显然,把输入数据的所有可能取值都进行测试,是不可能也是无意义的,我们应该用一定的规则选择有代表性的数据作为输入数据,主要有三种:正常输入,边界输入,非法输入,每种输入还可以分类,也就是平常说的等价类法,每类取一个数据作为输入数据,如果测试通过,可以肯定同类的其他输入也是可以通过的。下面举例说明:

正常输入

例如字符串的Trim函数,功能是将字符串前后的空格去除,那么正常的输入可以有四类:前面有空格;后面有空格;前后均有空格;前后均无空格。

边界输入

上例中空字符串可以看作是边界输入。

再如一个表示年龄的参数,它的有效范围是0-100,那么边界输入有两个:0和100。

非法输入

非法输入是正常取值范围以外的数据,或使代码不能完成正常功能的输入,如上例中表示年龄的参数,小于0或大于100都是非法输入,再如一个进行文件操作的函数,非法输入有这么几类:文件不存在;目录不存在;文件正在被其他程序打开;权限错误。

如果函数使用了外部数据,则正常输入是肯定会有的,而边界输入和非法输入不是所有函数都有。一般情况下,即使没有设计文档,考虑以上三种输入也可以找出函数的基本功能点。实际上,单元测试与代码编写是“一体两面”的关系,编码时对上述三种输入都是必须考虑的,否则代码的健壮性就会成问题。

白盒覆盖

上面所说的测试数据都是针对程序的功能来设计的,就是所谓的黑盒测试。单元测试还需要从另一个角度来设计测试数据,即针对程序的逻辑结构来设计测试用例,就是所谓的白盒测试。在老纳看来,如果黑盒测试是足够充分的,那么白盒测试就没有必要,可惜“足够充分”只是一种理想状态,例如:真的是所有功能点都测试了吗?程序的功能点是人为的定义,常常是不全面的;各个输入数据之间,有些组合可能会产生问题,怎样保证这些组合都经过了测试?难于衡量测试的完整性是黑盒测试的主要缺陷,而白盒测试恰恰具有易于衡量测试完整性的优点,两者之间具有极好的互补性,例如:完成功能测试后统计语句覆盖率,如果语句覆盖未完成,很可能是未覆盖的语句所对应的功能点未测试。

白盒测试针对程序的逻辑结构设计测试用例,用逻辑覆盖率来衡量测试的完整性。逻辑单位主要有:语句、分支、条件、条件值、条件值组合,路径。语句覆盖就是覆盖所有的语句,其他类推。另外还有一种判定条件覆盖,其实是分支覆盖与条件覆盖的组合,在此不作讨论。跟条件有关的覆盖就有三种,解释一下:条件覆盖是指覆盖所有的条件表达式,即所有的条件表达式都至少计算一次,不考虑计算结果;条件值覆盖是指覆盖条件的所有可能取值,即每个条件的取真值和取假值都要至少计算一次;条件值组合覆盖是指覆盖所有条件取值的所有可能组合。老纳做过一些粗浅的研究,发现与条件直接有关的错误主要是逻辑操作符错误,例如:||写成&&,漏了写!什么的,采用分支覆盖与条件覆盖的组合,基本上可以发现这些错误,另一方面,条件值覆盖与条件值组合覆盖往往需要大量的测试用例,因此,在老纳看来,条件值覆盖和条件值组合覆盖的效费比偏低。效费比较高且完整性也足够的测试要求是这样的:完成功能测试,完成语句覆盖、条件覆盖、分支覆盖、路径覆盖。做过单元测试的朋友恐怕会对老纳提出的测试要求给予一个字的评价:晕!或者两个字的评价:狂晕!因为这似乎是不可能的要求,要达到这种测试完整性,其测试成本是不可想象的,不过,出家人不打逛语,老纳之所以提出这种测试要求,是因为利用一些工具,可以在较低的成本下达到这种测试要求,后面将会作进一步介绍。

测试工具

现在开始介绍单元测试工具,分别按编程语言进行分组介绍。

C/C++

CppUnit

首先是CppUnit,这是C++单元测试工具的鼻祖,免费的开源的单元测试框架。建议读一下Cpluser 所作的《CppUnit测试框架入门》。

C++Test

然后介绍C++Test,这是Parasoft公司的产品。[C++Test是一个功能强大的自动化C/C++单元级测试工具,可以自动测试任何C/C++函数、类,自动生成测试用例、测试驱动函数或桩函数,在自动化的环境下极其容易快速的将单元级的测试覆盖率达到100%]。

gtest

gtest测试框架是在不同平台上(Linux,Mac OS X,Windows,Cygwin,Windows CE和Symbian)为编写C++测试而生成的。它是基于xUnit架构的测试框架,支持自动发现测试,丰富的断言集,用户定义的断言,death测试,致命与非致命的失败,类型参数化测试,各类运行测试的选项和XML的测试报告。

应用

极限编程

单元测试是极限编程的基础,依赖于自动化的单元测试框架。

极限编程创建单元测试用于测试驱动开发。首先,开发人员编写单元测试用于展示软件需求或者软件缺陷。因为需求尚未实现或者现有代码中存在软件缺陷,这些测试会失败。然后,开发人员遵循测试要求编写最简单的代码去满足它,直到测试得以通过。

系统中大多数代码都经过单元测试,但并非所有代码路径都必需单元测试。极限编程强调“测试所有可能中断”的策略,而传统方法是“测试所有执行路径”。这使得极限编程开发人员比传统开发少写单元测试,但这并不是问题。不争的事实是传统方法很少完全遵循完整地测试所有执行路径的要求。极限编程相互地认识到测试很少能完备,提供了如何有效地将有限资源集中投入可花费的代价到问题关键的导引。

技术

单元测试通常情况下自动进行,但也可被手动执行。单元测试的目标是隔离程序单元并验证其正确性。自动执行使目标达成更有效,也可获得本文上述单元测试收益。

借助于自动化测试框架,开发人员可以抓住关键进行编码并通过测试去验证程序单元的正确性。在测试案例执行期间,框架通过日志记录了所有失败的测试准则。很多测试框架可以自动标记和提交失败的测试案例总结报告。根据失败的程度不同,框架可以中止后续测试。

总体说来,单元测试会激发程序员创造解耦的和内聚的代码体。单元测试实践有利于促进健康的软件开发习惯。设计模式、单元测试和重构经常一起出现在工作中,借助于它们,开发人员可以生产出最为完美的解决方案。

GTEST

When using googletest, you start by writing assertions, which are statements that check whether a condition is true. An assertion's result can be success, nonfatal failure, or fatal failure. If a fatal failure occurs, it aborts the current function; otherwise the program continues normally.

Tests use assertions to verify the tested code's behavior. If a test crashes or has a failed assertion, then it fails; otherwise it succeeds.

A test suite contains one or many tests. You should group your tests into test suites that reflect the structure of the tested code. When multiple tests in a test suite need to share common objects and subroutines, you can put them into a test fixture class.

A test program can contain multiple test suites.

googletest assertions are macros that resemble function calls. You test a class or function by making assertions about its behavior. When an assertion fails, googletest prints the assertion's source file and line number location, along with a failure message. You may also supply a custom failure message which will be appended to googletest's message.

Basic Assertions

These assertions do basic true/false condition testing.

Fatal assertion

Nonfatal assertion

Verifies

ASSERT_TRUE(condition);

EXPECT_TRUE(condition);

condition is true

ASSERT_FALSE(condition);

EXPECT_FALSE(condition);

condition is false

Remember, when they fail, ASSERT_* yields a fatal failure and returns from the current function, while EXPECT_* yields a nonfatal failure, allowing the function to continue running. In either case, an assertion failure means its containing test fails.

Binary Comparison

This section describes assertions that compare two values.

Fatal assertion

Nonfatal assertion

Verifies

ASSERT_EQ(val1, val2);

EXPECT_EQ(val1, val2);

val1 == val2

ASSERT_NE(val1, val2);

EXPECT_NE(val1, val2);

val1 != val2

ASSERT_LT(val1, val2);

EXPECT_LT(val1, val2);

val1 < val2

ASSERT_LE(val1, val2);

EXPECT_LE(val1, val2);

val1 <= val2

ASSERT_GT(val1, val2);

EXPECT_GT(val1, val2);

val1 > val2

ASSERT_GE(val1, val2);

EXPECT_GE(val1, val2);

val1 >= val2

Value arguments must be comparable by the assertion's comparison operator or you'll get a compiler error. We used to require the arguments to support the << operator for streaming to an ostream, but it's no longer necessary. If << is supported, it will be called to print the arguments when the assertion fails; otherwise googletest will attempt to print them in the best way it can. 

 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值