技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 其他 --> Linux大棚版gtest官网教程译文

Linux大棚版gtest官网教程译文

浏览:2525次  出处信息

gtest,英文全称是Google C++ Testing Framework,英文简称是Google Test,中文译为“谷歌C++测试框架”,它是从谷歌内部诞生并受到业界追捧的一个非常优秀的测试框架,支持如自动发现测试、自定义断言、死亡测试、自动报告等诸多功能。

其他著名的自动化测试框架产品还有CppUnit、CxxTest、JUnit、PyUnit等。

如果你是一名开发工程师,或者你编写的程序要用到生产环境中,那么,你不可避免的需要学习和掌握一种自动化测试框架,以确保你的程序测试充分,质量上乘。

gtest官网教程原文,在这里

【介绍:为什么要选择谷歌C++测试框架】

因为:“谷歌C++测试框架可以帮助你编写出更好的C++测试程序”。

无论你的开发是基于Linux、Windows还是Mac,只要你使用的是C++语言,gtest都能够帮助到你。

那么,到底什么才是好的测试,gtest又如何实现这种好的测试的呢?我们是这样认为的:

  • 1. 测试应该是独立的且可重复的。

(如果一个测试的结果是依赖于另一个测试的结果的,将是件很痛苦的事情。而gtest可以有效的避免这一点,它会确保每一个测试以一个独立对象的形式存在。当一个测试失败时,gtest支持你在独立的环境中进行调试。)

  • 2. 应该有一套方法较好的来组织我们的测试,这种组织方法要能够较好地反映程序代码的结构。

(gtest会将test分组到“test case”中这样可以很好的来组织和管理所有的测试了。同时,test cases之间既可以共享信息,也可以嵌套。这种组织规则,会非常有利于记忆和管理。如果所有项目的测试都采用一致的组织规则,那么人员在测试项目间的迁移成本也会大大降低。)

  • 3. 测试应该是可迁移的且可复用的。

(开源社区中有很多的代码是“平台中立的”,也就是兼容多种平台,因而,这些代码的测试也应该遵循“平台中立”的原则。基于这种考虑,gtest支持多种操作系统平台、多种编译器,所以,gtest可以很好的支持这类测试工作。)

  • 4. 在测试失败时,要能够提供足够充分的测试信息。

(gtest并不会在首次失败后就停止工作,取而代之的是,gtest会停止当前这个测试,继续下一个测试。当然,你完全可以设置让gtest在继续下一个测试的同时,输出这次测试中非致命失败的相关信息,这样,你就可以在一个测试周期中,侦测和修复更多个bugs。)

  • 5. 测试框架应该让开发者从琐碎重复的工作中解脱出来,让它们能专注在测试内容上。

(gtest会自动的扫描和跟踪所有定义的测试,而不会让开发者一个一个去列举。)

  • 6. 测试应该是高效的。

(使用gtest,你可以复用不同测试中的资源,另外,set-up/tear-down也支持“一处定义,多处复用”的特性。)

由于gtest是基于xUnit框架设计实现的,所以如果你之前使用过JUnit或PyUnit的话,你会很容易上手;否则,你或许需要花上10分钟的时间来学习下相关的基础知识。

好了,我们现在就开始!

【编译gtest】

为了使用gtest来写一个测试程序,你首先需要做的便是把gtest编译成一个函数库,并且链接到你的测试程序中。我们支持多种流行的build系统,如用于Visual Studio的msvc,用于Mac Xcode的xcode,,用于GNU make的make,用于Borland C++ builder的codegear,以及用于CMake的CMakeLists.txt。

如果很不幸,你所使用的build系统不在上述列表中,你可以下载make/Makefile来了解gtest的编译方法,试着自己来编译gtest。

在你编译你自己的测试项目时,你的测试程序要引用gtest/gtest.h头文件(假如你的gtest安装在GTEST_ROOT路径下,那么gtest/gtest.h会存放在GTEST_ROOT/include文件夹下面)。

【基本概念】

当你使用gtest时,你会以assertion(断言)开始,assertion用来检查一个条件是否为真。一个assertion的结果可以为success(成功)、nonfatal ailure(非致命失败)和fatal failure(致命失败)。一旦fatal failure发生,当前的测试函数会终止,而如果只是nonfatal failure,则测试程序还是会继续运行的。

gtest就是使用assertion来验证程序代码的行为的。如果一个测试崩溃或者assertion失败,那么测试就未通过。

一个test case可以包括一个或多个test。你需要把各种test归类到你的test case中,这样有利于更好的显示出代码结构。当同一个test case中的多个test需要共享一些信息时,你可以把这些test放到一个test fixture类中。
(大棚:或许你读到这句话时,感觉有些晦涩,没关系。test fixture的具体用法后面还会具体讲的。:) )

一个test program往往会包含多个test case。

基本概念就只有这些,应该不难理解的。下面我们就来编写第一个test program,主要是让大家了解assertion的使用!

【初识断言】

gtest的assertion本质上是一些宏。

当一个assertion失败了,gtest会显示出这个assertion的源文件名称、所在行号以及错误信息。当然,你也可以自定义错误信息。

assertion在测试一个函数时,可以有两种方案,即ASSERT_*和EXPECT_*。在失败发生时,ASSERT_*这类assertion会产生fatal failure,并且会终止当前函数;而EXPECT_*则只会产生nonfatal failure。
(大棚:你还记得吧,fatal failure会引起函数终止,而nonfatal failure则不会)

我们通常推荐大家使用EXPECT_*类的assertion,因为它允许我们在一个测试周期中经历多一些的failure。如果某个错误会导致后面的逻辑无法正常执行的话,那就只能用ASSERT_*来终止函数了。

如果你想自定义failure message,可以使用<<操作符,举例如下:

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";
 
for(inti = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i],y[i]) << "Vectors x and y differ at index "<< i;
}

任何可以作为流的对象都可以输出给assertion的宏,包括C字符数组及C++字符串对象等,如果将宽字符串(wchar_t*, TCHAR*, std::wstring)输出给assertion,则会以UTF-8编码方式输出。

【断言 - 基本用法】

下面这些assertion用来进行最基本的true/false条件判断:

Fatal assertionNonfatal assertionVerifies
ASSERT_TRUE(condition);EXPECT_TRUE(condition);condition is true
ASSERT_FALSE(condition);EXPECT_FALSE(condition);condition is false

还是要再提醒一下,当上述assertion失败时,ASSERT_*会产生fatal failure并且立即从当前函数退出;而EXPECT_*只会产生nonfatal failure并且允许函数继续向下执行。

【断言 - 两值比较】

我们讲解一下如何通过assertion来做两个值的比较。

Fatal assertionNonfatal assertionVerifies
ASSERT_EQ(expected, actual);EXPECT_EQ(expected, actual);expected == actual
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

一旦assertion失败,gtest会打印出val和val2的值。

而在ASSERT_EQ(expected, actual)和EXPECT_EQ(expected, actual)中,你应该在actual参数位置放置你想测试的表达式,而把你期望的值放在expected参数位置。

值得注意的是,你所提供得val1和val2应该是可以被比较的,否则gtest会报出编译错误。上述这些assertion是支持用户自定义类型的,但是要求你进行运算符重载(==、<、>等)。

对于C/C++函数来说,不同编译器对参数检查顺序的规则是不同的,所以你的测试代码也不应该对此做任何假设。

ASSERT_EQ()在进行指针比较时需要格外注意。如果所传入的是两个C字符数串,assertion只会检查两个指针是否指向同一块内存区域,而非检查连个字符串内容是否相同。所以,如果你想检查两个字符串的内容是否相同,就要使用ASSERT_STREQ()宏来做。例如,如果你想判断C字符数组是否是否为NULL,则可以使用“ASSERT_STREQ(NULL, c_string);”实现。不过,如果你想判断两个C++字符串对象的话,则需要使用ASSERT_EQ()宏。

本小节中涉及的assertion宏,都支持窄字符对象(string)和宽字符对象(wstring)。

【断言 - 字符串比较】

本小节所讲的宏用来支持C字符串的比较。如果你相比较的是两个字符串对象,那么请使用
EXPECT_EQ/EXPECT_NE等宏。

Fatal assertionNonfatal assertionVerifies
ASSERT_STREQ(expected_str, actual_str);EXPECT_STREQ(expected_str, actual_str);the two C strings have the same content
ASSERT_STRNE(str1, str2);EXPECT_STRNE(str1,str2);the two C strings have different content
ASSERT_STRCASEEQ(expected_str, actual_str);EXPECT_STRCASEEQ(expected_str, actual_str);the two C strings have the same content,ignore case
ASSERT_STRCASENE(str1, str2);EXPECT_STRCASENE(str1,str2);the two C strings have different content,ignore case

需要注意的是,assertion宏里,“CASE”表示“忽略大小写”。

STREQ和STRNE类宏,也支持宽字符串,并且在必要时(如字符串比较失败时),会以UTF-8窄字符串
方式输出。

另外,一个NULL和一个空字符串被认为是不同的。

如果你想了解更多有关字符串比较的trick方法,比如在assertion中处理子字符串、前缀、后缀、正则匹配等,请进入“高级gtest指南”。

【最简单的测试】

为了创建一个test,你需要做三件事儿:

  • 1. 使用TEST()宏来定义和命名一个test函数,这个test函数不需要return任何值。

  • 2. 在这个test函数中,你可以写任何C++语句,并且使用assertion来检查。

  • 3. 这个test的结果是由assertion决定的。如果任何一个assertion失败了,或者这个test函数崩溃了,这个test则会返回fail。否则,会返回success。

编写TEST()宏的语法如下:

TEST(test_case_name, test_name) {
... test body ...
}

TEST()所需的两个参数,从宏观到具体。第一个参数表示这个test所属test case的名称,第二个参数表示这个test自身的名称。 名称中不允许使用下划线。

一个test的全称,应该包括其所在的test case名称及自身名称。位于不同test case中的test可以拥有相同的名字。

举例,让我们来看一个简单的阶乘函数:

intFactorial(intn); // Returns the factorial of n

针对这个函数的test case,可以这样来写:

// Tests factorial of 0
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(1, Factorial(0));
}
 
// Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}

gtest用test case来管理所有test,所以逻辑上相关的test应该放到同一个test case中,也就是说,TEST()的第一个参数应该相同。在上面这个例子中,我们建立了两个test,即HandlesZeroInput和HandlesPositiveInput,它们两个同属FactorialTest这个test case。

【Test Fixtures】

如果你发现你所写的多个test都在操作类似的数据,那么我推荐你使用test fixture。这个特性允许你在不同的test里复用相同的配置。

要想建立一个fixture,请遵循下面的步骤:

  • 1. 建立一个类,并继承::testing::Test,并且使用protected或public限制符,以便其子类可以访问到共享的数据。

  • 2. 在这个类中,声明你想复用的对象。

  • 3. 如果有必要,请写一个默认的构造函数或SetUp函数来准备所需对象。(要注意的是,不要将SetUp写成Setup)

  • 4. 如果有必要,请写一个析构函数或TearDown函数来释放资源。

  • 5. 如果需要,请为你的test定义要共享的函数。

(
读到这里,你或许会有疑问,准备对象时我是应该用构造函数还是SetUp函数?在释放资源时,我是应该用析构函数,还是TearDown函数?

首先你需要了解一个test fixture的执行过程:

  • Step 1. 创建一个全新的test fixture对象(会调用构造函数)

  • Step 2. 立即调用SetUp()

  • Step 3. 运行test程序

  • Step 4. 调用TearDown()

  • Step 5. 立即删除test fixture对象(会调用析构函数)

所以,看上去,你没有必要写SetUp()和TearDown()函数,只要在构造和析构函数中写下你要做的事情就可以了。但事实并非如此,在一些场景下,你就必须使用SetUp()和TearDown()函数,让我们来看看这些场景:

  • View 1. 如果你要在收尾时抛出异常,则你必须在TearDown中抛出,而非析构函数中。这是因为在析构函数中抛出异常,会引发不可预期的结果。

  • View 2. 因为assertion自身在失败时就会抛出异常,所以在收尾时,如果你仍想调用assertion,则也只能在TearDown()中,而非析构函数中。

  • View 3. 在构造和析构函数中,不能调用虚函数,所以,如果你想调用一个重载过的虚函数,则只能在SetUp()和TearDown()中。

综上,建议大家把初始化的逻辑都写到SetUp()中,而将收尾逻辑都写到TearDown()中。
)

当你使用了test fixture,请使用TEST_F()宏来代替TEST()宏,语法格式如下:

TEST_F(test_case_name, test_name) {
... test body ...
}

和TEST()类似,TEST_F()的第一个参数也是所属test case的名称,对于TEST_F来说,还要保证这个test case名称同时也是test fixture类的名称。(或许你已经猜到了,TEST_F的_F就是指fixture)。

在定义TEST_F()之前,你就应该先定义好test fixture类,否则编译器会报错“virtual outside class declaration”。

我们用TEST_F()来定义test,gtest会按照如下流程来动作:

  • Step 1. 创建一个全新的test fixture对象(会调用构造函数)

  • Step 2. 立即调用SetUp()

  • Step 3. 运行test程序

  • Step 4. 调用TearDown()

  • Step 5. 立即删除test fixture对象(会调用析构函数)

需要注意的是,在同一个test case中的不同test会拥有不同的test fixture对象,并且gtest总是会先删除一个test fixture后才建立下一个新的test fixture。gtest不会在多个test之间复用同一个test fixture。对一个test fixture所作的改变不会影响其他的test的效果。

我们来看一个例子,我们实现了一个FIFO队列的模板类,名叫Queue,它的外部接口包括这些:

template// E is the element type.
classQueue {
public:
Queue();
voidEnqueue(constE& element); // 元素加入队列
E* Dequeue(); // 从队列中清除,若队列为空则返回NULL
size_tsize() const; // 获取队列长度
...
};

首先,我们定义一个fixture类:

classQueueTest : public::testing::Test {
protected:
virtualvoidSetUp() {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
 
// virtual void TearDown(){}
Queue q0_; // 模板类的实例
Queue q1_;
Queue q2_;
};

因为我们没有分配什么资源,所以我们也不需要在TearDown()中进行收尾工作。

下面,我们就来使用TEST_F()和这个fixture写我们的test了:

TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(0, q0_.size());
}
 
TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(NULL, n);
 
n = q1_Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(1, *n);
EXPECT_EQ(0, q1_.size());
deleten;
 
n = q2_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(2, *n);
EXPECT_EQ(1, q2_size());
deleten;
}

上面的例子中,我们使用了ASSERT_*和EXPECT_*两类assertion。由于在本文开头部分已经多次讲过两种assertion的区别,所以,相信大家对于在何种情况下应该使用哪种,应该已经很清楚了。

当上面这个test执行,会发生下面一系列的动作:

  • 1. gtest运行构造函数,创建QueueTest对象(命名为t1)

  • 2. t1.SetUp()运行

  • 3. 第一个test(IsEmptyInitially)运行

  • 4. t1.TearDown()运行

  • 5. t1被析构

  • 6. 1-5的动作在另一个QueueTest对象上被再次执行,以运行DequeueWorks这个test。

建议继续学习:

  1. Xvfb+YSlow+ShowSlow搭建前端性能测试框架    (阅读:53981)
  2. 安全测试与渗透测试区别    (阅读:23498)
  3. 使用Fiddler对手机应用进行抓包测试    (阅读:6665)
  4. 服务器性能测试工具推荐    (阅读:6186)
  5. 给Apache做压力测试时遇到的问题    (阅读:5698)
  6. WEB性能测试工具推荐    (阅读:5451)
  7. 可用性测试好助手——Morae软件的应用    (阅读:5060)
  8. 12款很棒的浏览器兼容性测试工具推荐    (阅读:4696)
  9. 可用性测试的权衡之道(二)    (阅读:4643)
  10. 在线测试不同操作系统不同浏览器网页的显示效果    (阅读:4618)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
后一篇:玩转robots协议 >>
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1