guojh's Blog.

Google Test(gtest)写c++单元测试

字数统计: 1.4k阅读时长: 6 min
2019/03/02

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证,比如我们可以对程序中的一个文件、函数或一个类进行单元测试。单元测试是软件开发中重要环节,因此有必要在日常开发中就开始学习和使用单元测试。Google Test(简称gtest)是谷歌开源的用于c++单元测试的框架,其github主页:https://github.com/google/googletest。本文介绍单元测试框架gtest的安装和简单用法。

更新信息

  • 2019-03-02 初始版本
  • 2022-02-01 更改gtest安装方式,采用本地安装更加灵活
  • 2022-02-02 增加gtest Fixture等内容
  • 2022-03-12 增加全局SetUp/TearDown内容

1 编译和安装gtest

1
2
3
4
5
6
git clone https://github.com/google/googletest
cd googletest
mkdir -p build install
cd build
cmake -DCMAKE_INSTALL_PREFIX=../install .. # 推荐以这种本地安装的方式,而不是安装到系统默认安装路径下
make -j32 install

2 使用gtest

创建一个求平方根的新项目,项目组织结构如下:

1
2
3
4
5
.
├── CMakeLists.txt
├── sqrt.cpp
├── sqrt.h
└── sqrt_test.cpp

下面是这四个文件的具体内容:

1
2
3
4
5
6
7
8
// sqrt.h
#ifndef SQRT_H
#define SQRT_H
#include <cmath>

double squareRoot(const double a);

#endif
1
2
3
4
5
6
7
8
9
10
// sqrt.cpp
#include "sqrt.h"

// Get the Square root of a number.
double squareRoot(const double a)
{
double b = sqrt(a);
if(b != b) return -1.0;// NaN check
else return sqrt(a);
}

单元测试的测试用例要覆盖常用的输入组合、边界条件和异常,这里只进行常用和异常情况下两个测试。gtest采用一些macros来测试代码,如EXPECT_EQASSERT_EQ等。二者区别(摘自官方文档):

EXPECT_* versions generate nonfatal failures, which don’t abort the current function. Usually EXPECT_* are preferred, as they allow more than one failure to be reported in a test. However, you should use ASSERT_* if it doesn’t make sense to continue when the assertion in question fails.*

更多的macros语法可以参考gtest官方文档

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

TEST(SquareRootTest, PositiveNos) // normal cases
{
ASSERT_EQ(6, squareRoot(36.0));
ASSERT_EQ(18.0, squareRoot(324.0));
ASSERT_EQ(25.4, squareRoot(645.16));
ASSERT_EQ(0, squareRoot(0.0));
}

TEST(SquareRootTest, NegativeNos) // extreme cases
{
ASSERT_EQ(-1.0, squareRoot(-15.0));
ASSERT_EQ(-1.0, squareRoot(-0.2));
}

int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

CMakeLists.txt如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
project(test_sqrt)
cmake_minimum_required(VERSION 3.1)

set(CMAKE_CXX_STANDARD 11)

# gtest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

# test
add_library(mysqrt sqrt.cpp)
add_executable(mytest sqrt_test.cpp)
target_link_libraries(mytest mysqrt GTest::gtest GTest::gtest_main)

然后运行测试程序也很简单

1
2
3
mkdir build && cd build
cmake -DGTEST_ROOT=/Users/hui/Code/googletest/install .. # 改为你的gtest安装路径
make -j32

运行mytest可执行文件后,输出应该类似下面的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./mytest
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from SquareRootTest
[ RUN ] SquareRootTest.PositiveNos
[ OK ] SquareRootTest.PositiveNos (0 ms)
[ RUN ] SquareRootTest.NegativeNos
[ OK ] SquareRootTest.NegativeNos (0 ms)
[----------] 2 tests from SquareRootTest (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 2 tests.

可以看到两个测试都显示OK,因此最终状态显示PASSED。

3 Test Fixture

Fixture基于Setup函数或方法,完成对测试环境和状态的准备工作;基于Teardown函数或方法,完成对测试环境和状态的销毁工作。 简单地讲,Fixture作用是避免重复代码。 创建Fixture类则需要继承::testing::Test基类,测试的一般过程如下:

  • 调用构造函数
  • 调用Setup进行初始化
  • 执行测试用例,验证结果
  • 调用Teardown回到初始状态
  • 调用析构函数

一个Fixture类示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}

// void TearDown() override {}
// static void SetUpTestSuite() override {}
// static void TearDownTestSuite() override {}

Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};

这里SetUpTestSuite()TearDownTestSuite()用来初始化或销毁一些比较耗时的共享资源(如数据库等),以避免每个测试用例都初始化一次,更多用法请参考官方文档。

对应的测试代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TEST_F(QueueTest, IsEmptyInitially) {
EXPECT_EQ(q0_.size(), 0);
}

TEST_F(QueueTest, DequeueWorks) {
int* n = q0_.Dequeue();
EXPECT_EQ(n, nullptr);

n = q1_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 1);
EXPECT_EQ(q1_.size(), 0);
delete n;

n = q2_.Dequeue();
ASSERT_NE(n, nullptr);
EXPECT_EQ(*n, 2);
EXPECT_EQ(q2_.size(), 1);
delete n;
}

注意,当使用Fixture时,需要使用TEST_F而不是TEST

1
2
3
TEST_F(TestFixtureName, TestName) {
... test body ...
}

这里需要注意什么时候适合使用ctor/dtor,什么时候适合使用SetUp/TearDown,官方文档中对此也有很好的介绍:Should I use the constructor/destructor of the test fixture or SetUp()/TearDown()?

4 全局SetUp/TearDown

Fixture用于解决test suite层面的SetUp和TearDown。然而像初始化日志等操作通常全局只进行一次,我们不希望在不同Fixture的SetUp里重复进行,因此就需要environment来解决这种全局SetUp和TearDown的问题。

environment的使用比较简单,直接看例子就明白了:

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

TEST(TestSuite1, Foo) { ASSERT_EQ(6, squareRoot(36.0)); }

TEST(TestSuite2, Foo) { ASSERT_EQ(-1.0, squareRoot(-15.0)); }

class Environment : public ::testing::Environment {
public:
~Environment() override {}

void SetUp() override { std::cout << "Global SetUp called\n"; }

void TearDown() override { std::cout << "Global TearDown called\n"; }
};

int main(int argc, char **argv) {
testing::AddGlobalTestEnvironment(new Environment);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

运行mytest可执行文件后,输出应该类似下面的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ./mytest
[==========] Running 2 tests from 2 test suites.
[----------] Global test environment set-up.
Global SetUp called
[----------] 1 test from TestSuite1
[ RUN ] TestSuite1.Foo
[ OK ] TestSuite1.Foo (0 ms)
[----------] 1 test from TestSuite1 (0 ms total)

[----------] 1 test from TestSuite2
[ RUN ] TestSuite2.Foo
[ OK ] TestSuite2.Foo (0 ms)
[----------] 1 test from TestSuite2 (0 ms total)

[----------] Global test environment tear-down
Global TearDown called
[==========] 2 tests from 2 test suites ran. (0 ms total)
[ PASSED ] 2 tests.

参考资料

  • gtest官方文档:https://google.github.io/googletest/
  • https://www.srcmake.com/home/google-cpp-test-framework
  • Bo Qian C++单元测试视频:https://www.youtube.com/playlist?list=PL5jc9xFGsL8GyES7nh-1yqljjdTvIFSsh

个人理解错误的地方还请不吝赐教,转载请标明出处

原文作者:GJGJH

原文链接:https://gjgjh.github.io/gtest.html

发表日期:March 2nd 2019, 10:15:20 pm

更新日期:March 12th 2022, 4:06:39 pm

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 更新信息
  2. 2. 1 编译和安装gtest
  3. 3. 2 使用gtest
  4. 4. 3 Test Fixture
  5. 5. 4 全局SetUp/TearDown
  6. 6. 参考资料