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();
 }
相关文章
|
3天前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
28天前
|
机器学习/深度学习 监控 算法
自动化测试框架的演进与最佳实践
随着软件行业的迅猛发展,自动化测试已成为确保软件质量的关键手段。本文将深入探讨自动化测试框架的历史演进、当前趋势以及面临的挑战,并结合实际案例分析,提出一系列的最佳实践策略,旨在帮助读者构建更加高效、稳定的自动化测试体系。
|
18天前
|
Web App开发 数据管理 测试技术
自动化测试框架的搭建与实践
【8月更文挑战第1天】在软件开发的海洋中,自动化测试犹如一座灯塔,为开发团队指引方向,确保代码质量。本文将深入探讨如何搭建一套高效的自动化测试框架,并通过实际案例,揭示其在项目中的应用价值。我们将从框架设计原则出发,逐步实现一个简易的自动化测试框架,并分享实践中的经验和教训,帮助读者理解自动化测试的核心理念及其在真实世界的应用。
|
7天前
|
IDE 测试技术 持续交付
Python自动化测试与单元测试框架:提升代码质量与效率
随着软件行业的发展,代码质量和效率变得至关重要。自动化测试与单元测试是保证质量、提升效率的关键。Python凭借其简洁强大及丰富的测试框架(如Selenium、Appium、pytest和unittest等),成为了实施自动化测试的理想选择。本文将深入探讨这些框架的应用,帮助读者掌握编写高质量测试用例的方法,并通过持续集成等策略提升开发流程的效率与质量。
26 4
|
7天前
|
Web App开发 IDE 测试技术
天呐!当揭开 Selenium 自动化测试框架的神秘面纱,设计与实现令人瞠目!
【8月更文挑战第12天】Selenium 是一强大自动化测试框架,用于Web应用测试。它含WebDriver、IDE和Grid等工具,支持Chrome、Firefox等浏览器。可通过编程模拟用户交互验证应用功能。例如使用Python结合Selenium WebDriver编写自动化测试脚本,实现打开网页、操作元素及断言等功能。还可结合测试框架和Selenium Grid提升测试效率和并行执行能力。
19 1
|
13天前
|
人工智能 测试技术 持续交付
探索自动化测试框架的演进与实践
【8月更文挑战第6天】 随着软件行业的快速发展,自动化测试已经成为保障软件质量的关键手段。本文将深入分析自动化测试框架的发展脉络,从早期的线性脚本到现代的模块化、数据驱动和关键字驱动的框架,再到最新的基于AI的智能测试工具。文章还将探讨如何在实际工作中有效实施这些框架,以及在面对新技术时如何保持测试策略的灵活性和前瞻性。
|
18天前
|
jenkins 测试技术 持续交付
自动化测试框架的设计与实现
【8月更文挑战第1天】在软件开发过程中,测试是确保产品质量的关键步骤。本文将探讨如何设计并实现一个高效且可扩展的自动化测试框架,旨在提高测试效率,减少重复性工作,并通过具体示例展示其在现实项目中的应用。
|
19天前
|
运维 测试技术 调度
自动化测试框架的设计与实现自动化运维的利器:Ansible Role 实践指南
【7月更文挑战第31天】随着软件开发周期的缩短和迭代速度的加快,手动软件测试已难以满足效率与质量的双重需求。本文将深入探讨如何设计并实现一个高效的自动化测试框架,以提升测试工作的效率和准确性。我们将通过具体的代码示例,展示框架的核心组件和实现逻辑,帮助读者理解自动化测试框架的构建过程及其在实际项目中的应用价值。
26 5
|
16天前
|
存储 数据管理 jenkins
自动化测试框架的搭建与实践
【8月更文挑战第3天】随着软件行业的迅猛发展,自动化测试已成为保证软件质量的重要手段。本文将介绍如何搭建一个高效的自动化测试框架,并通过实际代码示例展示其应用。我们将探讨框架设计的核心原则、工具选择和脚本编写的最佳实践,以及如何通过持续集成实现自动化测试流程的优化。
|
16天前
|
敏捷开发 Web App开发 jenkins
自动化测试框架的搭建与实践
【8月更文挑战第3天】在软件开发过程中,自动化测试已成为提高软件质量和开发效率的关键。本文将深入讨论如何搭建一个高效且可靠的自动化测试框架,包括工具选择、环境配置、脚本编写和执行策略等。通过实际案例,我们将展示框架的具体实施过程,并分析其在真实项目中的表现和效益。
25 1

热门文章

最新文章