Google Test单元测试框架的使用

gtest 是一个google开源的C++单元测试的框架,它是跨平台的,可应用在windows、linux、Mac等OS平台。https://github.com/google/googletest

编译gtest

下载gtest源码

[heql@ubuntu gtest]$ git clone https://github.com/google/googletest.git

编译

[heql@ubuntu gtest]$ cd googletest/googletest
[heql@ubuntu googletest]$ g++ -isystem $(pwd)/include -I$(pwd) -lpthread -c $(pwd)/src/gtest-all.cc

编译成功后,会在当前目录下生成一个gtest-all.o文件。

生成静态库

[heql@ubuntu googletest]$ ar -rv libgtest.a gtest-all.o

执行成功后,会在当前目录下生成一个libgtest.a的静态库,以后编译测试程序链接的时候,指定这个静态库和包含本目录下的include头文件。或者可以将这个静态库和头文件拷贝到环境变量中,如下,将libgtest.a拷贝到/usr/local/lib/,将include下的gtest目录拷贝到/usr/local/include/

[heql@ubuntu googletest]$ cp libgtest.a /usr/local/lib/ 
[heql@ubuntu googletest]$ sudo cp include/gtest/ /usr/local/include/ -rf

简单测试

Factorial函数

factorial.cc文件中,创建一个求阶乘的函数Factorial,代码如下:

1
2
3
4
5
6
7
8
9
int Factorial(int n) {
int result = 1;

for(int i = 1; i <= n; ++i) {
result *= i;
}

return result;
}

Factorial函数的测试用例

factorial_unittest.cc文件中,创建测试Factorial函数的测试用例,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <gtest/gtest.h>

extern int Factorial(int n);

// Tests factorial of 0
TEST(FactorialTest, Zero) {
EXPECT_EQ(1, Factorial(0));
}

// Tests factorial of positive numbers
TEST(FactorialTest, Positive) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}

int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);

return RUN_ALL_TESTS();
}
  1. TEST() 宏用来定义和命名测试函数,这些是不返回值的普通C++函数。
  2. 在此函数中,可以使用任何有效的C++语句,使用各种Google Test断言来检查值。
  3. 测试的结果由断言确定,如果测试中的任何断言失败(致命或非致命),或者如果测试崩溃,则整个测试失败。 否则,它成功。

TEST() 的第一个参数是测试用例的名称,第二个参数是测试用例中的测试名称。 这两个名称必须是有效的C ++标识符,并且它们不应包含下划线(_)。一个测试的全称包括了包含它的测试用例名称,及其独立的测试名称。不同测试用例中的独立测试可以有相同的测试名称。

gtest 通过测试用例对测试结果进行分组,因此逻辑相关的测试应该在同一测试用例中; 换句话说,它们的 TEST() 的第一个参数应该是相同的。 在上面的例子中,有两个测试,ZeroPositive,属于同一个测试用例FactorialTest

EXPECT_EQ宏用来比较两个数字是否相等。

::testing::InitGoogleTest(&argc, argv),将命令行参数传递给gtest,进行一些初始化操作。如:--gtest_list_tests会列出所有的测试用例和测试名称。

[heql@ubuntu gtest]$ ./factorial_unittest --gtest_list_tests
FactorialTest.
  Zero
  Positive

可以执行./factorial_unittest --help查看命令行支持的参数。

RUN_ALL_TESTS()表示运行所有测试案例。

编译程序

[heql@ubuntu gtest]$ g++ factorial_unittest.cc factorial.cc -o factorial_unittest -lgtest -lpthread

运行上面的单元测试程序,输出如下:

factorial_unittest.png

FactorialTest测试用例,运行了两次测试,分别是ZeroPositive,测试的结果是正确的。注意: ZeroPositive的测试,执行的顺序是不确定的。

断言

gtest 断言是类似于函数调用的宏。您可以通过对其行为进行断言来测试类或函数。当断言失败时,gtest 会打印断言的源文件和行号位置以及失败消息。也可以提供自定义失败消息,该消息将附加到测试的信息中。

断言失败

把上面的FactorialTest测试用例的Positive测试改为:

1
2
3
4
5
6
TEST(FactorialTest, Positive) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(7, Factorial(3));
EXPECT_EQ(40320, Factorial(8));
}

编译、运行程序,输出如下:

factorial_unittest_err.png

FactorialTest测试用例,运行了两次测试,分别是ZeroPositiveZero测试成功,Positive测试失败:在factorial_unittest.cc文件的15行,Factorial(3)执行的结果应该是6,但是EXPECT_EQ的断言是7。

自定义断言失败消息

要提供自定义失败消息,只需使用<<运算符或一系列此类运算符将其流式传输到宏中即可。把上面的FactorialTest测试用例的Positive测试改为:

1
2
3
4
5
6
TEST(FactorialTest, Positive) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(7, Factorial(3)) << "Factorial(3) unequal to 7.";
EXPECT_EQ(40320, Factorial(8));
}

编译、运行程序,输出如下:

factorial_unittest_err_msg.png

可以看到输出的失败消息中,有自定义的消息Factorial(3) unequal to 7.

布尔值检查

这些断言做基本的真/假条件测试。

true_or_false.png

gtest 中,断言的宏可以理解为分为两类,一类是ASSERT系列,一类是EXPECT系列

  1. ASSERT_* 系列的断言,当检查点失败时,退出当前函数(注意:并非退出当前用例)。
  2. EXPECT_* 系列的断言,当检查点失败时,继续往下执行。

把上面的FactorialTest测试用例的Positive测试改为:

1
2
3
4
5
6
TEST(FactorialTest, Positive) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(7, Factorial(3));
EXPECT_EQ(40, Factorial(8));
}

编译、运行程序,输出如下:

factorial_unittest_err_expect.png

FactorialTest测试用例,运行了两次测试,分别是ZeroPositiveZero测试成功,PositiveFactorial(3)Factorial(8)测试失败。

把上面的FactorialTest测试用例的Positive测试改为:

1
2
3
4
5
6
TEST(FactorialTest, Positive) {
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
ASSERT_EQ(7, Factorial(3));
EXPECT_EQ(40, Factorial(8));
}

编译、运行程序,输出如下:

factorial_unittest_err_assert.png

FactorialTest测试用例,运行了两次测试,分别是ZeroPositiveZero照常测试,测试成功,PositiveFactorial(3)测试失败后,Factorial(8)不在进行测试。

数值型数据检查

比较两个值的断言。

binary.png

字符串检查

该组中的断言比较两个C字符串的值。 如果要比较两个字符串对象,请改用EXPECT_EQ,EXPECT_NE等。

string.png

ASSERT_EQ 在两个C字符串上使用,它会测试它们是否在同一个内存位置,而不是它们具有相同的值。因此,如果想比较C字符串(例如const char *)的值,请使用 ASSERT_STREQ 。要比较两个字符串对象,应该使用 ASSERT_EQ

要断言C字符串为NULL,请使用 ASSERT_STREQ(NULL,c_string),NULL指针和空字符串被认为是不同的。

如下代码,测试成功:

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
#include <gtest/gtest.h>

TEST(StringTest, Zero) {
char *p = NULL;

EXPECT_STREQ(NULL, p);
}

TEST(StringTest, Address) {
int number = 100;
int *p = &number;
int *q = &number;

EXPECT_EQ(p, q);
}

TEST(StringTest, Content) {
const char *kHelloString1 = "hello";
const char *kHelloString2 = "hello";

EXPECT_STREQ(kHelloString1, kHelloString2);

std::string hello_string1 = "hello, world";
std::string hello_string2 = "hello, world";

EXPECT_EQ(hello_string1, hello_string2);
}

int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);

return RUN_ALL_TESTS();
}

浮点数检查

float.png

Test Fixtures(测试装饰类) 对多个测试使用相同的数据配置

如果你发现自己写了两个或更多的测试来操作类似的数据,你可以使用测试装饰类。它允许您为几个不同的测试重复使用相同的对象配置。

要创建测试装饰类,只需:

  1. ::testing::Test派生一个类。 使用protected:或public:开始它的主体,因为我们想从子类访问fixture成员。
  2. 在类中,声明你打算使用的任何对象。
  3. 如果需要,可以编写默认构造函数或SetUp函数来为每个测试准备对象。
  4. 如果需要,写一个析构函数或TearDown函数来释放你在SetUp中分配的任何资源。

当使用夹具时,使用 TEST_F() 而不是 TEST()

Foo类

foo.h文件中,创建一个类Foo,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef FOO_H_
#define FOO_H_

class Foo {
public:
Foo() : value_(0) {}
void set_value(const int value) {value_ = value;}
int value(void) const {return value_;}

private:
int value_;
};

#endif // FOO_H_

Foo类的测试用例

foo_unittest.cc文件中,创建测试Foo类的测试用例,使用TEST() 宏进行测试,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <gtest/gtest.h>

#include "foo.h"

TEST(FooTest, DefaultConstructor) {
Foo foo;

EXPECT_EQ(0, foo.value());
}

TEST(FooTest, SetValue) {
Foo foo;

foo.set_value(100);
EXPECT_EQ(100, foo.value());
}

int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);

return RUN_ALL_TESTS();
}

FooTest测试用例的测试DefaultConstructorSetValue分别创建一个Foo的实例。可以使用TEST_F()来共享这个实例,指向创建一个实例即可,代码如下:

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
#include <gtest/gtest.h>

#include "foo.h"

class FooTest : public ::testing::Test {
protected:
virtual void SetUp() {}
virtual void TearDown() {}

Foo foo;
};

TEST_F(FooTest, DefaultConstructor) {
EXPECT_EQ(0, foo.value());
}

TEST_F(FooTest, SetValue) {
foo.set_value(100);
EXPECT_EQ(100, foo.value());
}

int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);

return RUN_ALL_TESTS();
}

在这里SetUpTearDown函数并没有使用,如果需要可以使用SetUp函数对foo实例进行一些初始化,TearDown函数来释放foo的资源。

参数化测试

在设计测试用例时,经常需要考虑给被测函数传入不同的值的情况。我们之前的做法通常是写一个通用方法,然后编写在测试案例调用它。即使使用了通用方法,这样的工作也是有很多重复性的。

IsPrime函数

IsPrime函数是用来判断一个整数是否为素数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool IsPrime(int n) {
if(n <= 1) {
return false;
}

if(n % 2 == 0) {
return (n == 2);
}

for(int i = 3;; i += 2) {
if(i > n / i) {
break;
}

if(n % i == 0) {
return false;
}
}

return true;
}

IsPrime函数的测试用例

使用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
#include <gtest/gtest.h>

bool IsPrime(int n);

// Tests n is not prime
TEST(IsPrimeTest, FalseReturn) {
EXPECT_FALSE(IsPrime(-1));
EXPECT_FALSE(IsPrime(0));
EXPECT_FALSE(IsPrime(1));
}

// Tests n is prime
TEST(IsPrimeTest, TrueReturn) {
EXPECT_TRUE(IsPrime(3));
EXPECT_TRUE(IsPrime(5));
EXPECT_TRUE(IsPrime(11));
EXPECT_TRUE(IsPrime(17));
EXPECT_TRUE(IsPrime(19));
}

int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);

return RUN_ALL_TESTS();
}
使用值参数化进行测试
  1. 添加一个类,继承testing::TestWithParam<T>,其中T就是你需要参数化的参数类型,比如上面的例子,需要参数化一个int型的参数。
1
2
3
class IsPrimeTest : public ::testing::TestWithParam<int> {

};
  1. 使用一个新的宏TEST_P,在 TEST_P 宏里,使用GetParam()获取当前的参数的具体值。
1
2
3
4
TEST_P(IsPrimeTest, TestReturnTrue) {
int n = GetParam();
EXPECT_TRUE(IsPrime(n));
}
  1. 使用 INSTANTIATE_TEST_CASE_P 这宏来告诉gtest要测试的参数范围。
1
2
3
INSTANTIATE_TEST_CASE_P(PrimeParamTest, 
IsPrimeTest,
::testing::Values(-1, 0, 3, 5, 11));

第一个参数是测试用例的前缀,可以任意取。
第二个参数是测试案例的名称,需要和之前定义的参数化的类的名称相同,如:IsPrimeTest
第三个参数是可以理解为参数生成器,上面的例子使用test::Values表示使用括号内的参数。Google提供了一系列的参数生成的函数:

param.png

编译、运行程序,输出如下:

is_prime_unittest.png

可以看到参数-1,0的返回值不是true,而是false。其余的参数测试是正确的。

其他

gtest 还提供了一些高级的用法,可以参考:

https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md