文章目录
- 一、介绍
- 1.1 介绍
- 1.2 教程
- 二、使用
- 2.1 基本使用
- 2.1.1 安装GTest (下载和编译)
- 2.1.2 编写测试
- 2.1.3 运行测试
- 2.1.4 高级特性
- 2.1.5 调试和分析
- 2.2 源码自带测试用例
- 2.3 TEST 使用
- 2.3.1 TestCase的介绍
- 2.3.2 TEST宏
- demo1
- demo2
- 2.3.3 TEST_F宏
- 2.3.4 TEST 和 TEST_IF 区别
- 2.4 EXPECT_*和ASSERT_*的宏介绍
- 2.4.1 gtest之断言
- 2.4.2 Boolean断言类型
- 2.4.3 二元值断言类型
- 2.4.4 字符串断言类型
- 三、参考资料
- 四、其他内容
- 4.1 gtest 和 C++ 版本
一、介绍
1.1 介绍
Google Test(通常简称GTest)是Google开发的一个用于C++的单元测试框架,它可以帮助你轻松地编写和运行测试用例,确保代码的质量和稳定性。
- 最大好处:实现自动化单元测试
1.2 教程
- 官网:https://google.github.io/googletest/
- 源码:https://github.com/google/googletest
- 参考:gtest教程(记录小白从0学习gtest的过程)
- GoogleTest测试框架介绍(二)
- C++ 的测试框架之使用 gtest 编写单元测试
二、使用
- 编译库
- 编写工程代码
- 编写测试用例代码
- 工程:
- 工程源码:库 / 程序
- samples:示例程序,演示如何使用库的基本功能。
- test:gtest
2.1 基本使用
2.1.1 安装GTest (下载和编译)
1.下载源码:
访问Google Test GitHub仓库,下载或克隆源码。
2.编译GTest:
GTest可以通过CMake等构建工具来构建。以CMake为例,你需要创建一个构建目录,然后运行CMake和Make工具。
mkdir build
cd build
cmake ..
make
3.安装GTest:
将GTest的库文件和头文件复制到你的项目中,或者在你的构建系统中链接GTest库。
2.1.2 编写测试
1.包含GTest头文件:
在你的测试文件中,需要包含GTest的头文件。
#include "gtest/gtest.h"
2.定义测试用例和测试函数:
测试用例(TEST_F)通常对应于一组相关的测试函数,而测试函数(TEST)则是具体执行的测试逻辑。
TEST(FactorialTest, PositiveNumbers) { // 单独的测试函数
EXPECT_EQ(1, Factorial(0));
EXPECT_EQ(1, Factorial(1));
EXPECT_EQ(2, Factorial(2));
EXPECT_EQ(6, Factorial(3));
}
class MyMathTest : public ::testing::Test {
protected:
void SetUp() override {
// 初始化工作
}
};
TEST_F(MyMathTest, TestAddition) {
EXPECT_EQ(5, Add(2, 3));
}
3.断言:
使用GTest提供的断言来检查函数的行为是否符合预期。例如,EXPECT_EQ用于比较两个值是否相等。
4.测试驱动:
在main函数中调用::testing::InitGoogleTest和RUN_ALL_TESTS来初始化GTest并运行所有的测试。
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
2.1.3 运行测试
1.编译测试:
使用你的构建系统(如Makefile或CMakeLists.txt)来编译你的测试代码。
2.执行测试:
运行生成的可执行文件来执行测试。GTest会输出测试的结果,包括通过、失败或跳过的测试。
2.1.4 高级特性
- 参数化测试:使用INSTANTIATE_TEST_SUITE_P来创建一系列使用不同参数的测试用例。
- 死亡测试:使用ASSERT_DEATH或EXPECT_DEATH来检查代码是否会在特定条件下崩溃。
- Google Mock:GTest的一部分,用于创建和使用mock对象来进行更复杂的测试。
2.1.5 调试和分析
- 测试过滤:在运行测试时,可以使用–gtest_filter参数来指定运行哪些测试。
- 测试日志:使用–gtest_output=xml:test_results.xml等选项来生成测试报告。
2.2 源码自带测试用例
- 参考:https://gitcode.csdn.net/65acab6ab8e5f01e1e451947.html
我们还没有添加我们自己的源码,和针对源码的测试用例,但是谷歌已经写好了一些例子,可以先体验下,放在如下路径:/xxx/googletest-1.15.0/googletest
测试:
-
修改 CMakeLists.txt
# 这里 OFF 改成 ON option(gtest_build_samples "Build gtest's sample programs." OFF)
相关编译内容:
if (gtest_build_samples) cxx_executable(sample1_unittest samples gtest_main samples/sample1.cc) cxx_executable(sample2_unittest samples gtest_main samples/sample2.cc) cxx_executable(sample3_unittest samples gtest_main) cxx_executable(sample4_unittest samples gtest_main samples/sample4.cc) cxx_executable(sample5_unittest samples gtest_main samples/sample1.cc) cxx_executable(sample6_unittest samples gtest_main) cxx_executable(sample7_unittest samples gtest_main) cxx_executable(sample8_unittest samples gtest_main) cxx_executable(sample9_unittest samples gtest) cxx_executable(sample10_unittest samples gtest) endif()
-
编译:
mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=pwd/result -DGOOGLETEST_VERSION=1.5.0 .. make -j4 # 单个文件编译 # g++ ../src/gtest_main.cc sample1.cc sample1_unittest.cc -o test -lgtest -lgmock -lpthread -std=c++11
-
运行:
./sample1_unittest
输出:
-
理解:
- gtest_main.cc :测试主程序入口,不是我们待测源码的主程序入口。
情况一:整个工程有两个main函数,一个是测试的main,一个可能是待测源码的main,两个包含main的文件不能同时编译,因为一个执行程序只能有一个入口。(可以n个文件n个main函数)
情况二:一个main函数通过宏定义区分启动 - sample1.cc:待测源码,就是测试对象,我们就是要对个源码进行白盒测试。
- sample1_unittest.cc:测试用例,里面就是我们针对源码写的测试用例脚本。
- gtest_main.cc :测试主程序入口,不是我们待测源码的主程序入口。
-
工程:
- 工程源码:库 / 程序
- samples:示例程序,演示如何使用库的基本功能。
- test:gtest
-
遗留问题:编译没看懂—>再看
2.3 TEST 使用
- 转自:Gtest入门2 Gtest之TEST宏的用法
2.3.1 TestCase的介绍
Gtest提供了若干个case方法进行测试不同的用例。主要常见的有TEST/TEST_F及TEST_P宏的使用。
在每个TestCase中可以通过断言提供的方法进行控制检查程序的预期走向是否是期望的结果,从而以此来判定程序的正确性。
在同一份TestCase中不能同时出现TEST和TEST_F两者进行混用;
其次TEST_F比TEST强的地方是会通过继承::testing::Test生成一个新类,而且这是必须的。
在新类中可以通过void SetUp();和void TearDown();进行创建和清除相关的资源数据;
2.3.2 TEST宏
TEST宏的作用是创建一个简单测试,它定义了一个测试函数,在这个函数里可以使用任何C++代码并使用提供的断言来进行检查。
TEST语法定义:
TEST(test_case_name, test_name)
- test_case_name第一个参数是测试用例名,通常是取测试函数名或者测试类名
- test_name 第二个参数是测试名这个随便取,但最好取有意义的名称
- 当测试完成后显示的测试结果将以"测试用例名.测试名"的形式给出
demo1
// test.cpp
// g++ -std=c++14 ../../src/gtest_main.cc test.cpp -o test -I../../include -L ../lib -lgtest -lpthread
#include <iostream>
#include <memory>
#include <gtest/gtest.h>
using namespace std;
class Base {
public:
Base(std::string name):m_name{name} {
std::cout << "name: " << m_name << std::endl;
}
std::string getName() {
return m_name;
}
~Base() {
std::cout << "destory base" << std::endl;
}
private:
std::string m_name;
};
void getNameFunc(std::shared_ptr<Base> base) {
std::cout << __func__ << " : usercount: " << base.use_count() << std::endl;
std::cout << __func__ << " : name: " << base->getName() << std::endl;
// EXPECT_EQ(2, base.use_count());
}
TEST(Base, createInstance) {
std::unique_ptr<Base> instance = make_unique<Base>("SvenBaseUnique");
// 测试创建的instance实例是否不为nullptr
EXPECT_NE(instance, nullptr);
instance.reset();
// 测试instance实例是否为nullptr
EXPECT_EQ(instance, nullptr);
}
TEST(Base, getName) {
std::unique_ptr<Base> instance = make_unique<Base>("BaseUnique");
EXPECT_NE(instance, nullptr);
auto name = instance->getName();
// 测试获取的name值是否和被给的值相等
EXPECT_STREQ(name.c_str(), "BaseUnique");
instance.reset();
EXPECT_EQ(instance, nullptr);
}
TEST(Base, shared_ptr) {
std::shared_ptr<Base> instance = std::make_shared<Base>("BaseShared");
EXPECT_NE(instance, nullptr);
std::cout << "shared_ptr.use_count: " << instance.use_count() << std::endl;
// 测试instance引用次数是否为1
EXPECT_EQ(1, instance.use_count());
getNameFunc(instance);
EXPECT_EQ(1, instance.use_count());
}
TEST(Base, unique_ptr) {
std::unique_ptr<Base> instance = make_unique<Base>("BaseUnique");
EXPECT_NE(instance, nullptr);
getNameFunc(std::move(instance));
EXPECT_EQ(instance, nullptr);
}
demo2
// test_2.cc
// g++ -std=c++14 test_2.cc -o test_2 -I../../include -L ../lib -lgtest -lpthread
#include <gtest/gtest.h>
#include <vector>
#include <string>
#include <cmath>
// 示例函数,用于测试
int Add(int a, int b) {
return a + b;
}
std::vector<int> GetPrimesUnder100() {
std::vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97};
return primes;
}
class StringClass {
public:
std::string Reverse(const std::string& s) {
return std::string(s.rbegin(), s.rend());
}
};
// 测试类
class MyTest : public ::testing::Test {
protected:
StringClass stringObj;
};
// 测试用例:测试 Add 函数
TEST(AdditionTest, HandlesZeroInput) {
EXPECT_EQ(Add(0, 0), 0);
ASSERT_EQ(Add(0, 1), 1);
// EXPECT_EQ(Add(0, 0), 30); // error test
// ASSERT_EQ(Add(0, 1), 30);
// ASSERT_EQ(Add(1, 1), 2);
}
TEST(AdditionTest, HandlesPositiveInput) {
EXPECT_EQ(Add(2, 3), 5);
ASSERT_EQ(Add(-1, 1), 0);
}
TEST(AdditionTest, HandlesNegativeInput) {
EXPECT_EQ(Add(-1, -1), -2);
ASSERT_EQ(Add(-2, 3), 1);
}
// 测试用例:测试 GetPrimesUnder100 函数
TEST(PrimeTest, ReturnsCorrectPrimes) {
auto primes = GetPrimesUnder100();
ASSERT_EQ(primes.size(), 25);
EXPECT_EQ(primes[0], 2);
EXPECT_EQ(primes[24], 97);
}
// 测试用例:测试 StringClass 的 Reverse 方法
TEST_F(MyTest, ReversesString) {
std::string original = "hello";
std::string reversed = stringObj.Reverse(original);
EXPECT_EQ(reversed, "olleh");
}
TEST_F(MyTest, ReversesEmptyString) {
std::string original = "";
std::string reversed = stringObj.Reverse(original);
EXPECT_EQ(reversed, "");
}
TEST_F(MyTest, ReversesSingleCharacterString) {
std::string original = "a";
std::string reversed = stringObj.Reverse(original);
EXPECT_EQ(reversed, "a");
}
// 测试用例:测试浮点数比较
TEST(FloatTest, ComparesFloats) {
float a = 1.0f + 1e-5f;
float b = 1.0f;
EXPECT_NEAR(a, b, 1e-4f);
}
// 测试用例:测试布尔值
TEST(BooleanTest, ChecksTrue) {
bool condition = true;
EXPECT_TRUE(condition);
ASSERT_TRUE(condition);
}
TEST(BooleanTest, ChecksFalse) {
bool condition = false;
EXPECT_FALSE(condition);
ASSERT_FALSE(condition);
}
// 测试用例:测试异常
TEST(ExceptionTest, ThrowsException) {
ASSERT_THROW(throw std::runtime_error("test exception"), std::runtime_error);
}
// 测试用例:测试子测试
TEST(SubtestTest, Subtest1) {
SUCCEED() << "This is a subtest.";
EXPECT_EQ(1, 1);
}
TEST(SubtestTest, Subtest2) {
SUCCEED() << "This is another subtest.";
EXPECT_EQ(2, 2);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
2.3.3 TEST_F宏
TEST_F主要是进行多样测试,就是多种不同情况的测试TestCase中都会使用相同一份的测试数据的时候将会才用它。
即用相同的数据测试不同的行为,如果采用TEST宏进行测试那么将会为不同的测试case创建一份数据。TEST_F宏将会共用一份避免重复拷贝共具灵活性。
语法定义为:
TEST_F(test_case_name, test_name);
- test_case_name第一个参数是测试用例名,必须取类名。这个和TEST宏不同
- test_name 第二个参数是测试名这个随便取,但最好取有意义的名称
- 使用TEST_F时必须继承::testing::Test类。并且该类提供了两个接口void SetUp(); void TearDown();
void SetUp()函数,为测试准备对象. void TearDown()函数 为测试后销毁对象资源
2.3.4 TEST 和 TEST_IF 区别
在 Google Test 中,TEST
和 TEST_F
是用来定义测试用例的两个不同的宏。它们之间的主要区别在于是否需要测试夹具(test fixture):
-
TEST(SuiteName, TestName)
TEST
宏用于定义不需要特殊初始化或清理的简单测试用例。- 它不接受任何参数,因此也不需要测试夹具类。
- 测试用例函数是自动实例化的,并且每个测试用例都是独立的,不共享任何设置。
- 适用于快速、独立的测试,不需要重复的初始化逻辑。
示例代码:
TEST(MultiplicationTest, HandlesZero) { EXPECT_EQ(0 * 5, 0); }
-
TEST_F(TestFixture, TestName)
TEST_F
宏用于定义需要使用测试夹具的测试用例。- 测试夹具是一个从
::testing::Test
派生的类,你可以在里面定义公共和受保护的成员,这些成员在每个测试用例中都可以访问。 - 测试夹具类允许你在
SetUp
方法中编写初始化代码,这些代码会在每个测试用例运行之前执行,而在TearDown
方法中编写清理代码,这些代码会在每个测试用例运行之后执行。 - 适用于需要共享初始化逻辑和成员数据的测试用例。
示例代码:
class MultiplicationTest : public ::testing::Test { protected: int result; void SetUp() override { result = 0; } void TearDown() override { // 清理工作,如果需要的话 } }; TEST_F(MultiplicationTest, HandlesZero) { result = 0 * 5; EXPECT_EQ(result, 0); }
除了 TEST
和 TEST_F
,Google Test 还有一个宏 TEST_P
,它用于定义参数化测试。参数化测试允许你用不同的参数多次运行同一个测试用例。
关于 TEST_IF
宏,实际上 Google Test 标准库中并没有这个宏。可能你指的是 TEST_P
或者某个特定版本的 Google Test 中的宏,或者是第三方扩展。通常情况下,TEST_P
用于参数化测试,允许你为测试用例提供参数,并且可以结合 INSTANTIATE_TEST_CASE_P
宏来实例化多个测试用例。
如果你需要更具体的信息或者有特定的使用场景,请提供更多的上下文,我可以给出更准确的答案。
2.4 EXPECT_*和ASSERT_*的宏介绍
- 转自:Gtest入门2 Gtest之TEST宏的用法
2.4.1 gtest之断言
要测试一个类或函数,我们需要对其行为做出断言。当一个断言失败时,Google Test会在屏幕上输出该代码所在的源文件及其所在的位置行号,以及错误信息。也可以在编写断言时,提供一个自定义的错误信息,这个信息在失败时会被附加在Google Test的错误信息之后。
断言常常成对出现,它们都测试同一个类或者函数,但对当前功能有着不同的效果。
- ASSERT_*版本的断言失败时会产生致命失败,并结束当前函数。
- EXPECT_*版本的断言产生非致命失败,而不会中止当前函数。
通常更推荐使用EXPECT_*断言,因为它们运行一个测试中可以有不止一个的错误被报告出来。但如果在编写断言如果失败,就没有必要继续往下执行的测试时,你应该使用ASSERT_*断言。 因为失败的ASSERT_*断言会立刻从当前的函数返回,可能会跳过其后的一些的清洁代码,这样也许会导致空间泄漏。
gtest中断言的宏可以分为两类:一类是ASSERT宏,另一类就是EXPECT宏了。
1、ASSERT_*系列:如果当前点检测失败则退出当前函数
2、EXPECT_*系列:如果当前点检测失败则继续往下执行
2.4.2 Boolean断言类型
2.4.3 二元值断言类型
比较两个值的大小。
2.4.4 字符串断言类型
比较两个字符串。
三、参考资料
-
玩转C++单元测试之快速上手gtest
-
Google Test(GTEST)使用入门(1)- 下载编译安装执行
四、其他内容
4.1 gtest 和 C++ 版本
Google Test(GTest)对不同版本的 C++ 标准的支持有所变化。下面是 Google Test 对 C++ 标准版本支持的大致时间线和相关信息。
Google Test 对 C++ 标准的支持
-
GTest 1.8.1(发布于 2015 年 7 月 29 日):
- 支持 C++98、C++03、C++11、C++14。
- 从 GTest 1.8.1 开始,GTest 支持 C++14,并且开始逐渐移除对旧版本 C++ 标准的支持。
-
GTest 1.10.0(发布于 2019 年 10 月 28 日):
- 支持 C++11、C++14、C++17。
- GTest 1.10.0 版本开始正式移除对 C++98 和 C++03 的支持。
-
GTest 1.11.0(发布于 2021 年 1 月 25 日):
- 支持 C++11、C++14、C++17。
- GTest 1.11.0 版本继续支持 C++11、C++14 和 C++17。
-
GTest 1.12.1(发布于 2022 年 8 月 17 日):
- 支持 C++11、C++14、C++17、C++20。
- GTest 1.12.1 版本增加了对 C++20 的支持。
-
GTest 1.13.0(发布于 2023 年 11 月 2 日):
- 支持 C++14、C++17、C++20。
- GTest 1.13.0 版本移除了对 C++11 的支持,现在仅支持 C++14 及以上版本。
总结
-
最新版本(截至 2024 年 7 月 25 日):
- GTest 1.13.0 支持 C++14、C++17 和 C++20。
-
历史版本:
- GTest 1.12.1 支持 C++11、C++14、C++17 和 C++20。
- GTest 1.11.0 支持 C++11、C++14 和 C++17。
- GTest 1.10.0 支持 C++11、C++14 和 C++17。
- GTest 1.8.1 支持 C++98、C++03、C++11 和 C++14。
建议
-
使用最新版本:
- 如果您的项目可以使用较新的 C++ 版本,建议使用 GTest 1.13.0 或更高版本,以获得更好的特性和支持。
- 对于 C++14 及以上版本的支持,您可以使用 GTest 1.13.0。
-
使用较旧版本:
- 如果您的项目受限于旧版本的 C++,您可以考虑使用相应的 GTest 版本。例如,如果您的项目使用 C++11,您可以考虑使用 GTest 1.12.1 或更低版本。
示例编译命令
如果您使用的是 GTest 1.13.0 或更高版本,并且您的编译器支持 C++14 或更高版本,您可以使用以下编译命令:
g++ -std=c++14 ../../src/gtest_main.cc test.cpp -o test -I../../include -L ../lib -lgtest -lpthread
请确保您的编译器支持所需的 C++ 标准版本。如果您的编译器不支持 C++14 或更高版本,您可能需要升级编译器或考虑使用较低版本的 GTest。
如果您需要使用 C++11,您可以使用 GTest 1.12.1 或更低版本,并相应地更新编译命令中的 C++ 标准版本:
g++ -std=c++11 ../../src/gtest_main.cc test.cpp -o test -I../../include -L ../lib -lgtest -lpthread
请根据您的具体需求调整编译命令中的 C++ 标准版本。