gtest使用方法 - artinfo1982/demos GitHub Wiki
gtest是google开发的一套C++单元测试框架,可以跨平台运行。
gtest的能力绝不仅仅局限于C++单元测试,其实它是一个完备的测试框架,基于gtest,可以完成包括性能、可靠性等多种复杂测试。
可以在gtest的github上下载其最新版本:
https://github.com/google/googletest/releases
将源码包上传到linux上,解压后进入googletest文件夹,执行以下命令,生成gtest-all.o文件:
g++ -isystem ./include -I./ -pthread -c src/gtest-all.cc
执行以下命令,生成libgtest.a文件:
ar -rv libgtest.a gtest-all.o
这样,生成的静态库libgtest.a和googletest的include文件夹,就成为我们后续使用时,编译依赖的库文件
gtest作为一款C++单元测试框架,最主要的当然是其C++单元测试的能力。下面举例说明(基于cygwin平台)。
假设有这样一个被测类,CDemo,是一个最简单的数学运算器,其定义如下:
class CDemo
{
public:
CDemo(int a, int b):
a_(a), b_(b) {}
int add() {return a_ + b_;}
int sub() {return a_ - b_;}
int mul() {return a_ * b_;}
int div() {return a_ / b_;}
private:
int a_;
int b_;
};
说这个被测类简单,是因为在函数的实现十分简单,没有考虑任何异常,比如被除数为0,这也正是需要被测试的原因。
简化起见,我们将被测类和测试类放在同一个文件中,比如test.cpp,其放在src文件夹下,将googletest中的include和libgtst.a拷贝至和src同级的位置,如下图的树状结构:
$ tree
.
├── include
│ └── gtest
│ ├── gtest.h
│ ├── gtest_pred_impl.h
│ ├── gtest_prod.h
│ ├── gtest-death-test.h
│ ├── gtest-message.h
│ ├── gtest-param-test.h
│ ├── gtest-param-test.h.pump
│ ├── gtest-printers.h
│ ├── gtest-spi.h
│ ├── gtest-test-part.h
│ ├── gtest-typed-test.h
│ └── internal
│ ├── custom
│ │ ├── gtest.h
│ │ ├── gtest-port.h
│ │ └── gtest-printers.h
│ ├── gtest-death-test-internal.h
│ ├── gtest-filepath.h
│ ├── gtest-internal.h
│ ├── gtest-linked_ptr.h
│ ├── gtest-param-util.h
│ ├── gtest-param-util-generated.h
│ ├── gtest-param-util-generated.h.pump
│ ├── gtest-port.h
│ ├── gtest-port-arch.h
│ ├── gtest-string.h
│ ├── gtest-tuple.h
│ ├── gtest-tuple.h.pump
│ ├── gtest-type-util.h
│ └── gtest-type-util.h.pump
├── libgtest.a
└── src
└── test.cpp
完整的test.cpp内容:
#include <iostream>
#include "gtest/gtest.h" //必须包含这个才能使用gtest
using namespace std;
using namespace testing; //引用gtest的名字空间testing
class CDemo
{
public:
CDemo(int a, int b):
a_(a), b_(b) {}
int add() {return a_ + b_;}
int sub() {return a_ - b_;}
int mul() {return a_ * b_;}
int div() {return a_ / b_;}
private:
int a_;
int b_;
};
//测试类,命名规则是被测类Test,先简化,里面可以什么也不写
class CDemoTest :
public testing::Test
{
};
TEST_F(CDemoTest, Test1) //TEST_F是一个宏,后面会说明
{
cout << "enter Test1" << endl;
CDemo d(8, 4);
ASSERT_EQ(12, d.add()); //断言,此处是判断是否相等,其余的断言类型在后面会介绍
cout << "leave Test1" << endl;
}
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv); //初始化gtest
return RUN_ALL_TESTS(); //跑所有的单元测试用例
}
在test.cpp所在目录,执行以下命令编译:
g++ test.cpp -I../include ../libgtest.a -o test
执行编译出的文件后的结果:
$ ./test.exe
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from CDemoTest
[ RUN ] CDemoTest.Test1
enter Test1
leave Test1
[ OK ] CDemoTest.Test1 (0 ms)
[----------] 1 test from CDemoTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (2 ms total)
[ PASSED ] 1 test.
这样就完成了一个最简单的单元测试用例执行。
进一步,gtest还可以让我们自定义setup、teardown。setup、teardown的意义在于,setup会在用例执行前执行,通常做一些数据预置的事情,teardown会在用例执行之后或者用例执行过程中出错而执行,通常做一些数据清理的事情,避免用例之间数据干扰。
gtest的自定义setup、teardown,在测试类中进行。用上面的例子,将CDemoTest改造一下(简化起见,仅用打印):
class CDemoTest :
public testing::Test
{
protected:
virtual void SetUp()
{
cout << "setup" << endl;
}
virtual void TearDown()
{
cout << "teardwon" << endl;
}
};
重新编译执行后的结果:
$ ./test.exe
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from CDemoTest
[ RUN ] CDemoTest.Test1
setup
enter Test1
leave Test1
teardwon
[ OK ] CDemoTest.Test1 (0 ms)
[----------] 1 test from CDemoTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[ PASSED ] 1 test.
可见用例正常执行时,依次执行了setup、case、teardown。
那如果用例执行失败了,teardown是否会执行呢,答案是肯定的。我们把测试用例改造下,其他不变:
TEST_F(CDemoTest, Test1)
{
cout << "enter Test1" << endl;
CDemo d(8, 4);
ASSERT_EQ(12, d.add());
ASSERT_EQ(2, d.sub()); //这一句会出错,预期下一句不会执行
ASSERT_EQ(32, d.mul());
cout << "leave Test1" << endl;
}
重新编译后执行结果:
$ ./test.exe
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from CDemoTest
[ RUN ] CDemoTest.Test1
setup
enter Test1
test.cpp:40: Failure
Expected: 2
To be equal to: d.sub()
Which is: 4
teardwon
[ FAILED ] CDemoTest.Test1 (0 ms)
[----------] 1 test from CDemoTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] CDemoTest.Test1
1 FAILED TEST
可见用例执行过程中出了错,后续的用例步骤不会执行,但teardown是一定会执行的。
再进一步,这只是一个用例,要是多个用例呢,请看下面的例子,我们构造四个用例,分别进行加减乘除的测试,简化起见,后续都去掉自定义的setup、teardwon,先看4个用例都成功的情况:
TEST_F(CDemoTest, Add)
{
CDemo d(8, 4);
ASSERT_EQ(12, d.add());
}
TEST_F(CDemoTest, Sub)
{
CDemo d(8, 4);
ASSERT_EQ(4, d.sub());
}
TEST_F(CDemoTest, Mul)
{
CDemo d(8, 4);
ASSERT_EQ(32, d.mul());
}
TEST_F(CDemoTest, Div)
{
CDemo d(8, 4);
ASSERT_EQ(2, d.div());
}
重新编译后,执行结果如下:
$ ./test.exe
[==========] Running 4 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 4 tests from CDemoTest
[ RUN ] CDemoTest.Add
[ OK ] CDemoTest.Add (0 ms)
[ RUN ] CDemoTest.Sub
[ OK ] CDemoTest.Sub (0 ms)
[ RUN ] CDemoTest.Mul
[ OK ] CDemoTest.Mul (0 ms)
[ RUN ] CDemoTest.Div
[ OK ] CDemoTest.Div (0 ms)
[----------] 4 tests from CDemoTest (0 ms total)
[----------] Global test environment tear-down
[==========] 4 tests from 1 test case ran. (0 ms total)
[ PASSED ] 4 tests.
可见4个用例都顺次执行成功,那如果中间某个用例执行失败呢,会不会影响其他用例的执行呢,答案是否定的。我们故意将乘法的用例出错:
TEST_F(CDemoTest, Mul)
{
CDemo d(8, 4);
ASSERT_EQ(30, d.mul());
}
重新编译后执行:
$ ./test.exe
[==========] Running 4 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 4 tests from CDemoTest
[ RUN ] CDemoTest.Add
[ OK ] CDemoTest.Add (0 ms)
[ RUN ] CDemoTest.Sub
[ OK ] CDemoTest.Sub (0 ms)
[ RUN ] CDemoTest.Mul
test.cpp:41: Failure
Expected: 30
To be equal to: d.mul()
Which is: 32
[ FAILED ] CDemoTest.Mul (0 ms)
[ RUN ] CDemoTest.Div
[ OK ] CDemoTest.Div (0 ms)
[----------] 4 tests from CDemoTest (0 ms total)
[----------] Global test environment tear-down
[==========] 4 tests from 1 test case ran. (1 ms total)
[ PASSED ] 3 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] CDemoTest.Mul
1 FAILED TEST
可见,失败的用例标记了出来,没有影响后续用例的执行。
下面我们就几个比较重要的知识点进行讲解:
testing是一个名字空间,定义在gtest.h中,里面包含了一些类的定义,摘抄如下:
namespace testing
{
......
class Test;
class TestCase;
class TestInfo;
class UnitTest;
......
}
TEST和TEST_F的定义都在gtest.h中:
#define GTEST_TEST(test_case_name, test_name)\
GTEST_TEST_(test_case_name, test_name, \
::testing::Test, ::testing::internal::GetTestTypeId())
#if !GTEST_DONT_DEFINE_TEST
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
#endif
#define TEST_F(test_fixture, test_name)\
GTEST_TEST_(test_fixture, test_name, test_fixture, \
::testing::internal::GetTypeId<test_fixture>())
它们二人都公共引用了另一个宏定义GTEST_TEST_,这个宏在internal/gtest-internal.h中定义:
#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\
class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\
public:\
GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\
private:\
virtual void TestBody();\
static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\
GTEST_DISALLOW_COPY_AND_ASSIGN_(\
GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\
};\
\
::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\
::test_info_ =\
::testing::internal::MakeAndRegisterTestInfo(\
#test_case_name, #test_name, NULL, NULL, \
::testing::internal::CodeLocation(__FILE__, __LINE__), \
(parent_id), \
parent_class::SetUpTestCase, \
parent_class::TearDownTestCase, \
new ::testing::internal::TestFactoryImpl<\
GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\
从两者的定义可以看出,差别在第一个参数上。TEST的第一个参数仅仅是个测试用例的名字,而TEST_F的第一个参数是fixture(意思是测试中依赖的数据和条件等,一般是类)。TEST一般用在简单测试上,比如没有类,仅仅针对某个或某些函数测试,而TEST_F一般用在测试class等复杂数据结构上,因为被测的class可以在测试类中一次生成,供很多用例共享其数据,这是TEST无法办到的。
gtest断言的介绍在这个文章里有详细的描述,此处引用:
http://blog.csdn.net/acaiwlj/article/details/49329477
gtest是一款单元测试框架,但它不仅仅可以做单元测试,还可以做复杂的比如功能、性能、可靠性等测试,可以用C调用shell来完成,下面分步说明。
用例中C调用shell脚本,外部由gtest框架统一进行用例调度
将test.cpp改写一下,主要实现C调用shell程序的函数:
#include <iostream>
#include <stdio.h>
#include "gtest/gtest.h"
#define BUF_SIZE 64
using namespace std;
using namespace testing;
int execCmd(const char *cmd)
{
int result = 1;
FILE *fstream = NULL;
char *buf = (char *)malloc(BUF_SIZE * sizeof(char));
memset(buf, 0x0, BUF_SIZE);
if (NULL == (fstream = popen(cmd, "r")))
{
perror("popen");
free(buf);
buf = NULL;
return 1;
}
while (NULL != fgets(buf, BUF_SIZE, fstream)) {}
result = atoi(buf);
pclose(fstream);
free(buf);
buf = NULL;
return result;
}
TEST(Demo, Test1)
{
ASSERT_EQ(0, execCmd("/home/cd/a.sh"));
}
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
编译好后,在/home/cd下面创建一个名为a.sh的脚本,内容如下(最简单的形式):
#! /bin/bash
function demo()
{
date > /dev/null
return 0
}
demo
echo -n "0"
细心的同学会发现,在实现的C调用shell的函数代码中,定义的缓冲区宏大小仅仅64字节,这就要求我们满足一个约定,所有的业务逻辑,都由强大的shell来完成,最终shell的输出要求非常简单,不是0就是1,用以gtest的断言。
运行结果如下:
$ ./test.exe
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from Demo
[ RUN ] Demo.Test1
[ OK ] Demo.Test1 (60 ms)
[----------] 1 test from Demo (61 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (62 ms total)
[ PASSED ] 1 test.
gtest采用xml来记录批量跑用例的结果,只需要在执行命令后面加上--gtest_output=xml:d:\a.xml即可。生成的xml报告文件内容示例:
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="1" failures="0" disabled="0" errors="0" timestamp="2017-10-28T19:08:51" time="0.058" name="AllTests">
<testsuite name="Demo" tests="1" failures="0" disabled="0" errors="0" time="0.058">
<testcase name="Test1" status="run" time="0.057" classname="Demo" />
</testsuite>
</testsuites>
gtest提供--gtest_repeat=[COUNT]参数来实现重复跑case的能力。
【END】