blueyi's notes

Follow Excellence,Success will chase you!

0%

Google Test(gtest)使用

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
2
3
4
5
$ git clone https://github.com/google/googletest.git
$ cd googletest
$ mkdir mybuild
$ cd mybuild
$ cmake –G “Visual Studio 12 2013” ..

-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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(GTEST_DIR)\include;$(GTEST_DIR)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>gtestd.lib;gtest_maind.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(GTEST_DIR)\mybuild\googlemock\gtest\Debug;</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>

Gtest-基础应用

举例一个例子说明gtest的用法,当然强烈建议看看官方的sample。

创建自己的项目

首先创建一个简单的dll项目,该项目将包含一个名为IsLessStr的函数,检查字符串是否为小字符串,这里假定长度小于等于4的为小字符串。
IsLessStr.h文件内容如下:

1
2
3
4
5
6
7
#ifndef IS_LESS_H
#define IS_LESS_H
#include <string>

bool IsLessStr(const std::string &str);

#endif

IsLessStr.cpp文件内容如下:

1
2
3
4
5
6
7
8
9
10
#include "IsLessStr.h"

bool IsLessStr(const std::string &str)
{
if (str.length() > 4)
{
return false;
}
return true;
}

创建dll导出文件IsLessStr.def:

1
2
3
4
LIBRARY "IsLessStr"

EXPORTS
IsLessStr

现在我们的这个项目将可以编译生成相应的lib和dll文件了,下面我们使用gtest进行相应的单元测试。

创建gtest测试项目

在上面项目的解决方案中创建测试项目,可以命名为IsLessStrTest,在属性管理器中右键这个测试项目的属性选择添加现有属性表,选中我们刚才的那个属性表即可完全gtest的配置。然后再在属性管理器中双击IsLessStrTest会引出它的整体属性页,通用属性—引用中添加新引用,选择我们刚才的项目IsLessStr,以使该项目链接时可以找到IsLessStr的lib文件和dll文件(dll非必须)。在它的配置属性中找到C/C++将IsLessStr的头文件所在路径添加到它的附加包含目录。到此gtest测试项目配置完成。
基本测试使用TEST(),用法:

1
2
3
TEST(test_case_name, test_name) {
... test body ...
}

下面是测试的实际代码Test.cpp中的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include "gtest/gtest.h"
#include "IsLessStr.h"

GTEST_API_ int main(int argc, char **argv)
{
std::cout << "Study GTEST" << std::endl;
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

TEST(IsLessStr_Test, TestShortLen)
{
const std::string str = "H";
EXPECT_TRUE(IsLessStr(str)) << "Test H is the less str";
}

TEST(IsLessStr_Test, TestEmptyStr)
{
const std::string str;
EXPECT_TRUE(IsLessStr(str));
}

TEST(IsLessStr_Test, TestLongLen)
{
const std::string str = "Hello World! I Love China";
EXPECT_TRUE(IsLessStr(str)) << "AHA, This is an mistake";
}

注意上面的main函数实现,也就是说可以将测试代码放在单独的文件中,而不用再去关注main函数的实现。
编译运行,输出如下(cmd窗口中会有彩色的标记):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Study GTEST
[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from IsLessStr_Test
[ RUN ] IsLessStr_Test.TestShortLen
d:\proj\gteststudy\gteststudy\getstrtest\test.cpp(16): error: Value of: IsLessStr(str)
Actual: true
Expected: false
Test H is the less str
[ FAILED ] IsLessStr_Test.TestShortLen (1 ms)
[ RUN ] IsLessStr_Test.TestEmptyStr
[ OK ] IsLessStr_Test.TestEmptyStr (0 ms)
[ RUN ] IsLessStr_Test.TestLongLen
d:\proj\gteststudy\gteststudy\getstrtest\test.cpp(28): error: Value of: IsLessStr(str)
Actual: false
Expected: true
AHA, This is an mistake
[ FAILED ] IsLessStr_Test.TestLongLen (1 ms)
[----------] 3 tests from IsLessStr_Test (2 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (3 ms total)
[ PASSED ] 1 test.
[ FAILED ] 2 tests, listed below:
[ FAILED ] IsLessStr_Test.TestShortLen
[ FAILED ] IsLessStr_Test.TestLongLen

2 FAILED TESTS

为了测试输出信息更丰富,我们上面那最后一个测试用例其实故意写错了

断言/测试宏

Google Test采用一系列的断言(assertion)来进行代码测试,这些宏有点类似于函数调用。
当断言失败时Google Test将会打印出assertion时的源文件和出错行的位置,以及附加的失败信息,
用户可以直接通过“<<”在这些断言宏后面跟上自己希望在断言命中时的输出信息。
测试宏可以分为两大类:ASSERT_*EXPECT_*,这些成对的断言功能相同,但效果不同。其中ASSERT_*将会在失败时产生致命错误并中止当前调用它的函数执行。EXPECT_*版本的会生成非致命错误,不会中止当前函数,而是继续执行当前函数。通常情况应该首选使用EXPECT_*,因为ASSERT_*在报告完错误后不会进行清理工作,有可能导致内容泄露问题。

断言后面可以直接跟用户想要输出的信息:

1
2
3
4
5
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";

for (int i = 0; i < x.size(); ++i) {
EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}

常用的一些宏如下:

基础断言

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:

  1. 通过继承自::testing::Test创建一个类,为了使子类可以访问我们fixture的成员,内容成员应该以protected:或public:开头。
  2. 在类内部声明测试中需要使用的使用成员变量或成员函数。
  3. 类内部会有两个默认的虚函数:默认构造函数SetUp()和默认析构函数TearDown()。SetUp在应该放置每个测试中都需要提前进行的公共操作,如初始化。而TearDown中应该放置每个测试在测试之后需要进行操作,如释放资源。

创建完fixture之后,通过TEST_F()来使用它,其中_F来自于fixture,用法如下:

1
2
3
TEST_F(Test_Fixture_Name, test_name) {
... test body ...
}

与TEST类似,只是test case名必须为我们定义的Test Fixture的类名,这样我们才能在test body中直接调用类对象和类成员函数(实际上是通过宏实现的)。
对于每一个TEST_F(),Google Test将会:

  1. 在运行时创建一个全新的test fixture。
  2. 通过SetUp()立即对其进行初始化
  3. 运行测试代码。
  4. 调用TearDown()清理资源
  5. 删除该test fixture。也就是说对于同一个test case中的每一个测试都有一个不同的test fixture对象,并且google test总是会在创建下一下test fixture之前删除之前的。以避免改变某一个测试时对其他测试环境产生影响。

TEST_F示例:
测试一下FIFO的例子,其中头文件sample3-in1.h中包含Queue的如下接口:

1
2
3
4
5
6
7
8
9
template <typename E> // E is the element type.
class Queue {
public:
Queue();
void Enqueue(const E& element);
E* Dequeue(); // Returns NULL if the queue is empty.
size_t size() const;
...
};

测试文件sample3_unittest.cc文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include "sample3-inl.h"
#include "gtest/gtest.h"
namespace {
// 首先通过继承testing::Test类创建一个测试Test Fixture
class QueueTestSmpl3 : public testing::Test {
protected: // 为了子类可以访问其成员,将类成员定义为protected
// SetUp()将会在每个test中被调用,可以在其中定义一些需要初始化的变量
// 如果不需要,可以直接跳过
virtual void SetUp() {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// TearDown()将会在每个test结束时调用,可以用于清理工作
// 如果不需要,可以直接省略
// virtual void TearDown() {
// }
// 有些测试中会用到的辅助函数
static int Double(int n) {
return 2*n;
}
// 用于测试Queue::Map()的辅助函数
void MapTester(const Queue<int> * q) {
// Map函数接受一个函数做为参数,对queue中每一个元素调用该函数之后
// 返回一个新的queue
const Queue<int> * const new_q = q->Map(Double);
// 通过ASSERT_EQ验证新的queue是否具有相同的元素个数
ASSERT_EQ(q->Size(), new_q->Size());
// 调用EXPECT_EQ验证新queue中的每一个元素是否正确
for ( const QueueNode<int> * n1 = q->Head(), * n2 = new_q->Head();
n1 != NULL; n1 = n1->next(), n2 = n2->next() ) {
EXPECT_EQ(2 * n1->element(), n2->element());
}
delete new_q;
}
// 声明需要用到的成员变量
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
// 通过TEST_F调用test fixture类
// 测试默认构造函数
TEST_F(QueueTestSmpl3, DefaultConstructor) {
// 注意这里看上去像是直接通过成员变量名调用了类成员变量
EXPECT_EQ(0u, q0_.Size());
}
// 测试Dequeue
TEST_F(QueueTestSmpl3, Dequeue) {
int * n = q0_.Dequeue();
EXPECT_TRUE(n == NULL);
n = q1_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(1, *n);
EXPECT_EQ(0u, q1_.Size());
delete n;
n = q2_.Dequeue();
ASSERT_TRUE(n != NULL);
EXPECT_EQ(2, *n);
EXPECT_EQ(1u, q2_.Size());
delete n;
}
// 测试Map函数
TEST_F(QueueTestSmpl3, Map) {
MapTester(&q0_);
MapTester(&q1_);
MapTester(&q2_);
}
} // namespace

参考: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class FooTest : public ::testing::Test {
protected:
// 将在第一个测试用例运行时调用
// 不需要的话可以直接忽略
static void SetUpTestCase() {
shared_resource_ = new ...;
}
// 将在最后一个测试用例删除时调用
// 不需要的话可以直接忽略
static void TearDownTestCase() {
delete shared_resource_;
shared_resource_ = NULL;
}
// 每一个测试用例都需要执行的内容
virtual void SetUp() { ... }
virtual void TearDown() { ... }
// 一些由所有测试用例共用且消耗比较大的资源
static T* shared_resource_;
};
T* FooTest::shared_resource_ = NULL;
TEST_F(FooTest, Test1) {
... 可以在这里引用你的shared_resource ...
}
TEST_F(FooTest, Test2) {
... 可以在这里引用你的shared_resource ...
}

Gtest-测试调用(main函数)

Google Test将会自动注册TEST和TEST_F的调用,当定义了测试用例之后,可以通过调用RUN_ALL_TESTS()来运行测试,该函数当所有测试成功时返回0,否则返回1。
主函数通常如下:

1
2
3
4
5
6
#include "gtest/gtest.h"
int main(int argc, char **argv) {
// 一些FLAG设置,例如xml的输出
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

可见主函数确实很简洁,所以如果不想写这段代码,如果你还记得的话,之前与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的报告。
三种配置方式如下:

  1. 通过配置环境变量“GTEST_OUTPUT”指定xml输出路径。例如Windows下,添加环境变量:变量名为GTEST_OUTPUT,值为你需要的路径,如:xml,或者xml:D:/test.xml
  2. 通过命令行参数–gtest_output指定。例如: gtestTest.exe –gtest_output=xml:outputTest.xml
  3. 通过在程序中设置output的FLAG值指定。

对于第3种情况,可以通过在调用testing::InitGoogleTest之前调用testing::GTEST_FLAG来指定,如:

1
2
3
4
5
6
7
GTEST_API_ int main(int argc, char **argv)
{
std::cout << "Study GTEST" << std::endl;
testing::GTEST_FLAG(output) = "xml:d:/1.xml"; // 指定报告输出路径
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

当三种方式同时使用时,优先级顺序为:命令行参数 > 代码中指定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

编译方法可以查看相应的makefilehttps://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-其他高级功能

  1. 更多的Assertion:SUCCEED()、FAIL()、ADD_FAILURE()等。
  2. 死亡测试(Death Test),是指对程序中那些断言后直接中断程序执行的代码进行测试,例如C标准库函数的assert函数,为了保证程序正常运行,我们通常会在程序中插入这些代码,以便在出错时快速定位错误,死亡测试的目的就是让程序运行在会导致这个assertion触发的情况下,以检查其是否可以正常工作。死亡测试的宏将与正则表达式一起使用。
  3. 通过继承::testing::Environment的子类设置全局的SetUp和TearDown
  4. 当有多个测试函数调用同一个子测试函数时,在子函数中使用Assertion有可能导致该子函数报错时并不知道哪个函数的调用导致的,可以通过SCOPED_TRACE宏来更好的跟踪错误,还有一些其他有关子测试函数的高级用法。
  5. 支持多机环境下的分布式测试,即使用多台机器进行分布式测试,以加快测试速度。
  6. 支持与其他测试框架一起使用。
  7. 支持控制输出报告,如标准输出的颜色、xml格式及内容等。
  8. 支持对测试用例进行混洗(shuffling),即打乱测试顺序。用–gtest_shuffle或GTEST_SHUFFLE环境变量设置。
  9. 通过–gtest_repeat指定测试的次数。
  10. 可以选择需要进行的测试,或者忽略的测试。
  11. 可以通过处理测试事件来扩展Google Test,例如监听测试的情况。

这些高级功能的应用请参见:
https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md

参考:https://github.com/google/googletest

Welcome to my other publishing channels