8

Linux大棚版gtest官网教程译文

 3 years ago
source link: https://blogread.cn/it/article/6495?f=hot1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Linux大棚版gtest官网教程译文

浏览:2266次  出处信息

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 assertionVerifiesASSERT_TRUE(condition);EXPECT_TRUE(condition);condition is trueASSERT_FALSE(condition);EXPECT_FALSE(condition);condition is false

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

【断言 - 两值比较】

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

Fatal assertionNonfatal assertionVerifiesASSERT_EQ(expected, actual);EXPECT_EQ(expected, actual);expected == actualASSERT_NE(val1, val2);EXPECT_NE(val1, val2);val1 != val2ASSERT_LT(val1, val2);EXPECT_LT(val1, val2);val1 < val2ASSERT_LE(val1, val2);EXPECT_LE(val1, val2);val1 <= val2ASSERT_GT(val1, val2);EXPECT_GT(val1, val2);val1 > val2ASSERT_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 assertionVerifiesASSERT_STREQ(expected_str, actual_str);EXPECT_STREQ(expected_str, actual_str);the two C strings have the same contentASSERT_STRNE(str1, str2);EXPECT_STRNE(str1,str2);the two C strings have different contentASSERT_STRCASEEQ(expected_str, actual_str);EXPECT_STRCASEEQ(expected_str, actual_str);the two C strings have the same content,ignore caseASSERT_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。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK