GTest简介
GTest是Google官方团队开发的开源的C++单元测试框架,地址为:https://github.com/google/googletest。该地址下同时有一个名为googlemock的项目,Google Mock可以理解为Google Test的辅助测试框架,Google Mock可以用于实现一个模拟类(mock类),可以用于完成需要接口交互但相应的类又没有实现的测试。本文不涉及Mock,以后有空了再追加Google Mock的使用。
使用Google Test可以实现:
1、测试应该是独立并且可以重复的。
2、测试应该组织良好,能够清晰的反应出测试代码的结构。
3、测试应该是可移植和可利用的。
4、当测试失败时,Google Test将会提供尽可能多的测试信息,并继续后面的测试而不是结束测试。
5、Google Test会跟踪所有用户定义的新测试,而不需要用户一一再去调用它们。从而将测试人员从繁杂的测试用例调用中解放出来,将更多的精力关注中测试本身上。
6、测试实现起来很快,因为Google Test框架能够在测试用例之间共用资源,并且它们之前还不会相互依赖。
VS下的GTest环境配置
本文测试的环境为VS IDE,linux下使用cmake生成所需要的内容,然后直接引用即可,主要是学习如何使用这个强大的工具。
首先将Google Test源码使用你喜欢的方式整到本地(以下命令是git bash中操作,如果是cmd,修改一下mkdir为md):
1 | $ git clone https://github.com/google/googletest.git |
-G
后面指定你的VS版本号(注意在git bash下有可能-G
参数无效,需要在cmd执行,或者直接使用cmake-gui操作),可能通过查看cmake的帮助找到更多版本,如果需要同时生成示例代码的VS工程,添加参数-Dgtest_build_samples=ON
运行cmake命令之后会在mybuild目录下生成gtest和gmock的项目工程文件,通过vs打开名为googletest-distribution.sln
的解决方案,直接生成即可编译完成gtest和gmock,我们这里只使用gtest,所以只关注gtest即可。在路径“mybuild\googlemock\gtest\Debug”
下会有文件“gtest_maind.lib”和“gtestd.lib”两个静态库,用这两个文件即可在自己的测试项目中调用gtest了。
为了便于后面的VS或者其他编译器调用头文件和库,可以添加环境变量GTEST_DIR值为/path/to/googletest
。值即为你的googletest代码根目录
创建项目属性表
为了便于以后新建项目可以复用项目属性表,简化新建测试项目的过程,我们可以首先生成gtest的项目属性表,以后直接引用即可。如果觉得下面的创建过程太复杂,可以直接跳过,复制下面项目属性表的内容,本地新建一个文本文件,命名为Gtest_Debug.props。注意其中的路径。
先随便建一个项目,然后在属性管理器中右键相应的配置属性,选择添加新项目属性表,任意命名,例如我的GTest_Debug。双击该属性表后在C/C++的附加包含目录添加“$(GTEST_DIR)\include;$(GTEST_DIR)”
,在链接器的常规中为附加库目录添加“$(GTEST_DIR)\mybuild\googlemock\gtest\Debug;”,输入中的附加依赖项添加“gtestd.lib;gtest_maind.lib;”。然后保存该属性表后会在相应项目文件夹中生成名为“GTest_Debug.props”的文件,将它保存在一个好找的位置,以后再创建gtest项目直接在刚才的属性管理器中右键添加现有属性表选择它就可以了。
下面是我的Gtest_Debug.props文件内容:
1 |
|
Gtest-基础应用
举例一个例子说明gtest的用法,当然强烈建议看看官方的sample。
创建自己的项目
首先创建一个简单的dll项目,该项目将包含一个名为IsLessStr的函数,检查字符串是否为小字符串,这里假定长度小于等于4的为小字符串。
IsLessStr.h文件内容如下:
1 |
|
IsLessStr.cpp文件内容如下:
1 |
|
创建dll导出文件IsLessStr.def:
1 | LIBRARY "IsLessStr" |
现在我们的这个项目将可以编译生成相应的lib和dll文件了,下面我们使用gtest进行相应的单元测试。
创建gtest测试项目
在上面项目的解决方案中创建测试项目,可以命名为IsLessStrTest,在属性管理器中右键这个测试项目的属性选择添加现有属性表,选中我们刚才的那个属性表即可完全gtest的配置。然后再在属性管理器中双击IsLessStrTest会引出它的整体属性页,通用属性—引用中添加新引用,选择我们刚才的项目IsLessStr,以使该项目链接时可以找到IsLessStr的lib文件和dll文件(dll非必须)。在它的配置属性中找到C/C++将IsLessStr的头文件所在路径添加到它的附加包含目录。到此gtest测试项目配置完成。
基本测试使用TEST(),用法:
1 | TEST(test_case_name, test_name) { |
下面是测试的实际代码Test.cpp中的内容:
1 |
|
注意上面的main函数实现,也就是说可以将测试代码放在单独的文件中,而不用再去关注main函数的实现。
编译运行,输出如下(cmd窗口中会有彩色的标记):
1 | Study GTEST |
为了测试输出信息更丰富,我们上面那最后一个测试用例其实故意写错了
断言/测试宏
Google Test采用一系列的断言(assertion)来进行代码测试,这些宏有点类似于函数调用。
当断言失败时Google Test将会打印出assertion时的源文件和出错行的位置,以及附加的失败信息,
用户可以直接通过“<<”
在这些断言宏后面跟上自己希望在断言命中时的输出信息。
测试宏可以分为两大类:ASSERT_*
和EXPECT_*
,这些成对的断言功能相同,但效果不同。其中ASSERT_*
将会在失败时产生致命错误并中止当前调用它的函数执行。EXPECT_*
版本的会生成非致命错误,不会中止当前函数,而是继续执行当前函数。通常情况应该首选使用EXPECT_*
,因为ASSERT_*
在报告完错误后不会进行清理工作,有可能导致内容泄露问题。
断言后面可以直接跟用户想要输出的信息:
1 | ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length"; |
常用的一些宏如下:
基础断言
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition is true |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition is false |
二值比较
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 |
字符串比较
Fatal assertion | Nonfatal assertion | Verifies |
---|---|---|
ASSERT_STREQ(str1,str2); | EXPECT_STREQ(str1,str2); | 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(str1,str2); | EXPECT_STRCASEEQ(str1,str2); | the two C strings have the same content, ignoring case |
ASSERT_STRCASENE(str1,str2); | EXPECT_STRCASENE(str1,str2); | the two C strings have different content, ignoring case |
Google Test
还有其他很多测试宏,例如单纯的测试成功与失败、测试指定代码块是否抛出指定异常等,更多内容参见:
https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md
Gtest-Test Fixtures(多个Test共享一份代码)
通常我们测试一些小的代码时直接使用上面的TEST进行相应的宏调用测试即可,但很多时候我们的项目会需要对一些基础数据进行初始化,然后后面运行的代码都是基于这些初始化之后的内容进行。
如果我们继续使用TEST进行测试,为了保证代码的可重复性,我们就必须对每一个测试编写相同的基础操作代码,违反了编程中的代码利用性。
此时TEST_F
就出现了,Test Fixtures
通过对::testing::Test
进行继承后,将该类做为测试的公共类,在其中进行公共代码的编写。Google Test会在每一个新的TEST_F调用之前删除前一个使用该Test Fixtures的公共类对象,这样就保证了所有测试用例的独立性。可以理解为Test Fixture的目的是为了即要少写代码,又要保证每一个测试用例数据环境的独立性。
Test Fixtures的用法:
首先需要创建一个fixture:
- 通过继承自::testing::Test创建一个类,为了使子类可以访问我们fixture的成员,内容成员应该以protected:或public:开头。
- 在类内部声明测试中需要使用的使用成员变量或成员函数。
- 类内部会有两个默认的虚函数:默认构造函数SetUp()和默认析构函数TearDown()。SetUp在应该放置每个测试中都需要提前进行的公共操作,如初始化。而TearDown中应该放置每个测试在测试之后需要进行操作,如释放资源。
创建完fixture之后,通过TEST_F()
来使用它,其中_F来自于fixture,用法如下:
1 | TEST_F(Test_Fixture_Name, test_name) { |
与TEST类似,只是test case名必须为我们定义的Test Fixture的类名,这样我们才能在test body中直接调用类对象和类成员函数(实际上是通过宏实现的)。
对于每一个TEST_F(),Google Test将会:
- 在运行时创建一个全新的test fixture。
- 通过SetUp()立即对其进行初始化
- 运行测试代码。
- 调用TearDown()清理资源
- 删除该test fixture。也就是说对于同一个test case中的每一个测试都有一个不同的test fixture对象,并且google test总是会在创建下一下test fixture之前删除之前的。以避免改变某一个测试时对其他测试环境产生影响。
TEST_F示例:
测试一下FIFO的例子,其中头文件sample3-in1.h中包含Queue的如下接口:
1 | template <typename E> // E is the element type. |
测试文件sample3_unittest.cc文件内容如下:
1 |
|
参考:https://github.com/google/googletest/blob/master/googletest/docs/Samples.md
Test Fixture中的test case如何共享数据资源
Test Fixture可以很好的做到复用一些公共代码,而有些时候我们需要同一个Test Fixture中的Test case可以共用一些基本的初始化数据,而不仅仅是公共的代码。Google Test也考虑了这个需求,通过在Test Fixture类中定义两个静态函数来解决:static void SetUpTestCase()将会在第一个测试用例调用时执行一次; static void TearDownTestCase()将会在最后一个测试用例被删除时执行一次。与SetUp()和TearDown()非常类似,很明显,你应该已经会用了。需要注意的时Google Test并不会保证测试用例按顺序执行,所以不要依赖与测试用例的顺序。下面是举例:
1 | class FooTest : public ::testing::Test { |
Gtest-测试调用(main函数)
Google Test将会自动注册TEST和TEST_F
的调用,当定义了测试用例之后,可以通过调用RUN_ALL_TESTS()
来运行测试,该函数当所有测试成功时返回0,否则返回1。
主函数通常如下:
1 |
|
可见主函数确实很简洁,所以如果不想写这段代码,如果你还记得的话,之前与gtestd.lib同目录下有一个gtest_maind.lib,可以直接用它来链接你的代码。
InitGoogleTest()函数将会解析Google Test的命令行参数,用于影响后续的测试行为
关于其命令行参数,可以参见
https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md
Gtest-输出XML报告
Google Test有三种方式指定是否输出xml报告,不管哪一种方式,都是通过”xml:_path_to_output_file_”
来设定xml文件路径,如果只有”xml”则会在项目目录下输出名为test_tetail.xml的报告。
三种配置方式如下:
- 通过配置环境变量“GTEST_OUTPUT”指定xml输出路径。例如Windows下,添加环境变量:变量名为GTEST_OUTPUT,值为你需要的路径,如:xml,或者xml:D:/test.xml
- 通过命令行参数–gtest_output指定。例如: gtestTest.exe –gtest_output=xml:outputTest.xml
- 通过在程序中设置output的FLAG值指定。
对于第3种情况,可以通过在调用testing::InitGoogleTest之前调用testing::GTEST_FLAG来指定,如:
1 | GTEST_API_ int main(int argc, char **argv) |
当三种方式同时使用时,优先级顺序为:命令行参数 > 代码中指定FLAG > 系统环境变量
Gtest filter-筛选需要运行的测试
类似于上面输出报告xml
文件,也有三种相似的方式来指定要运行哪些测试:
- 环境变量
GTEST_FILTER
- 命令行参数
--gtest_filter
- 设置相应标示位
testing::GTEST_FLAG(filter)
filter
支持通配符*
(0个名多个字符)和?
(一个字符),例如以命令行的形式让测试用例只运行以FooTest_CreateHandle
开头的用例:./foo_test --gtest_filter=FooTest_CreateHandle*
,等价于在其代码中,InitGoogleTest()
之前设置标示:
1 | testing::GTEST_FLAG(filter) = "FooTest_CreateHandle*" |
当然该标志位也可能通过命令行参数来传递
当有很多测试用例时,该功能很好用。
Gtest(portable)-2个文件包含所有gtest代码
Google是一个很重视用户体验和用户需求的公司额,为了便于用户将gtest内嵌到自己的项目中,或者为了便于携带,google提供了一个python脚本可以将Google Test的所有代码打包到2个文件中(gtest.h和gtest-all.cc)。脚本位置(1.8.0版本):googletest\scripts\ fuse_gtest_files.py
。
用法:
1 | python fuse_gtest_files.py OUTPUT_DIR |
编译方法可以查看相应的makefile
(https://github.com/google/googletest/blob/master/googletest/scripts/test/Makefile),其实可以直接使用原项目编译成库然后使用这个头文件,当然这个cc文件也可以直接编译之后与你的测试进行链接,就不再需要头文件了。
更多内容可参考链接:
https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#fusing-google-test-source-files
Gtest-其他高级功能
- 更多的Assertion:SUCCEED()、FAIL()、ADD_FAILURE()等。
- 死亡测试(Death Test),是指对程序中那些断言后直接中断程序执行的代码进行测试,例如C标准库函数的assert函数,为了保证程序正常运行,我们通常会在程序中插入这些代码,以便在出错时快速定位错误,死亡测试的目的就是让程序运行在会导致这个assertion触发的情况下,以检查其是否可以正常工作。死亡测试的宏将与正则表达式一起使用。
- 通过继承::testing::Environment的子类设置全局的SetUp和TearDown
- 当有多个测试函数调用同一个子测试函数时,在子函数中使用Assertion有可能导致该子函数报错时并不知道哪个函数的调用导致的,可以通过SCOPED_TRACE宏来更好的跟踪错误,还有一些其他有关子测试函数的高级用法。
- 支持多机环境下的分布式测试,即使用多台机器进行分布式测试,以加快测试速度。
- 支持与其他测试框架一起使用。
- 支持控制输出报告,如标准输出的颜色、xml格式及内容等。
- 支持对测试用例进行混洗(shuffling),即打乱测试顺序。用–gtest_shuffle或GTEST_SHUFFLE环境变量设置。
- 通过–gtest_repeat指定测试的次数。
- 可以选择需要进行的测试,或者忽略的测试。
- 可以通过处理测试事件来扩展Google Test,例如监听测试的情况。
这些高级功能的应用请参见:
https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md