GoogleTest 测试框架

简介: GoogleTest 测试框架

1、单元测试

单元测试unit testing是指对软件中的最小可测试单元进行检查和验证,包括函数、类、模块、复杂交互逻辑等。gtest 中单元测试以单例模式实现。每个单元测试包含若干个测试套件test suite,测试套件是指一组功能相同的测试脚本或过程。每个测试套件包含多个测试案例test case,测试同一个功能的不同方向。

根据 gtest 官方文档,一个好的单元测试应该满足:

  • 独立、可重复:测试案例可单独执行,错误可重复发生。
  • 反映测试代码结构:功能完整,体现完备逻辑。对于关联功能,一个功能是否影响其他功能
  • 可移植、可复用:跨平台。
  • 尽可能多的出错信息:不会因为一次失败而停止,会继续测试下一个测试案例。一次测试发现多个错误。
  • 自动跟踪所有测试而无需枚举:向容器中添加
  • 测试高效:测试间重用资源,mock 模拟复杂交互

如何整体使用单元测试

  • 每个类或功能块加上测试案例
  • test 目录加上所有的测试
  • 一个单元测试可能散落在多个文件

单元测试中的打桩,是指用来代替关联代码或者未实现代码的代码,即用桩函数代替原函数。打桩测试由 gtest 里的 gmock 来实现。

2、GTest 安装

GTest 官网

# 下载
 git clone https://github.com/google/googletest.git
 # 安装
 cd googletest
 cmake CMakeLists.txt
 make
 sudo make install

源码文件中的 lib 库,包含 gtest 库和 gmock 库。

libgtest.a  libgtest_main.a  libgmock.a  libgmock_main.a

当测试代码有 main 函数,使用不带 main 的静态库,否则使用带 main 的静态库。

# 无 main 函数
 g++ sample.cc sample_unittest.cc -o sample -lgtest -lgtest_main -lpthread
 # 有 main 函数
 g++ gmock_output_test_.cc -o output -lgtest -lgmock -lpthread

若需要编写 main 函数,关键在于添加两个地方

int main(int argc, char **argv) {
   // 1.定义 main 函数:初始化 gtest
   ::testing::InitGoogleTest(&argc, argv);
   // 2.定义 main 函数:开启全部测试
   return RUN_ALL_TESTS();
 }

3、GTest 原理

GTest 测试底层原理

  • 创建单元测试类,单例模式实现,包含vector<TestSuite*>
  • 根据测试套件名,生成一个TestSuite 类实例,包含vector<TestInfo*>;
  • 根据测试案例名,生成一个Test_info类实例,继承父类 testing::Test
  • 将测试案例实例注册到测试套件中vector<TestSuite*>,测试套件类实例调用run方法执行测试

类的组织层次

// 单元测试,单例模式实现
 class Impl {
     // 存储测试套件类实例
     vector<TestSuite*>;
 }
 // 测试套件
 class TestSuite {
     // 存储测试案例类实例
     vector<TestInfo*>;
     // 执行测试套件中的测试案例
     run();
 }
 // 测试案例
 class TestInfo {
     // 执行测试
     TestBody();
 };

4、断言

使用测试断言,通过断言其行为来测试类和函数。ASSERT_* 失败时会生成致命错误,并中止当前功能;EXPECT_*失败时生成非致命错误,不会中止当前功能。通常选用EXPECT_*

所有断言宏都支持输出流,经流输出的信息自动转换为utf-8,可利用这一特性输出详细错误信息

EXPECT_TRUE(my_condition) << "My condition is not true";

更多断言的使用,见官方文档:Assertions

明确指定成功或失败

当测试案例中的条件太复杂,不能使用断言,那么自己写判断语句;自己返回成功或者失败;

if (condition) {
     SUCCEED();    
 }
 else {
     FAIL();    
 }

布尔条件

EXPECT_TRUE(condition)
 ASSERT_TRUE(condition)
 EXPECT_FALSE(condition)
 ASSERT_FALSE(condition)

二元比较

// val1 = val2
 EXPECT_EQ( val1 , val2 )
 ASSERT_EQ( val1 , val2 )
 // val1 != val2,空指针使用 nullptr
 EXPECT_NE( val1 , val2 )
 ASSERT_NE( val1 , val2 )
 // val1 <= val2
 EXPECT_LT( val1 , val2 )
 ASSERT_LT( val1 , val2 )
 // val1 > val2
 EXPECT_GT( val1 , val2 )
 ASSERT_GT( val1 , val2 )
 // val1 >= val2
 EXPECT_GE( val1 , val2 )
 ASSERT_GE( val1 , val2 )

谓词断言

EXPECT_PREDn( pred , val1, ..., valn ) \
 ASSERT_PREDn( pred , val1, ..., valn ) \

例如:测试阶乘函数,参数 1 个

EXPECT_PRED1(Factorial, 1)

死亡测试

用于测试程序是否以预期的方式崩溃。

EXPECT_DEATH(func, desc);

5、GTest 使用

测试的方法

  • 基本功能:验证基本逻辑是否正确
  • 边界情况:验证边界值是否正确输出
  • 异常情况:非法输入做出合理错误处理。判断错误的方式
  • 函数返回值
  • 全局变量:linux: Errnowindows: GetLastError
  • 异常:抛出异常


5.1、测试1:测试函数

使用 TEST宏来定义测试案例。

#include "sample1.h"
 #include <limits.h>
 #include "gtest/gtest.h"
 // 使用 TEST 宏定义测试案例 
 // #define TEST(test_suite_name,test_name)
 // 测试阶乘:负数
 TEST(FactorialTest, Negative) {
   // 断言:预期相等 EXPECT_EQ(expected, actual),后面同理
   EXPECT_EQ(1, Factorial(-5));
   EXPECT_EQ(1, Factorial(-1));
   EXPECT_GT(Factorial(-10), 0);
 }
 // 测试阶乘:0
 TEST(FactorialTest, Zero) { EXPECT_EQ(1, Factorial(0)); }
 // 测试阶乘:正数
 TEST(FactorialTest, Positive) {
   EXPECT_EQ(1, Factorial(1));
   EXPECT_EQ(2, Factorial(2));
   EXPECT_EQ(6, Factorial(3));
   EXPECT_EQ(40320, Factorial(8));
 }

编译代码

g++ sample1.cc sample1_unittest.cc -o sample1 -lgtest -lgtest_main -lpthread

测试结果

[==========] Running 6 tests from 2 test suites.
 [----------] Global test environment set-up.
 [----------] 3 tests from FactorialTest
 [ RUN      ] FactorialTest.Negative
 [       OK ] FactorialTest.Negative (0 ms)
 [ RUN      ] FactorialTest.Zero
 [       OK ] FactorialTest.Zero (0 ms)
 [ RUN      ] FactorialTest.Positive
 [       OK ] FactorialTest.Positive (0 ms)
 [----------] 3 tests from FactorialTest (0 ms total)
 ...
 [----------] Global test environment tear-down
 [==========] 6 tests from 2 test suites ran. (0 ms total)
 [  PASSED  ] 6 tests.

5.2、测试2:测试类

#include <iostream>
 #include <initializer_list>
 #include <vector>
 #include <gtest/gtest.h>
 using namespace std;
 class IslandProblem {
 public:
     using Matrix = vector<vector<char>>;
     IslandProblem(const initializer_list<vector<char>> list) {
         _islands.assign(list);
     }
     int Do() {
         int num = 0;
         for (int row = 0; row < (int)_islands.size(); row++) {
             for (int col = 0; col < (int)_islands[row].size(); col++) {
                 if (canUnion(row, col)) {
                     num++;
                     unionIsland(row, col);
                 }
             }
         }
         return num;
     }
 protected:
     bool canUnion(int row, int col) {
         if (row < 0 || row >= (int)_islands.size())
             return false;
         if (col < 0 || col >= (int)_islands[row].size())
             return false;
         if (_islands[row][col] != 1)
             return false;
         return true;
     }
     void unionIsland(int row, int col) {
         _islands[row][col] = 2;
         if (canUnion(row-1, col)) unionIsland(row-1, col);
         if (canUnion(row, col-1)) unionIsland(row, col-1);
         if (canUnion(row+1, col)) unionIsland(row+1, col);
         if (canUnion(row, col+1)) unionIsland(row, col+1);
     }
 private:
     Matrix _islands;
 };
 TEST(IslandProblem, logic) {
     IslandProblem ip1{
         {1,1,1,1},
         {1,0,1,1},
         {0,0,0,0},
         {1,0,1,0}
     };
     EXPECT_EQ(ip1.Do(), 3);
     IslandProblem ip2{
         {1,0,1,1},
         {1,0,1,1},
         {0,0,0,0},
         {1,0,1,0}
     };
     EXPECT_EQ(ip2.Do(), 4);
 }
 TEST(IslandProblem, boundary) {
     IslandProblem ip1{
         {1,1,1,1},
         {1,0,0,1},
         {1,0,0,1},
         {1,1,1,1}
     };
     EXPECT_EQ(ip1.Do(), 1);
     IslandProblem ip2{
     };
     EXPECT_EQ(ip2.Do(), 0);
 }
 TEST(IslandProblem, exception) {
     IslandProblem ip1{
         {-1,1,1,1},
         {1,0,0,1},
         {1,0,0,1},
         {1,1,1,1}
     };
     EXPECT_EQ(ip1.Do(), 1);
 }

5.3、测试3:测试夹具

用相同的数据配置来测试多个测试案例,实现测试夹具共享,而不是数据共享。

使用测试夹具的方法

  • 在使用测试夹具前,定义测试夹具类,继承基类testing::Test,类成员可访问 protected
  • 实现SetUp()接口:测试前调用,若要初始化变量,重定义该接口,否则跳过。
  • 实现TearDown()接口:测试后调用,若有清理工作要做,重定义该接口,否则跳过。
  • 自定义成员

使用 TEST_F宏测试夹具

#include "sample3-inl.h"
 #include "gtest/gtest.h"
 namespace {
 // 使用测试夹具,必须继承基类 testing::Test
 class QueueTestSmpl3 : public testing::Test {
  protected:
   // 1、实现 SetUp() 接口
   void SetUp() override {
     q1_.Enqueue(1);
     q2_.Enqueue(2);
     q2_.Enqueue(3);
   }
   // 2、实现 TearDown() 接口
   // virtual void TearDown() {
   // }
   // 自定义辅助测试成员函数
   // A helper function that some test uses.
   static int Double(int n) { return 2 * n; }
   // A helper function for testing Queue::Map().
   void MapTester(const Queue<int>* q) {
     // Creates a new queue, where each element is twice as big as the
     // corresponding one in q.
     const Queue<int>* const new_q = q->Map(Double);
     // Verifies that the new queue has the same size as q.
     ASSERT_EQ(q->Size(), new_q->Size());
     // Verifies the relationship between the elements of the two queues.
     for (const QueueNode<int>*n1 = q->Head(), *n2 = new_q->Head();
          n1 != nullptr; n1 = n1->next(), n2 = n2->next()) {
       EXPECT_EQ(2 * n1->element(), n2->element());
     }
     delete new_q;
   }
   // 自定义数据成员
   Queue<int> q0_;
   Queue<int> q1_;
   Queue<int> q2_;
 };
 // 使用 TEST_F 宏测试夹具 TEST_F(test_fixture, test_name)
 // 测试队列:构造函数
 TEST_F(QueueTestSmpl3, DefaultConstructor) {
   EXPECT_EQ(0u, q0_.Size());
 }
 // 测试队列:出队
 TEST_F(QueueTestSmpl3, Dequeue) {
   int* n = q0_.Dequeue();
   EXPECT_TRUE(n == nullptr);
   n = q1_.Dequeue();
   ASSERT_TRUE(n != nullptr);
   EXPECT_EQ(1, *n);
   EXPECT_EQ(0u, q1_.Size());
   delete n;
   n = q2_.Dequeue();
   ASSERT_TRUE(n != nullptr);
   EXPECT_EQ(2, *n);
   EXPECT_EQ(1u, q2_.Size());
   delete n;
 }
 // 测试队列:map()
 TEST_F(QueueTestSmpl3, Map) {
   MapTester(&q0_);
   MapTester(&q1_);
   MapTester(&q2_);
 }
 }  // namespace

5.4、测试4:类型参数化

相同的接口,有多个实现,复用测试案例,策略模式。例如:写日志(写磁盘、写数据库、写 kafka)。

使用 TYPED_TEST宏测试类型参数化

// 枚举测试类型:同一接口的不同实现形式类
 typedef Types<Class1, Class2, class3, ...> Implementations;
 // 定义测试套件
 TYPED_TEST_SUITE(TestFixtureSmpl, Implementations);
 // 使用 `TYPED_TEST`宏测试类型参数化
 TYPED_TEST(TestFixtureSmpl, TestName)

5.5、测试5:事件

通过事件机制,在测试前后进行埋点处理。事件机制定义如下

class TersePrinter : public EmptyTestEventListener {
  private:
   // Called before any test activity starts.
   void OnTestProgramStart(const UnitTest& /* unit_test */) override {}
   // Called after all test activities have ended.
   void OnTestProgramEnd(const UnitTest& unit_test) override {
     fprintf(stdout, "TEST %s\n", unit_test.Passed() ? "PASSED" : "FAILED");
     fflush(stdout);
   }
   // Called before a test starts.
   void OnTestStart(const TestInfo& test_info) override {
     fprintf(stdout, "*** Test %s.%s starting.\n", test_info.test_suite_name(),
             test_info.name());
     fflush(stdout);
   }
   // Called after a failed assertion or a SUCCEED() invocation.
   void OnTestPartResult(const TestPartResult& test_part_result) override {
     fprintf(stdout, "%s in %s:%d\n%s\n",
             test_part_result.failed() ? "*** Failure" : "Success",
             test_part_result.file_name(), test_part_result.line_number(),
             test_part_result.summary());
     fflush(stdout);
   }
   // Called after a test ends.
   void OnTestEnd(const TestInfo& test_info) override {
     fprintf(stdout, "*** Test %s.%s ending.\n", test_info.test_suite_name(),
             test_info.name());
     fflush(stdout);
   }
 };  // class TersePrinter

例:内存泄漏

在需要检测的类中,重载 new 和 delete 操作符,再用静态成员统计两者的次数是否一致。

#include <stdio.h>
 #include <stdlib.h>
 #include "gtest/gtest.h"
 using ::testing::EmptyTestEventListener;
 using ::testing::InitGoogleTest;
 using ::testing::Test;
 using ::testing::TestEventListeners;
 using ::testing::TestInfo;
 using ::testing::TestPartResult;
 using ::testing::UnitTest;
 namespace {
 // 需要检测的类
 class Water {
  public:
   // 类的定义
   // 重载 new 和 delete 函数
   void* operator new(size_t allocation_size) {
     allocated_++;
     return malloc(allocation_size);
   }
   void operator delete(void* block, size_t /* allocation_size */) {
     allocated_--;
     free(block);
   }
  static int allocated() { return allocated_; }
  private:
   // 静态成员,统计 new 和 delete 的次数,判断内存泄漏
   static int allocated_;
 };
 int Water::allocated_ = 0;
 // 检测内存泄漏:事件机制
 class LeakChecker : public EmptyTestEventListener {
  private:
   // Called before a test starts.
   void OnTestStart(const TestInfo& /* test_info */) override {
     initially_allocated_ = Water::allocated();
   }
   // Called after a test ends.
   void OnTestEnd(const TestInfo& /* test_info */) override {
     int difference = Water::allocated() - initially_allocated_;
     // 输出测试结果
     EXPECT_LE(difference, 0) << "Leaked " << difference << " unit(s) of Water!";
   }
   int initially_allocated_;
 };
 // 开启内存泄漏检测 --check_for_leaks
 TEST(ListenersTest, DoesNotLeak) {
   Water* water = new Water;
   delete water;
 }
 // 未开启内存泄漏检测?通过判断指针是否为空,来判断是否有内存谢洛
 TEST(ListenersTest, LeaksWater) {
   Water* water = new Water;
   EXPECT_TRUE(water != nullptr);
 }
 }  // namespace
 int main(int argc, char** argv) {
   // 1、main 函数定义:初始化 InitGoogleTest
   InitGoogleTest(&argc, argv);
   bool check_for_leaks = false;
   if (argc > 1 && strcmp(argv[1], "--check_for_leaks") == 0)
     check_for_leaks = true;
   else
     printf("%s\n", "Run this program with --check_for_leaks to enable leak checking.");
   // 若启动命令行添加参数 --check_for_leaks,开启内存泄漏检测
   if (check_for_leaks) {
     TestEventListeners& listeners = UnitTest::GetInstance()->listeners();
     listeners.Append(new LeakChecker);
   }
   // 2、main 函数定义:开启全级测试
   return RUN_ALL_TESTS();
 }
相关文章
|
15天前
|
Web App开发 IDE 测试技术
【专栏】Selenium 是一款广泛使用的自动化测试框架:深入理解 Selenium 的核心组件
【4月更文挑战第27天】Selenium 是一款广泛使用的自动化测试框架,核心组件包括 WebDriver(与浏览器交互的接口,支持多浏览器测试),IDE(可视化的测试脚本录制和编辑工具)和 Grid(分布式测试,实现多机器并行测试)。通过这些组件,开发者能高效、稳定地进行自动化测试,但需注意浏览器兼容性、脚本维护和性能问题。理解并掌握这些组件的使用,能提升测试效率和质量。
|
2天前
|
Java 中间件 测试技术
深入理解自动化测试框架Selenium的设计与实现
【5月更文挑战第10天】 本文旨在深度剖析自动化测试工具Selenium的核心架构与实现机制,通过对其设计理念、组件结构以及在实际软件测试中的应用进行详细解读,使读者能够全面理解Selenium在现代Web应用测试中的重要性和有效性。文章首先介绍Selenium的发展背景及其解决的问题,然后详细探讨其架构设计,包括各种驱动和API的作用,最后结合实际案例分析Selenium如何提高测试效率和准确性。
|
3天前
|
测试技术
测试基础 Junit单元测试框架
测试基础 Junit单元测试框架
11 2
测试基础 Junit单元测试框架
|
4天前
|
Java 测试技术 持续交付
自动化测试框架选型与实战:深入探索与应用
【5月更文挑战第8天】本文探讨了自动化测试框架的选型与实战应用,强调了其在软件质量保障中的重要性。选型原则包括考虑项目需求、技术栈、可扩展性和可维护性,以及社区支持和文档。介绍了Selenium、Appium、JUnit和Pytest等常用框架,并概述了实战应用的步骤,包括明确需求、搭建环境、编写测试用例、执行测试、分析结果、维护代码和持续集成。合理选型与实践能提升测试效率,保障项目成功。
|
7天前
|
敏捷开发 测试技术 持续交付
深入理解自动化测试:框架与实践
【5月更文挑战第5天】 在现代软件开发周期中,自动化测试已成为确保产品质量和加速交付过程的关键环节。本文将深入探讨自动化测试的核心概念、框架选择以及实际实施过程中的最佳实践。通过分析各种自动化测试工具和技术的优缺点,我们旨在为读者提供一种系统化的方法来构建和维护有效的自动化测试环境。
|
7天前
|
机器学习/深度学习 人工智能 自然语言处理
自动化测试中AI驱动的决策框架设计与实现
【5月更文挑战第5天】 在软件测试领域,自动化测试已成为提升测试效率和质量的关键手段。然而,随着软件系统的复杂性增加,传统的自动化测试方法面临挑战,尤其在测试用例的生成、执行及结果分析等方面。本文提出一种基于人工智能(AI)的自动化测试决策框架,旨在通过智能化的算法优化测试过程,并提高异常检测的准确率。该框架结合机器学习和深度学习技术,能够自学习历史测试数据,预测高风险变更区域,自动生成针对性强的测试用例,并在测试执行过程中实时调整测试策略。此外,通过自然语言处理(NLP)技术,该框架还能对测试结果进行语义分析,进一步提供更深入的洞察。本研究不仅增强了自动化测试工具的智能性,也为软件质量保证提
|
7天前
|
测试技术
使用CLion创建Cmake项目,使用GoogleTest和GoogleMock对代码进行测试
使用CLion创建Cmake项目,使用GoogleTest和GoogleMock对代码进行测试
20 3
|
11天前
|
数据管理 测试技术
深入理解自动化测试框架:以Selenium为例
【4月更文挑战第30天】 随着软件开发的快速发展,自动化测试已经成为保证软件质量和提升开发效率的重要手段。本文将深入探讨自动化测试框架的核心概念,并以广泛应用的开源工具Selenium为例,解析其架构、原理及在实际项目中的运用。通过实例分析与性能评估,旨在为读者提供一套系统的自动化测试解决方案,并探讨其在复杂应用场景下的优化策略。
|
12天前
|
敏捷开发 前端开发 JavaScript
深入理解自动化测试框架:以Selenium为例
【4月更文挑战第30天】 在现代软件开发过程中,自动化测试已成为确保产品质量和加快市场投放的关键步骤。本文聚焦于流行的自动化测试框架——Selenium,探讨其架构、核心组件以及如何有效地利用Selenium进行Web应用测试。通过分析真实案例,我们将揭示Selenium在实际项目中的应用优势与面临的挑战,并提出优化策略。文章的目的在于帮助测试工程师深入理解Selenium,提升其在复杂项目中的运用效率。
|
12天前
|
前端开发 IDE 数据可视化
深入理解与应用自动化测试框架Selenium的最佳实践
【4月更文挑战第30天】 本文将深入剖析自动化测试框架Selenium的核心原理,并结合最佳实践案例,探讨如何有效提升测试覆盖率和效率。文中不仅涉及Selenium的架构解析,还将提供针对性的策略来优化测试脚本,确保测试流程的稳定性与可靠性。通过实例演示,读者可以掌握如何在不同测试场景中灵活运用Selenium,以及如何处理常见的技术挑战。

热门文章

最新文章