【C++ GTest】GTest集成测试框架搭建及总结

一、前言

最近项目中需要对SDK对外接口进行集成测试,这边搭建一套GTest的集成用例框架,确保能够覆盖所有类型的接口,同时对GTest语法进行学习总结;另外i这套GTest集成用例框架内部,会再嵌套插件OpenCppCoverage / gcov,用于对集成用例覆盖率的分析。

【debug问题点】:集成测试
【环境】:Windows、Linux
【使用工具】:GTest、VS、OpenCppCoverage、g++、gcov

二、概述

Google Test (gtest) 是一个由 Google 开发的流行的 C++ 单元测试框架。它为开发者提供了一套强大的工具和功能,用于编写、组织和运行测试用例。gtest 框架支持测试驱动开发(Test-Driven Development,TDD)和行为驱动开发(Behavior-Driven Development,BDD)方法。总结下来,有几个主要功能和特点:

  • 丰富的断言
    gtest 框架提供了一组丰富的断言宏,用于验证测试结果。这些断言宏包括比较值、检查条件、抛出异常等功能,可以满足各种测试需求;
  • 灵活的测试组织
    gtest 框架允许开发者将测试用例组织成测试套件(Test Suite),并可以嵌套多个测试套件。这样可以更好地组织和管理测试用例,提高测试代码的可读性和可维护性;
  • 测试夹具支持
    gtest 框架提供了测试夹具(Test Fixture 的概念,允许开发者在多个测试用例之间共享资源或设置环境。通过派生测试夹具类并重写 SetUp() 和 TearDown() 方法,开发者可以方便地进行资源的初始化和清理操作;
  • 完善的编译及平台支持
    gtest 框架支持 C++11 及以上的 C++ 标准,支持 Linux、MacOS、Windows 等众多平台,支持 gcc5.0+、clang5.0+、MSVC2015+ 编译器,支持 Bazel 以及 cmake 构建工具;
  • 丰富的测试报告
    gtest 框架生成详细的测试报告,显示每个测试用例的运行结果,包括通过的用例和失败的用例。如果断言失败,报告会提供失败的原因和位置,方便开发者快速定位问题。

三、GTest环境配置

这边默认使用者有GTest源码,如果没有,网上很多地方可以下载。
可参考:https://www.cnblogs.com/djh5520/p/14283361.html

3.1、Windos环境

  • 解压后打开msvc文件夹,打开gtest.sln,直接编译即可。
  • 默认生成静态链接库

/msvc/gtest/debug :gtestd.lib
/msvc/gtest/release:gtest.lib

3.2、Linux环境

解压后,终端进入到make文件夹,执行make命令,生成gtest_main.a,编译完成。

3.3、用例工程配置

解压Gtest源码包后,进入到/include/gtest, 拷贝其头文件。

  • 配置项目属性——头文件
  • 配置项目属性——编译方式【与gtest链接库一致】
  • 配置项目属性——链接库路径
  • 配置项目属性——链接库

四、GTest语法

4.1、简单demo

#include <gtest/gtest.h>
int add(int a, int b) { return a + b; }
TEST(TEST_ADD, UNSIGNED_INT_VALUE) {  
  int result = add(100, 200);  
  EXPECT_EQ(result, 300);  
  result = add(200, 300);  
  EXPECT_NE(result, 400);
}
int main() {
  testing::InitGoogleTest();
  return RUN_ALL_TESTS();
}

4.2、测试宏

4.2.1、TEST 宏【TestSuiteName, TestCaseName】

【说明】:该宏定义用来测试其内部代码,其内部断言决定最终的测试结果。
【用途】:针对多个用例之间不需要进行数据共用的测试场景。

TEST(TEST_ADD, UNSIGNED_INT_VALUE) {
  bool nRet = add(100);  // 测试内容
  EXPECT_TRUE(nRet);	// 断言
}
TEST(TEST_ADD, NEGATIVE_INT_VALUE) {
  bool nRet = add(100);
  EXPECT_TRUE(nRet);
}

4.2.2、TEST_P 宏【TestSuiteName, TestCaseName】

【说明】:该宏定义用来参数化测试。
【用途】:当待测试方法的行为取决于传入的参数时,而且这些参数的不同组合有多种, 而你又不想为此写多个类似的 test case 时,可以用参数化测试。

struct TestData {
  int  a;
  int  b;
  int  result;
  char type;
};
class CalculateTest : public testing::TestWithParam<TestData> { // 确认参数类型
protected:
  void checkData(TestData data) {
    // TODO: ...
  }
};
`INSTANTIATE_TEST_SUITE_P`(TestMyClassParams,  // 确认参数范围
                         CalculateTest,
                         ::testing::Values(
                           TestData{100, 200, 300, '+'},
                           TestData{20, 5, 15, '-'}
                         ));
INSTANTIATE_TEST_SUITE_P(TestMyClassParams,
                         CalculateTest,
                         ::testing::Range(
                           TestData{100, 200, 300, '+'},
                           TestData{20, 5, 15, '-'}
                         ));
TEST_P(CalculateTest, Test) {
  TestData data;
  bool nRet = checkData(data);
  EXPECT_TRUE(nRet);
}

4.2.3、TEST_F 宏【TestSuiteName, TestCaseName】

【说明】:该宏定义用来对 TestFixtureName 类进行多样测试。
【用途】:针对多个用例之间需要进行数据共用的测试场景,用于多样测试,也有助于简化测试代码。

class Student {
public:
  Student(int id, std::string name): id_(id), name_(name) {};
  ~Student() = default;
  void SetAge(int age) { age_ = age; }
  int GetAge() const { return this->age_; }
  void SetScore(int score) { score_ = score; }
  int GetScore() const { return this->score_; }
private:
  int id_;
  std::string name_;
  int age_;
  int score_;
};
class StudentTest : public testing::Test {
protected:
  void SetUp() override {
    student = new Student(1234, "Tom");
  }
  void TearDown() override {
    delete student;
  }
  Student* student;
};
TEST_F(StudentTest, SET_AGE_TEST) {
  student->SetAge(16);
  int age = student->GetAge();
  EXPECT_EQ(age, 16);
}
TEST_F(StudentTest, SET_SCORE_TEST) {
  student->SetScore(99);
  int score = student->GetScore();
  ASSERT_EQ(score, 99);
}

4.3、事件机制

gtest 提供了多种事件机制:

  • 全局事件:所有用例执行前后;
  • TestSuite 级别:同一 TestSuite 的第一个用例前,最后一个用例执行后;
  • TestCase 级别:每个测试用例前后。

4.3.1、全局事件

要实现全局事件,必须写一个类,继承 testing::Environment 类,实现里面的 SetUp 和 TearDown 方法:

  • SetUp() 方法在所有用例执行前执行;
  • TearDown() 方法在所有用例执行后执行。
class FooEnvironment : public testing::Environment {
public:
  virtual void SetUp() {        
    std::cout << "Foo FooEnvironment SetUP" << std::endl;    
  }    
  virtual void TearDown() {        
    std::cout << "Foo FooEnvironment TearDown" << std::endl;    
  }
};
int main() {    
  testing::AddGlobalTestEnvironment(new FooEnvironment); // 事件挂载
  testing::InitGoogleTest();    
  return RUN_ALL_TESTS();
}

4.3.1、TestSuite 事件

要实现 TestSuite 事件,必须写一个类,继承 testing::Test 类,实现 SetUpTestCase 和 TearDownTestCase 方法:

  • SetUpTestCase() 方法在 TestSuite 里的第一个 TestCase 之前执行;
  • TearDownTestCase() 方法在 TestSuite 里的最后一个 TestCase 之后执行。
class FooTest : public testing::Test {
 protected:  
  static void SetUpTestCase() {
    shared_resource_ = new int(1234);  
  }  
  static void TearDownTestCase() {    
    delete shared_resource_;    
    shared_resource_ = NULL;  
  }  
  int* shared_resource_;
};

4.3.1、TestCase 事件

实现方法和 TestSuite 事件几乎一样,只不过它是实现的 SetUp 和 TearDown 方法:

  • SetUp() 方法在每一个 TestCase 之前执行;
  • TearDown() 方法每一个 TestCase 之后执行。
class FooCalcTest:public testing::Test {
protected:
    virtual void SetUp() {
      m_foo.Init();    
    }    
    virtual void TearDown() {    
      m_foo.Finalize();    
    }    
    FooCalc m_foo;
};

4.4、断言

对测试代码中的检查点进行检查。分为以下两种断言类型:

  • ASSERT_*断言:当检查点失败时,退出当前函数;
  • EXPECT_*断言:当检查点失败时,继续往下执行。

断言后可以增加自定义信息输出

EXPECT_EQ(a, b) << "Vectors x and y differ at index "

4.4.1、布尔值检查

在这里插入图片描述

4.4.2、字符串类型检查

在这里插入图片描述

4.4.3、 数值类型检查

在这里插入图片描述

4.4.4、 浮点数检查

在这里插入图片描述

4.4.5、返回成功或失败

在这里插入图片描述
在这里插入图片描述

4.4.6、 Windows 平台的 HRESULT 检查

在这里插入图片描述

4.4.7、 类型检查

testing::StaticAssertTypeEq(); 类似模板中的编译检查

4.5、运行参数控制

gtest提供了一系列运行参数,使我们可以对用例的执行进行控制。主要分为两种方式:

  • 命令行参数
  • 代码中指定FLAG

4.5.1、–gtest_filter

对执行的测试用例进行过滤,支持通配符。 ? 单个字符 * 任意字符 - 排除,如,-a 表示除了 a : 取或,如,a:b 表示 a 或 b。

命令行:--gtest_filter
FLAG:testing::GTEST_FLAG(filter)

--gtest_filter=libtest.*
testing::GTEST_FLAG(filter)= "libtest.*";

4.5.2、–gtest_repeat=[COUNT]

设置用例重复运行次数。

命令行:--gtest_repeat
FLAG: testing::GTEST_FLAG(gtest_repeat) 

--gtest_repeat=1000  // 重复执行1000次,即使中途出现错误。
--gtest_repeat=-1  // 无限次数执行
--gtest_repeat=1000 --gtest_filter=FooBar  // 重复执行1000次测试用例名称为FooBar的用例。
testing::GTEST_FLAG(gtest_repeat) = 1000;
testing::GTEST_FLAG(filter) = "FooBar";

4.5.3、–gtest_output=xml[:DIRECTORY_PATH|:FILE_PATH]

4.5.3.1、gtest_output使用

输出测试报告,xml格式。

【注意!!!】: 该命令需要在testing::InitGoogleTest(); 前调用,否则不生效,且xml只支持生成一份。

  • xml: 不指定输出路径,默认为当前路径
  • xml:d:\tmp 输出到d:\tmp目录
  • xml:d:\tmp\test.xml 输出到d:\tmp\test.xml目录
命令行:--gtest_output
FLAG: testing::GTEST_FLAG(output) 

testing::GTEST_FLAG(output) = "xml:"; 
4.5.3.2、CustomTestResultProcessorListener监听器

因为GTest只支持生成一份xml,若测试用例过多的话,xml会非常大,不利于查看,这边可以通过GTest的CustomTestResultProcessorListener监听器来实现写多份xml文件。

CustomTestResultProcessorListener 是 Google Test 框架中提供的一个监听器,它允许你为测试结果添加自定义处理逻辑。这个监听器在每个测试用例运行后都会被调用,你可以在这个监听器中添加自定义的处理逻辑,例如将测试结果写入到文件中。

这个监听器是 ::testing::UnitTest::GetInstance()->listeners().Append() 方法添加的,这个方法允许你向测试框架的监听器列表中添加新的监听器。

// 自定义测试结果处理器
bool CustomTestResultProcessor(const ::testing::TestResult& result) {...}
// 设置自定义的测试结果处理器
::testing::UnitTest::GetInstance()->listeners().Append(new ::testing::CustomTestResultProcessorListener(CustomTestResultProcessor));

4.5.4、–gtest_print_time

输出命令行时是否打印每个测试用例的执行时间。默认是打印。

命令行:-gtest_print_time
FLAG: testing::GTEST_FLAG(print_time)

testing::GTEST_FLAG(print_time) = true; 

五、GTest结构理解

在Google Test中,Test 和 Environment 是两个不同的概念,它们分别用于不同的目的:

  • Test:
    测试用例(Test Case):这是一个单元测试,它测试一个特定的功能或行为。
    【对应TEST】
    测试套件(Test Suite):这是一个测试用例的集合,它可以包含多个测试用例。
    【对应testing::Test类】
    测试参数(Test Parameter):这是一个测试用例的参数,它允许你为每个测试用例提供不同的输入。
    【对应testing::TestWithParam<int>类】
  • Environment:
    环境(Environment):这是一个测试环境,它可以为测试用例提供一个共享的设置和清理环境。
    【对应testing::Environment 类】

以下是一些关于Test和Environment的关键区别:

  • 生命周期:
    Test:每个测试用例都有自己的生命周期,它在测试开始时创建,在测试结束时销毁。
    Environment:每个测试套件都有自己的环境,它在测试开始时创建,在测试结束时销毁。
  • 作用域:
    Test:测试用例的生命周期仅限于当前测试。
    Environment:环境的生命周期是整个测试套件。
  • 设置和清理:
    Test:测试用例通常不需要设置或清理环境,因为它们是为特定的测试目的而设计的。
    Environment:环境通常用于为测试套件提供共享的设置和清理环境。
  • 使用方式:
    Test:通常在测试套件中使用,用于测试特定的功能或行为。
    Environment:通常在测试套件中使用,用于为测试套件提供共享的设置和清理环境。
  • 实例化:
    Test:通常在测试套件中实例化,每个测试用例都有自己的实例。
    Environment:通常在测试套件中实例化,每个测试套件都有自己的环境实例。
  • 参数化:
    Test:可以使用测试参数来为每个测试用例提供不同的输入。
    Environment:通常不直接使用,而是通过环境工厂来创建环境。
  • 总的来说,Test 和 Environment 是两个不同的概念,它们分别用于不同的目的。Test 用于测试单个功能或行为,而 Environment 用于为测试套件提供共享的设置和清理环境。
    类图

六、OpenCppCoverage代码覆盖工具

这边是写了个bat脚本,执行OpenCppCoverage的命令,如图:
在这里插入图片描述
在这里插入图片描述
直接执行bat,会在输出res路径中生成index.html,可查看相应代码走到的分支,触发为绿色,未触发为红色。

【注意!!!】:dll和pdb需要都有,代码优化也要关掉,否则生成的html里面是分析不到的。

七、GTest集成测试框架设计

待更新

八、总结

上述学习总结了GTest的一些语法和设计的工程框架,但其实还主要集中在windows下进行实现,后续在linux做二次开发的时候,可以搭建一套Linux环境下的GTest工程,原理大差不大,主要是一些语法使用上的差异。

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
GTest是Google Test的简称,是一个功能强大的C++单元测试框架。它提供了丰富的断言和测试工具,可以方便地编写、运行和管理测试用例。 首先,我们需要下载并安装GTest框架GTest可以从官方网站下载并编译安装,也可以使用包管理工具进行安装。安装完成后,我们就可以在自己的项目中使用GTest进行单元测试了。 在编写测试用例时,我们需要在一个类中定义多个测试函数。每个测试函数都应该以"TEST"宏开始,并且应该在测试函数中使用多个断言来验证被测试代码的行为。例如,我们可以使用"EXPECT_EQ"断言来验证两个值是否相等。当测试函数执行完毕时,我们可以使用"ASSERT_"宏来检查测试是否通过。 GTest还提供了一些高级功能,例如测试夹具(Test Fixture)和参数化测试(Parameterized Test)等。测试夹具可以帮助我们在测试函数之前和之后执行一些共享的设置和清理操作。参数化测试可以使得我们在一组测试数据上运行相同的测试代码,以验证被测试代码在不同输入条件下的行为。 在运行测试时,我们可以使用GTest提供的命令行工具来执行测试用例。它会输出每个测试函数的执行结果以及总体的测试统计信息。我们也可以在IDE中集成GTest,并通过点击运行按钮来执行测试。 总之,GTest是一个非常强大和方便的单元测试框架,可以帮助我们编写高质量的测试用例并验证被测试代码的正确性。通过充分利用GTest提供的功能,我们可以玩转Google单元测试框架,提升软件开发的质量和效率。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值