目录
一.介绍
GTest 是一个跨平台的 C++单元测试框架,由 google 公司发布。gtest 是为了在不同平台上为编写 C++单元测试而生成的。它提供了丰富的断言、致命和非致命判断、参数化等等测试所需的宏,以及全局测试,单元测试组件。
二.安装
使用命令安装:
sudo apt-get install libgtest-dev
三.头文件包含
#include <gtest/gtest.h>
四.框架初始化接口
testing::InitGoogleTest(&argc, argv);
五.调用测试样例
RUN_ALL_TESTS();
返回0则所有测试通过,返回非0则存在测试未通过。
六.TEST 宏
//这里不需要双引号,且同测试下多个测试样例不能同名
TEST(测试名称, 测试样例名称)
TEST_F(test_fixture,test_name)
- TEST:主要用来创建一个简单测试, 它定义了一个测试函数, 在这个函数中可以使用任何 C++代码并且使用框架提供的断言进行检查;
- TEST_F:主要用来进行多样测试,适用于多个测试场景如果需要相同的数据配置的情况, 即相同的数据测不同的行为。
七.断言宏
GTest 中的断言的宏可以分为两类:
- ASSERT_系列:如果当前点检测失败则退出当前函数
- EXPECT_系列:如果当前点检测失败则继续往下执行
// bool 值检查
ASSERT_TRUE(参数),期待结果是 true
ASSERT_FALSE(参数),期待结果是 false
//数值型数据检查
ASSERT_EQ(参数 1,参数 2),传入的是需要比较的两个数 equal
ASSERT_NE(参数 1,参数 2),not equal,不等于才返回 true
ASSERT_LT(参数 1,参数 2),less than,小于才返回 true
ASSERT_GT(参数 1,参数 2),greater than,大于才返回 true
ASSERT_LE(参数 1,参数 2),less equal,小于等于才返回 true
ASSERT_GE(参数 1,参数 2),greater equal,大于等于才返回 true
EXPECT_系列也是一样的,只是前缀改成“EXPECT_”即可。
八.样例
#include "gtest/gtest.h"
TEST(测试1, 加法测试)
{
ASSERT_EQ(10 + 10, 20);
std::cout << "测试通过" << std::endl;
ASSERT_LT(20 + 1, 30);
}
TEST(测试2, 字符串比较测试)
{
EXPECT_EQ("Hello", "hello");
std::cout << "测试未通过" << std::endl;
EXPECT_EQ("hello", "hello");
}
//gtest还有事件机制,所以有空再学
int main(int argc, char* argv[])
{
//初始化框架
testing::InitGoogleTest(&argc, argv);
//启动测试用例
return RUN_ALL_TESTS();
}
makefile文件:
main:main.cc
g++ -o $@ $^ -std=c++17 -lgtest
.PHONY:clean
clean:
rm -rf main
这里注意需要链接gtest的动态库。
程序运行后输出结果:
在它的输出中,我们可以看到哪些测试通过了,哪些测试没有通过,在最后几行还做了总结,我们就可以根据这个单元测试结果得知程序是否正确。
九.事件机制
GTest 中的事件机制是指在测试前和测试后提供给用户自行添加操作的机制,而且该机制也可以让同一测试套件下的测试用例共享数据。GTest 框架中事件的结构层次:
- 测试程序:一个测试程序只有一个 main 函数,也可以说是一个可执行程序是一个测试程序。该级别的事件机制是在程序的开始和结束执行;
- 测试套件:代表一个测试用例的集合体,该级别的事件机制是在整体的测试案例开始和结束执行;
- 测试用例:该级别的事件机制是在每个测试用例开始和结束都执行事件机制的最大好处就是能够为我们各个测试用例提前准备好测试环境,并在测试完毕后用于销毁环境,这样有个好处就是如果我们有一端代码需要进行多种不同方法的测试,则可以通过测试机制在每个测试用例进行之前初始化测试环境和数据,并在测试完毕后清理测试造成的影响。
GTest 提供了三种常见的的事件:
全局事件:
针对整个测试程序。实现全局的事件机制,需要创建一个自己的类,然后继承 testing::Environment 类,然后分别实现成员函数 SetUp 和 TearDown,同时在 main函数内进行调用 testing::AddGlobalTestEnvironment(new MyEnvironment);函数添加全局的事件机制。
以下使用一个代码说明,在代码中我们使用C++的stl库中的哈希来作为测试对象,测试查找和size,并且使用全局事件在测试开启前插入一部分数据来方便测试,在测试结束后清空hash中的键值对,从而不影响下一次测试,就是说下一次测试也会用到这个哈希。
#include <gtest/gtest.h>
#include <unordered_map>
#include <string>
std::unordered_map<std::string, std::string> hash;
class hashTest : public testing::Environment
{
public:
void SetUp()
{
std::cout << "测试前:提前准备数据!!\n";
hash["Hello"] = "你好";
hash["hello"] = "你好";
hash["雷吼"] = "你好";
}
void TearDown()
{
std::cout << "测试结束后:清理数据!!\n";
hash.clear();
}
};
TEST(hash_case_test, find_test)
{
auto it = hash.find("hello");
ASSERT_NE(it, hash.end());
}
TEST(hash_case_test, size_test)
{
ASSERT_GT(hash.size(), 0);
}
int main(int argc, char* argv[])
{
testing::AddGlobalTestEnvironment(new hashTest);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
运行结果:
TestSuite 事件:
针对一个个测试套件。测试套件的事件机制我们同样需要去创建一个类,继承自testing::Test,实现两个静态函数 SetUpTestCase 和 TearDownTestCase,测试套件的事件机制不需要像全局事件机制一样在 main 注册,而是需要将我们平时使用的TEST 宏改为 TEST_F 宏。
- SetUpTestCase() 函数是在测试套件第一个测试用例开始前执行;
- TearDownTestCase() 函数是在测试套件最后一个测试用例结束后执行;
- 需要注意 TEST_F 的第一个参数是我们创建的类名,也就是当前测试套件的名称,这样在 TEST_F 宏的测试套件中就可以访问类中的成员了。
代码样例同样采用对hash的插入,查找,size比较来进行测试。
#include <iostream>
#include <gtest/gtest.h>
class HashTestEnv1 : public testing::Test
{
public:
static void SetUpTestCase()
{
std::cout << "环境1第一个TEST之前调用\n";
}
static void TearDownTestCase()
{
std::cout << "环境1最后一个TEST之后调用\n";
}
public:
std::unordered_map<std::string, std::string> dict;
};
TEST_F(HashTestEnv1, insert_test)
{
std::cout << "环境1,中间insert测试\n";
dict["Hello"] = "你好";
dict["hello"] = "你好";
dict["雷吼"] = "你好";
auto it = dict.find("hello");
ASSERT_NE(it, dict.end());
}
TEST_F(HashTestEnv1, sizeof)
{
std::cout << "环境 1,中间 size 测试\n";
ASSERT_GT(dict.size(), 0);
}
int main(int argc, char* argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
运行结果:
能够看到在上例中,有一个好处,就是将数据与测试结合到同一个测试环境类中了,这样与外界的耦合度更低,代码也更清晰。
但是同样的,我们发现在两个测试用例中第二个测试用例失败了,这是为什么呢?这就涉及到了 TestCase 事件的机制。
TestCase 事件:
针对一个个测试用例。测试用例的事件机制的创建和测试套件的基本一样,不同地方在于测试用例实现的两个函数分别是 SetUp 和 TearDown, 这两个函数也不是静态函数。
- SetUp()函数是在一个测试用例的开始前执行
- TearDown()函数是在一个测试用例的结束后执行
也就是说,在 TestSuite/TestCase 事件中,每个测试用例,虽然它们同用同一个事件环境类,可以访问其中的资源,但是本质上每个测试用例的环境都是独立的,这样我们就不用担心不同的测试用例之间会有数据上的影响了,保证所有的测试用例都使用相同的测试环境进行测试。
代码逻辑同样同上,只是又使用了TestCase事件机制。
#include <iostream>
#include <gtest/gtest.h>
class HashTestEnv2 : public testing::Test
{
public:
static void SetUpTestCase()
{
std::cout << "环境 2 第一个 TEST 之前被调用,进行总体环境配置\n";
}
static void TearDownTestCase()
{
std::cout << "环境 2 最后一个 TEST 之后被调用,进行总体环境清理\n";
}
void SetUp()
{
std::cout << "环境 2 测试前:提前准备数据!!\n";
dict.insert(std::make_pair("bye", "再见"));
dict.insert(std::make_pair("see you", "再见"));
}
void TearDown()
{
std::cout << "环境 2 测试结束后:清理数据!!\n";
dict.clear();
}
public:
std::unordered_map<std::string, std::string> dict;
};
TEST_F(HashTestEnv2, insert_test)
{
std::cout << "环境 2,中间测试\n";
dict.insert(std::make_pair("hello", "你好"));
ASSERT_EQ(dict.size(), 3);
}
TEST_F(HashTestEnv2, size_test)
{
std::cout << "环境 2,中间 size 测试\n";
auto it = dict.find("hello");
ASSERT_EQ(it, dict.end());
ASSERT_EQ(dict.size(), 2);
}
int main(int argc, char* argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
运行结果:
十.总结
以上就是gtest的安装,使用和机制讲解。本篇文章方便我们进行日常开发的参考。看到这里,我和大家一样有很多疑问,那就是它是怎么实现的,为什么TEST函数中两个参数不用引号括起来字符串,为什么测试套件中可以直接访问类成员而不用创建对象,我想是因为它源码这里做了公有继承的方式吧,这些疑问都只能到源码中去查看。