文章目录
1.gtest简介
gtest是一个跨平台的(Liunx、Mac OS X、Windows、Cygwin、Windows CE and Symbian)C++单元测试框架,由google公司发布。gtest是为在不同平台上为编写C++测试而生成的,它提供了丰富的断言、致命和非致命判断、参数化、“死亡测试”等等。
gtest在github上的链接为:googletest
2.gtest原理
gtest主要由一系列的宏和事件实现。
“事件” 的本质是gtest框架提供了一个机会, 可以使用这样的机会来执行定制的代码, 来给测试用例准备/清理数据。
- 宏:有
TEST
和TEST_F
宏,TEST宏针对简单的测试用例,TEST_F宏针对需要做初始化和资源回收的测试用例,有点像类似C++的构造函数和析构函数,两个宏都是把参数展开后拼成一个类。 - 事件:分为三种事件
1)TestSuite事件
需要写一个类,继承testing::Test,然后实现两个静态方法:SetUpTestCase方法在第一个TestCase之前执行;TearDownTestCase方法在最后一个TestCase之后执行。2)TestCase事件
是挂在每个用例执行前后的,需要实现的是SetUp方法和TearDown方法。SetUp方法在每个TestCase之前执行;TearDown方法在每个TestCase之后执行。下面是一个例子:3)全局事件
要实现全局事件,必须写一个类,继承testing::Environment类,实现里面的SetUp和TearDown方法。SetUp方法在所有测试用例执行前执行;TearDown方法在所有测试用例执行后执行。 除了要继承testing::Environment类,还要定义一个该全局环境的一个对象并将该对象添加到全局测试环境中。 全局事件可以按照下列方式来使用:
3.gtest安装
3.1使用dnf进行安装
1.更新dnf源
sudo yum install epel-release
sudo yum install dnf
2.安装gtest
sudo dnf install dnf-plugins-core
sudo dnf install gtest gtest-devel
使用find查看gtest头文件位置:
find /usr -name gtest.h
显示如下即为安装成功。
3.2编译gtest项目源码
1.从github下载源码:
git clone https://github.com/google/googletest.git
下载后内容如下:
可以看到里面既有gtest也有gmock。大部分情况下,gmock是配合gtest来做白盒测试的。注意编译该项目时,要求gcc版本在5.0以上,如果gcc版本低于5.0,请参考gcc4.9升级gcc10升级gcc。
2.创建build目录,在build目录下执行
cmake ../ -DCMAKE_BUILD_TYPE=Release
3.编译安装
make && make install
4.gtest内置测试相关的宏
TEST(test_case_name, test_name)
多个测试场景需要相同数据配置的情况,用TEST_F。
//TEST_F test fixture,测试夹具,测试套,承担了一个注册的功能(为测试类的类名)。
TEST_F(test_fixture,test_name)
TEST_F宏的使用需要按照C++的风格定义类,这个类需要继承自testing::Test并且重写这四个成员函数:SetUpTestCase、TearDownTestCase、SetUp、TearDown。如果有多个测试用例,那么SetUpTestCase就会在所有用例之前执行,TearDownTestCase会在所有用例之后执行;SetUp会在单个用例之前执行,TearDown会在单个用例之后执行。
【注意】使用TEST宏时,test_case_name不要求是当前测试类的类名,而使用TEST_F宏时,第一个参数必须是当前测试类的类名。
TEST_F宏第一个参数不是当前测试类的类名时,编译错误:
TEST宏的作用是创建一个简单测试,它定义了一个测试函数,在这个函数里可以使用任何C++代码并使用提供的断言来进行检查。
5.gtest断言
gtest中断言的宏可以分为两类:
一类是ASSERT宏,另一类是EXPECT宏。
1、
ASSERT_系列:如果当前点检测失败则退出当前函数
2、
EXPECT_系列:如果当前点检测失败则继续往下执行
如果对自动输出的错误信息不满意的话,也可以通过
operator<<
在失败的时候打印日志,将一些自定义的信息输出。
常用ASSERT_系列:
宏类型宏名称功能描述bool值检查ASSERT_TRUE(条件)判断条件是否为true期待结果是truebool值检查ASSERT_FALSE(条件)判断条件是否为false期待结果是false数值型数据检查ASSERT_EQ(参数1,参数2)equal相等为true数值型数据检查ASSERT_NE(参数1,参数2)not equal不等于返回true数值型数据检查ASSERT_LT(参数1,参数2)less than小于返回true数值型数据检查ASSERT_GT(参数1,参数2)greater than大于返回true数值型数据检查ASSERT_LE(参数1,参数2)less equal小于等于返回true数值型数据检查ASSERT_GE(参数1,参数2)greater equal大于等于返回true数值型数据检查ASSERT_FLOAT_EQ(expected, actual)equal两个float值相同返回true数值型数据检查ASSERT_DOUBLE_EQ(expected, actual)equal两个double值相同返回true字符串检查ASSERT_STREQ(expected_str, actual_str)判断字符串是否相等两个C风格的字符串相等返回true字符串检查ASSERT_STRNE(expected, actual)判断字符串不相等两个C风格的字符串不相等时返回true字符串检查ASSERT_STRCASEEQ(expected_str, actual_str)判断字符串是否相等(忽略大小写)/字符串检查ASSERT_STRCASENE(str1, str2)判断字符串不相等(忽略大小写)/
EXPECT_系列,具有ASSERT系列类似的宏结构。将上表中的ASSERT换成EXPECT即可。
6.自定义测试失败信息输出
7.gtest demo
7.1简单用法
下面的例子展示了gtest最简单的用法,直接引入gtest头文件和库文件即可。
7.2作为测试类使用
7.2.1定义一个要被测试的类
7.2.2定义测试类
把要测试的类作为测试类的成员变量,重写SetUpTestCase、TearDownTestCase、SetUp、TearDown成员函数。
实测SetUpTestCase、TearDownTestCase、SetUp、TearDown四个函数
只用重写其中的一对即可
(如SetUp和TearDown、SetUpTestCase和TearDownTestCase均可)。可以按实际需求选择要重写的函数。
注意SetUpTestCase和TearDownTestCase是static函数,SetUp和TearDown是虚函数。
gtest中函数声明如下:
重写任意一对即可:
7.3执行所有测试用例
8.gtest测试类的私有成员
使用gtest编写单元测试,一个最常见的问题是对类私有成员的测试与验证。理想情况下,我们希望在测试中,类中所有的数据与方法都是可以访问的;而在产品代码中,只暴露实现定义好的接口。
gtest官方文档中也提到了对私有成员的处理,方法有两种:一是使用friend关键字;二是重构采用Pimpl模式,公共类中只暴露接口,而实现类中暴露所有细节(public),测试时包含实现类即可。当然,还有另外一种简单暴力的方法,要达到的目的无非是在产品代码中,该pubic的是public,该private的还是private;而在测试代码中,全部都是public。加宏编译,将private/protected全部改为public。
8.1加宏编译
在测试类头文件中添加:
#ifdef GTEST_API_
#define private public
#define protected public
#endif
下面是一个测试private方法的例子:
如果直接测试,由于private方法无法被访问,编译失败
加入宏后,重新编译:
优点:可以非常方便的使用被测试类中的任何成员
缺点:一是使得访问私有成员过于容易,从而导致写出来的test访问私有成员的可能性增大。
测试访问了私有成员,依赖于实现
,这会让单元测试成为重构的负担,而不是保证。二是这种方法是不标准的C++用法:
C++标准不允许重定义关键词
,所以这种方法即使此时在当前的编译器上是可行的,也不能保证将来,或者在其他编译器上可行;
public, protected private等访问修饰符可能会影响对象成员的布局
,这样当测试是直接链接到产品代码时会有问题。
如果是以纯源代码的方式使用产品类的,这个方法没有任何问题;但如果是通过静态库,或者动态库的方式使用产品类的,在测试代码中若直接使用这些库,编译是没有问题,因为全部伪装成了public,但在链接的时候,因为private的成员是没有从库中导出来的,必然会出现链接错误。此时有两个方案:一是以GTEST的方式重新编译库;二是直接将产品源代码编译进你的测试工程中去。
所以,
除非对以上利弊十分清楚并且可以接受,不然,还是使用官方的FRIEND_TEST要更好一些。
8.2 FRIEND_TEST
摘自官方文档:
Private class members are only accessible from within the class or by friends. To access a class’ private members, you can declare your test fixture as a friend to the class and define accessors in your fixture. Tests using the fixture can then access the private members of your production class via the accessors in the fixture. Note that even though your fixture is a friend to your production class, your tests are not automatically friends to it, as they are technically defined in sub-classes of the fixture.( 类的私有成员只能从类内部或友元函数访问。要访问类的私有成员,可以将测试夹具声明为类的友元,并在fixture中定义访问器。然后,使用fixture的测试可以通过fixture中的访问器访问生产类的私有成员。请注意,即使你的fixture是生产类的友元,你的测试也不会自动成为它的友元,因为它们是在fixture的子类中定义的。)
FRIEND_TEST(TestCaseName, TestName)
展开后如下:
#define FRIEND_TEST(test_case_name, test_name)
friend class test_case_name##_##test_name##_Test
gtest通过FRIEND_TEST(TestCaseName, TestName)声明友元类,即TestCaseName为友元类,而TestName为具体的case名。
要使用FRIEND_TEST,在被测试的类中添加:
FRIEND_TEST(TestCaseName, TestName),如
在测试类中包含被测试类的头文件,使用友元调用被测试类的private函数:
注意,
对FRIEND_TEST声明的友元类测试时,当使用TEST宏,TEST宏的两个参数必须和FRIEND_TEST的宏参数保持一致。
或者,也可以使用TEST_F宏
上例中给了两种访问私有变量的方式:
一种是FRIEND_TEST(FriendTest, addTest)声明的类,然后通过TEST(FriendTest, addTest)实现测试特例实体,实体中需要使用FriendTest的构造函数构造一个对象,然后调用其私有的add方法。
一种是FRIEND_TEST(Friend_Test2, addTest)声明的类。它使用到了Test Fixtures技术。在Friend_Test2类中,声明了一个FriendTest对象。然后在TEST_F(Friend_Test2, addTest)中直接使用了Friend_Test2类中的成员变量调用add方法。TEST_F(Friend_Test2, addTest)既继承于Friend_Test2,又是FriendTest的友元类。
缺点:
需要通过FRIEND_TEST宏来新增该类的友元类(就是通过友元类来访问私有成员,所以这是一种
侵入式的测试
——修改了原来类的结构)。
8.3使用pimpl重构被测代码
Pimpl主要的作用是解开代码调用接口和具体实现的耦合,具体的实现可以参考C++程序设计机制——PIMPL机制。利用这种方式打开私有成员,思路是先对被测代码按照pimpl模式进行重构,把具体实现的私有成员全部放到另外一个辅助类,通过指针访问实现的私有成员,然后对这个辅助类进行原类的私有成员测试。
原被测类如下:
//MyClass.h
修改后的MyClass为:
通过辅助类进行测试:
经过上面的处理,调用新的MyClass类,就可以访问之前的私有成员了,pub_square ()对应原来的square (),pub_func ()对应原MyClass私有成员MyPrivateClass的func ()。也就可以通过调用新的MyClass来访问其原私有成员进行测试了。
缺点:实现比较复杂。如果采用此方法,需要开发编码时就采用该模式,或者提测后人工对代码进行重构,工作量比较大。
如果只是为了支持测试而做这样的重构,可能没有太大意义。况且,手工重构很麻烦。
9.gtest-demo git链接
以上截图代码示例使用
git clone https://github.com/Angeliau/gtest-demo.git
即可下载。demo使用Qt Creator生成,如果需要使用cmake,请自行生成CMakeLists.txt。
版权归原作者 絔离 所有, 如有侵权,请联系我们删除。