笔者结合自己的项目实战经验介绍Google Test的集成方法和基本使用教程,可直接将本文中的方法应用到自己的C++软件工程中,本文中的示例代码也可直接编译运行,建议在学习的过程中直接运行示例代码,不会占用你太多时间的。读完本文后,对Google Test会有一个框架型的认知,后续笔者会结合在实际工程中遇到的问题继续对Google Test的高级使用进行讲解。
目前C++单元测试有gtest
、catch2
、cpputest
等框架,对比其他C++单元测试框架,gtest的使用人数最多,而且资料也比较完善,很多使用方法可以通过Google搜索到。而且gtest被广泛应用在全球很多C++软件项目中(嵌入式领域、航天飞行器领域、自动驾驶领域、金融领域、服务器领域等等),保障了几十亿量级C++代码的可靠性,足矣说明其易用性和可靠性。github排名前三的C++单元测试框架:
单元测试代码是和产品代码紧密相关的,而且单元测试代码要随着产品代码的演变而变化,所以单元测试代码需要和产品代码放在同一个仓库中。可以在产品代码的根目录下,创建UnitTest文件夹,将单元测试代码放到该路径下。目录结构:
|-- 产品代码根目录
|-- UnitTest # 产品代码的单元测试存放在该路径下
|-- Fixture # 存放测试夹具代码(下面的教程中会讲解测试夹具)
|-- MockInterface # 存放mock代码(下面的教程中会讲解mock)
|-- UnitTest.cpp # 单元测试用例代码存放在该文件中
|-- CMakeLists.txt # 单元测试代码的cmake编译文件
cmake_minimum_required(VERSION 3.14)
project(my_project)
# GoogleTest requires at least C++11
set(CMAKE_CXX_STANDARD 11)
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
这些操作会先从GitHub上将Google Test框架的源码下载到当前的编译路径下,然后和自己的单元测试代码一起编译。
609281088cfefc76f9d0ce82e1ff6c30cc3591e5
是Google Test的commit hash,如果有需要,自己可以更新到最新的提交版本。
cmake文件
cmake_minimum_required(VERSION 3.14)
project(my_project)
# GoogleTest requires at least C++11
set(CMAKE_CXX_STANDARD 11)
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(
my_test
UnitTest.cpp
)
target_link_libraries(
my_test
gtest_main
)
include(GoogleTest)
gtest_discover_tests(my_test)
UnitTest.cpp文件
#include
// Demonstrate some basic assertions.
TEST(HelloTest, BasicAssertions) {
// Expect two strings not to be equal.
EXPECT_STRNE("hello", "world");
// Expect equality.
EXPECT_EQ(7 * 6, 42);
}
直接运行cmake编译,然后运行单元测试:./my_test
,除非用户需要自己做一些单元测试前的初始化操作,否则一般情况下不需要用户自己去写main函数,直接使用Google Test提供的main函数即可。
Google Test中使用了Test Suite
和Test
来命名测试。
一个Test Suite
可包含多个Test
,同一套Test Suite
具有相同的Suite
命名,不同的Test有各自不同的命名,例:
TEST(TestSuiteName_1, TestName_1) {
... test body ...
}
TEST(TestSuiteName_1, TestName_2) {
... test body ...
}
TEST(TestSuiteName_2, TestName_1) {
... test body ...
}
ASSERT_*
类型的断言宏,如果断言失败就直接终止程序的运行。如果在使用该宏前有动态内存分配的操作,该宏可能会导致程序终止运行,继而出现内存泄漏现象的,所以使用该断言宏时,一定注意不要内存泄漏
EXPECT_*
类型的断言宏,如果断言失败不会终止程序的运行,而是继续执行下面的代码。一般单元测试中会使用该宏,除非是一些无法接受的程序运行结果或者程序运行结果会导致下面的测试无法进行的情况才会使用ASSERT_*
当多个测试中需要使用相同的数据内容,这种情况下可以使用test fixture
,测试夹具允许用户在不同测试用例中使用相同的数据配置对象。
使用方法:
::testing::Test
写一个派生类,在protected中重写SetUp()
和TearDown()
函数,在SetUp()
函数中初始化数据,在TearDown()
中做清理工作TEST_F
,该宏是专门为测试夹具的使用准备的,在测试用例开始时自动执行SetUp函数,用例结束时自动执行TearDown函数TEST_F
的使用
TEST_F(TestFixtureName, TestName) {
... test body ...
}
代码范例
// 需要测试的代码
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;
...
};
// 自己定义派生类QueueTest
class QueueTest : public ::testing::Test {
protected:
void SetUp() override {
q1_.Enqueue(1);
q2_.Enqueue(2);
q2_.Enqueue(3);
}
// void TearDown() override {}
Queue<int> q0_;
Queue<int> q1_;
Queue<int> q2_;
};
// 使用TEST_F测试代码
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;
}
以上两个测试互不干扰,这两个测试用例使用相同的数据内容,但QueueTest
对象是不同的。每个用例执行开始时会创建新的QueueTest
对象,用例执行完毕后QueueTest
对象自动被销毁
在单元测试的过程中,有些地方会调用第三方接口或者使用第三方系统,我们不需要去为第三方的代码做单元测试,所以在做单元测试的过程中不应该依赖第三方接口或系统。
在不依赖第三方代码的情况下,为了能使自己代码的逻辑跑通,就需要对第三方接口或系统进行模拟,Google Test提供了gmock
机制用来方便地实现模拟功能。
可直接运行示例代码,mock
的详细文字说明见代码注释
#include
#include
namespace {
using ::testing::AtLeast;
using ::testing::Return;
using ::testing::Ge;
using ::testing::_;
// 用该抽象接口模拟第三方代码
class FooInterface {
public:
virtual ~FooInterface() {}
virtual void DoThis() = 0;
virtual int returnValue() = 0;
virtual int inputparam(int p) = 0;
virtual int inputparam2(int p, int q) = 0;
};
// 该类对第三方代码进行模拟打桩,通过MOCK_METHOD宏给抽象接口打桩
class MockFoo : public FooInterface {
public:
MOCK_METHOD(void, DoThis, (), (override));
MOCK_METHOD(int, returnValue, (), (override));
MOCK_METHOD(int, inputparam, (int p), (override));
MOCK_METHOD(int, inputparam2, (int p, int q), (override));
};
TEST(LeakTest, test01) {
MockFoo foo;
// EXPECT_CALL:该匹配器用来配合mock功能使用
// AtLeast(1):至少调用一次DoThis函数
// 如果不指定调用次数,EXPECT_CALL默认调用该接口一次
EXPECT_CALL(foo, DoThis()).Times(AtLeast(1));
foo.DoThis();
foo.DoThis();
}
TEST(LeakTest, test02) {
MockFoo foo;
// Times(5):要调用5次returnValue()
// WillOnce(Return(150)):调用一次返回150
// WillRepeatedly(Return(200)):后续调用全部返回200
// WillOnce(Return(100)).WillOnce(Return(150)).WillRepeatedly(Return(200)):该接口前两次返回100,后面三次返回200
EXPECT_CALL(foo, returnValue()).Times(5).WillOnce(Return(100)).WillOnce(Return(150)).WillRepeatedly(Return(200));
for (int i = 0; i < 5; i++) {
printf("time:%d, value: %d\n", i, foo.returnValue());
}
}
TEST(LeakTest, test03) {
MockFoo foo;
// inputparam(100):后续调用过程中,期望该接口的入参为100
EXPECT_CALL(foo, inputparam(100));
foo.inputparam(100);
// inputparam:后面没有跟入参,表示用户不关心入参,入参可以是任意值
EXPECT_CALL(foo, inputparam).Times(1).WillOnce(Return(50));
foo.inputparam(67);
// Ge(70):表示期望入参要大于70
EXPECT_CALL(foo, inputparam(Ge(70)));
foo.inputparam(80);
// inputparam2(50, _):用户只期待第一个入参是50,第二个入参可以是任意值(“_”可以匹配任意值)
EXPECT_CALL(foo, inputparam2(50, _));
foo.inputparam2(50, 98);
}
// 上面的例程都是只使用了单个预期,下面几个例程中会出现多预期
// 注意:google test中的预期是有粘性的
TEST(LeakTest, test04) {
MockFoo foo;
EXPECT_CALL(foo, inputparam(_));
EXPECT_CALL(foo, inputparam(100)).Times(2);
/* 预期宏是倒叙执行的,先执行EXPECT_CALL(foo, inputparam(100)).Times(2);,
然后再执行EXPECT_CALL(foo, inputparam(_)); */
// 这两条语句满足了EXPECT_CALL(foo, inputparam(100)).Times(2);
foo.inputparam(100);
foo.inputparam(100);
// 这条语句满足了EXPECT_CALL(foo, inputparam(_));
foo.inputparam(900);
/* 如果把foo.inputparam(900);换成foo.inputparam(100);就会失败,因为预期宏是有粘性的,
连续出现三个foo.inputparam(100);会触发 EXPECT_CALL(foo, inputparam(100)).Times(2);
的失败 */
}
TEST(LeakTest, test05) {
MockFoo foo;
// 如果想让预期宏顺序执行,使用如下操作即可
{
InSequence seq;
EXPECT_CALL(foo, inputparam(_));
EXPECT_CALL(foo, inputparam(100)).Times(2);
}
// 按顺序执行预期宏,第一条语句满足了EXPECT_CALL(foo, inputparam(_));条件
foo.inputparam(7800);
// 按顺序执行预期宏,后面两条语句满足了EXPECT_CALL(foo, inputparam(100)).Times(2);条件
foo.inputparam(100);
foo.inputparam(100);
}
TEST(LeakTest, test06) {
MockFoo foo;
EXPECT_CALL(foo, inputparam(_));
// 使用RetiresOnSaturation()可以消除预期宏的粘性
EXPECT_CALL(foo, inputparam(100)).Times(2).RetiresOnSaturation();
/* 因为上面已经消除了EXPECT_CALL(foo, inputparam(100)).Times(2)的粘性,
所以在前两条foo.inputparam(100);执行完成后,满足了EXPECT_CALL(foo, inputparam(100)).Times(2)
的条件,该宏就会自动结束,第三条foo.inputparam(100)则满足了EXPECT_CALL(foo, inputparam(_))的
条件 */
foo.inputparam(100);
foo.inputparam(100);
foo.inputparam(100);
}
} // namespace
cmake编译文件
cmake_minimum_required(VERSION 3.14)
project(unit_test_demo)
# GoogleTest requires at least C++11
set(CMAKE_CXX_STANDARD 11)
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/86add13493e5c881d7e4ba77fb91c1f57752b3a4.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
set(BUILD_GMOCK ON)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(
my_test
UnitTest.cpp
)
target_link_libraries(
my_test
#gtest_main
gmock_main
)
include(GoogleTest)
gtest_discover_tests(my_test)