本系列文章主要介绍GoogleTest(也称“GTest”)测试框架的相关知识,同时通过一些示例程序介绍GoogleTest测试框架的使用方法。
本文为系列文章的第二篇,主要通过一些示例程序介绍GoogleTest测试框架的使用方法。
1 TEST()宏
TEST()宏的第一个参数是Test Case的名称,第二个参数是(隶属于第一个Test Case参数的)Test的名称。一个测试的完整名称包括Test Case名称及Test的名称,不同Test Case的Test名称可以相同。
GoogleTest根据Test Case对测试结果进行分组,所以一些相关的test应当放入同一个Test Case中。
下面使用TEST()宏来编写一个测试程序,示例代码(gtest_test1.cpp)的内容如下:
#include "gtest/gtest.h"
// 此函数用于判断入参是否为正整数:如果是,则返回0;否则,返回-1
int Positive(int nNum)
{
if (nNum > 0)
{
return 0;
}
else
{
return -1;
}
}
// 测试入参2是否为正整数
TEST(PositiveTest, HandlesPositiveInput)
{
EXPECT_EQ(Positive(2), 0);
}
// 测试入参0是否为正整数
TEST(PositiveTest, HandlesZeroInput)
{
EXPECT_EQ(Positive(0), -1);
}
// 测试入参-2是否为正整数
TEST(PositiveTest, HandlesNegativeInput)
{
EXPECT_EQ(Positive(-2), -1);
}
int main(int argc, char **argv)
{
// 分析gtest程序的命令行参数
testing::InitGoogleTest(&argc, argv);
// 调用RUN_ALL_TESTS()运行所有测试用例
// main函数返回RUN_ALL_TESTS()的运行结果
return RUN_ALL_TESTS();
}
在上述代码中,我们编写了三个test,分别为:HandlesPositiveInput、HandlesZeroInput和HandlesNegativeInput,这三个test都属于同一个Test Case(PositiveTest)。
编译并执行上述代码,结果如下:
2 TEST_F()宏
当我们想让多个test使用同一套数据配置时,就需要用到Test Fixtures了。
Test Fixtures的用法相对复杂一些,创建fixture的具体方法如下:
- 派生一个继承“::testing::Test”的类,并将该类中的一些内容声明为protected类型,以便在子类中进行访问;
- 根据实际情况,编写默认的构造函数或SetUp()函数,来为每个test准备所需内容;
- 根据实际情况,编写默认的析构函数或TearDown()函数,来释放SetUp()中分配的资源;
- 根据实际情况,定义test共享的子程序。
当使用fixture时,我们使用TEST_F()宏代替TEST()宏,TEST_F()允许我们在Test Fixture中访问对象和子程序。
注意:TEST_F()的第一个参数(即,Test Case的名称)必须是Test Fixture类的名字。
对于TEST_F()定义的每个test,GoogleTest将会在运行时创建一个新的Test Fixture,并立即通过SetUp()对其进行初始化,然后运行test,之后通过调用TearDown()进行数据清理,最后删除Test Fixture。需要注意的是,同一个Test Case中不同的test具有不同的Test Fixture对象,并且GoogleTest每次创建新的Test Fixture前都会先删除之前的Test Fixture。多个test不会重用相同的Test Fixture,某个test对fixture进行的修改对其他test无影响。
使用TEST_F()宏来编写一个测试程序,示例代码(gtest_test2.cpp)的内容如下:
#include "gtest/gtest.h"
// 定义测试类FooTest
class FooTest: public testing::Test {
protected:
// Code here will be called immediately after the constructor (right before each test)
void SetUp()
{
m_nTarget = 5;
}
// Code here will be called immediately after each test (right before the destructor)
void TearDown()
{
}
public:
int IsLargeThan5(const int & nNum);
int m_nTarget;
};
// 判断入参是否大于5:如果是,则返回0;否则返回-1
int FooTest::IsLargeThan5(const int & nNum)
{
if (nNum > m_nTarget)
{
return 0;
}
else
{
return -1;
}
}
TEST_F(FooTest, HandlesInput6)
{
EXPECT_EQ(IsLargeThan5(6), 0);
}
TEST_F(FooTest, HandlesInput5)
{
EXPECT_EQ(IsLargeThan5(5), 0);
}
TEST_F(FooTest, HandlesInput4)
{
EXPECT_EQ(IsLargeThan5(4), -1);
}
int main(int argc, char **argv)
{
// 分析gtest程序的命令行参数
::testing::InitGoogleTest(&argc, argv);
// 调用RUN_ALL_TESTS()运行所有测试用例
// main函数返回RUN_ALL_TESTS()的运行结果
return RUN_ALL_TESTS();
}
在上述代码中,我们编写了三个test,分别为:HandlesInput6、HandlesInput5和HandlesInput4,这三个test都属于同一个Test Case(即,FooTest)。注意,这里的Test Case(即,FooTest) 一定要是Test Fixture类。
上述代码中的test运行时,主要会进行如下操作:
- GoogleTest构造一个FooTest类的对象(我们称之为f1);
- f1.SetUp()函数对f1进行初始化;
- 使用对象f1,运行第一个test(HandlesInput6);
- f1.TearDown()在test完成后,进行清理工作;
- 对象f1被析构。
- 上述5个步骤在另一个FooTest类的对象(如f2)中重复,此次会运行HandlesInput5。
编译并执行上述代码,结果如下:
3 调用Test
在上面的代码示例中我们能够看到,调用Test的操作是通过RUN_ALL_TESTS()宏完成的。
RUN_ALL_TESTS()宏在所有test都成功时,返回0;否则返回1。需要注意的是,RUN_ALL_TESTS()会运行所有关联的test,这些test可以来自不同的Test Case,甚至不同的源文件。
当我们调用RUN_ALL_TESTS()宏的时候,会进行以下操作:
- 保存所有googletest flag的状态;
- 为第一个test创建一个test fixture对象;
- 通过SetUp()对上一步创建的test fixture对象进行初始化;
- 使用test fixture对象运行test;
- 通过TearDown()清理fixture;
- 删除fixture;
- 还原所有googletest flag的状态;
- 为下一个test重复上述操作,直到所有的test执行完成。
注意:main()函数必须要返回RUN_ALL_TESTS()宏的结果。同时,RUN_ALL_TESTS()只能运行一次,多次调用会与GoogleTest的一些功能(如thread-safe、death tests)发生冲突。
4 编写main()函数
编写main()函数时,要返回RUN_ALL_TESTS()宏的值。
main()函数中的“::testing::InitGoogleTest()”函数将会解析命令行中的GoogleTest参数,它允许用户通过多样的命令行参数来控制测试程序的行为(即定制命令行参数的功能)。需要注意的是,“::testing::InitGoogleTest()”函数必须要在RUN_ALL_TESTS()之前调用,否则对应的flag可能不会被正常地初始化。
5 备注
Google Test是线程安全的,其线程安全特性要依赖pthreads库。