gtest使用方法 - artinfo1982/demos GitHub Wiki

gtest简介

gtest是google开发的一套C++单元测试框架,可以跨平台运行。
gtest的能力绝不仅仅局限于C++单元测试,其实它是一个完备的测试框架,基于gtest,可以完成包括性能、可靠性等多种复杂测试。

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的基本用法

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

可见,失败的用例标记了出来,没有影响后续用例的执行。
下面我们就几个比较重要的知识点进行讲解:

1)namespace testing

testing是一个名字空间,定义在gtest.h中,里面包含了一些类的定义,摘抄如下:

namespace testing
{
        ......
        class Test;
        class TestCase;
        class TestInfo;
        class UnitTest;
        ......
}

2)TEST宏和TEST_F宏的区别

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无法办到的。

3)多种断言

gtest断言的介绍在这个文章里有详细的描述,此处引用:
http://blog.csdn.net/acaiwlj/article/details/49329477

gtest的高级用法

gtest是一款单元测试框架,但它不仅仅可以做单元测试,还可以做复杂的比如功能、性能、可靠性等测试,可以用C调用shell来完成,下面分步说明。

1)用例设计思路

用例中C调用shell脚本,外部由gtest框架统一进行用例调度

2)用例实现举例

将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.

3)批量跑用例时的结果报表

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>

4)重复跑用例

gtest提供--gtest_repeat=[COUNT]参数来实现重复跑case的能力。
【END】

⚠️ **GitHub.com Fallback** ⚠️