探讨GMock封装与StubMock的实现及其优点

文章目录

  • 0.概要
  • 1. 为什么要封装GMock?
  • 2. `stub_mock.h` 的设计与实现
    • 2.1 接口(宏)介绍
    • 2.2 核心实现细节
    • 2.3 使用示例
      • 2.3.1 静态函数打桩
      • 2.3.2 类成员函数打桩
      • 2.3.3 虚函数打桩
      • 2.3.4 重载函数打桩
    • 2.4 lambda表达式的使用场景
    • 2.5 gmock action
  • 3 `stub.h` 的介绍
    • 3.1 替换函数实现
    • 3.2 内存保护机制
  • 4 结论

0.概要

在软件开发和测试过程中,模拟(mocking)是一个非常重要的技术手段。特别是在单元测试中,模拟对象可以帮助我们隔离被测试的代码,确保测试的独立性和准确性。Google Mock(GMock)是一个广泛使用的C++模拟框架,但在一些复杂的场景下,直接使用GMock可能会显得不够灵活和高效。本文将深入探讨如何通过封装GMock,结合第三方库cpp-stub中的stub.h,实现一个功能更强大且灵活的模拟框架——StubMock。

1. 为什么要封装GMock?

虽然GMock已经提供了强大的模拟功能,但在某些特定场景下,直接使用GMock仍然存在一些不足:

  • 复杂的设置和管理

    • 在大型项目中,涉及多个类和函数的复杂交互时,直接使用GMock进行设置和管理可能变得繁琐和复杂。封装可以简化这些设置过程,使代码更清晰和易于维护。
  • 类型安全和编译期检查

    • 尽管GMock提供了良好的类型安全支持,但封装可以进一步强化这一点。例如,使用模板和宏定义,可以在编译期进行更严格的类型检查,避免运行时错误。
  • 统一的管理和复用

    • 对于需要在多个测试用例中反复使用的模拟和存根函数,封装可以提供统一的管理方式。通过单例模式和静态成员变量,可以方便地复用和管理这些函数,减少代码重复。
  • 特殊场景支持

    • 原始GMock可能对某些特殊场景(如特定修饰符的成员函数)支持不够完善。通过封装,可以扩展GMock的功能,支持更多类型的成员函数签名。
  • 简化测试代码

    • 提供了一系列辅助宏,简化测试代码的编写。减少样板代码的数量,使测试代码更简洁、更易读。

2. stub_mock.h 的设计与实现

为了弥补GMock的不足,我们设计并实现了StubMockStubMock结合了GMock和第三方库cpp-stub中的stub.h,为静态函数、类成员函数、虚函数以及重载函数提供了灵活的模拟支持。

代码
橘色的喵/custom_gtest_stub

2.1 接口(宏)介绍

  • NF_SMOCK(n, fn, fn_stub):用于设置静态函数或类静态成员函数的存根。n表示存根函数的编号,fn表示要存根的函数,fn_stub表示用于替换的存根函数或行为。
  • F_SMOCK(fn, fn_stub):类似于NF_SMOCK,但编号由__COUNTER__宏自动生成,避免手动编号。
  • SMOCK_CLEAR:清除所有设置的存根函数,还原原始函数的行为。
  • ADDR(CLASS_NAME, MEMBER_NAME):获取类静态成员函数的地址。
  • V_ADDR(CLASS_NAME, MEMBER_NAME):获取虚函数的地址。
  • O_ADDR(CLASS_NAME, MEMBER_NAME, RETURN, ARGS, SPEC):获取重载函数的地址,需提供函数返回类型、参数列表及修饰符。

2.2 核心实现细节

  • 单例模式

    static StubMock &get_instance() {static StubMock stub_mock;return stub_mock;
    }
    
    • 确保整个程序只有一个StubMock实例,使用static局部变量实现线程安全的单例模式。
  • 静态存根函数模板类

    template <int N, typename R, typename... ARGS>
    class FnStatic {
    public:static testing::Action<R(ARGS...)> action;
    };
    
    • 使用模板类管理静态存根函数,每个函数都有一个唯一的编号N,确保不同函数的独立管理。
  • 设置存根函数

    template <int N = 0, typename F, typename R, typename... ARGS>
    static void set_fn(R (*fn)(ARGS...), F fn_stub) {FnStatic<N, R, ARGS...>::action = fn_stub;get_instance().set(fn, &StubMock::call_fn<N, R, ARGS...>);
    }
    
    • 通过模板参数设置存根函数,并将其与StubMock::call_fn绑定,确保调用时能正确执行存根函数。
  • 辅助宏

    #define F_SMOCK(fn, fn_stub) NF_SMOCK(__COUNTER__, fn, fn_stub)
    
    • 使用宏简化存根函数的设置,__COUNTER__宏自动生成唯一编号,减少手动管理的复杂性。
  • 成员函数地址获取

    #define V_ADDR(CLASS_NAME, MEMBER_NAME) decltype(StubMock::vfn_addr(&CLASS_NAME::MEMBER_NAME))(&CLASS_NAME::MEMBER_NAME)
    
    • 使用decltype和模板函数,安全地获取成员函数地址,确保类型匹配。

2.3 使用示例

以下是一些使用StubMock进行函数打桩的示例:

2.3.1 静态函数打桩

// unistd.h
extern ssize_t read(int __fd, void *__buf, size_t __nbytes) __wur;
// string.h
extern void *memset(void *__s, int __c, size_t __n) __THROW __nonnull((1));using namespace testing;class CLS {
public:static void s1() {}
};TEST {// use lambdaNF_SMOCK(0, read, [] { return 0; }); // have return typeNF_SMOCK(1, read, [] {}); // no return typeNF_SMOCK(2, ADDR(CLS, s1), [] {}); // class static function// use gmock actionNF_SMOCK(0, read, Return(0)); // have return typeNF_SMOCK(1, read, Return()); // no return typeNF_SMOCK(2, ADDR(CLS, s1), Return()); // class static function// do somethingSMOCK_CLEAR; // clear
}

2.3.2 类成员函数打桩

using namespace testing;class CLS {int cfn(int x) const { return 0; }
};TEST {// use lambdaNF_SMOCK(0, ADDR(CLS, cfn), [] { return 1; });// use gmock actionNF_SMOCK(0, ADDR(CLS, cfn), Return(1));// do somethingSMOCK_CLEAR; // clear
}

2.3.3 虚函数打桩

using namespace testing;class CLS {virtual int vir_fun() const { return 0; }
};TEST {// use lambdaNF_SMOCK(0, V_ADDR(CLS, vir_fun), [] { return 1; });// use gmock actionNF_SMOCK(0, V_ADDR(CLS, vir_fun), Return(1));// do somethingSMOCK_CLEAR; // clear
}

2.3.4 重载函数打桩

class CLS {int fun() const { return 0; }int fun(double) const { return 0; }
};TEST(a, b) {// use lambdaNF_SMOCK(0, O_ADDR(CLS, fun, int, (), (const)), [] { return 1; });NF_SMOCK(1, O_ADDR(CLS, fun, int, (double), (const)), [] { return 2; });// use gmock actionNF_SMOCK(0, O_ADDR(CLS, fun, int, (), (const)), Return(1));NF_SMOCK(1, O_ADDR(CLS, fun, int, (double), (const)), Return(2));// do somethingSMOCK_CLEAR; // clear
}

2.4 lambda表达式的使用场景

lambda表达式通常用于逻辑复杂的场景,例如根据条件返回不同的值:

// unistd.h
extern ssize_t read(int __fd, void *__buf, size_t __nbytes) __wur;TEST {NF_SMOCK(0, read, [] {static int cnt = 0;cnt++;if (cnt == 1) return 0;return -1;});// do somethingSMOCK_CLEAR; // clear
}

2.5 gmock action

gmock中最常用的action是Return函数,用于指定模拟函数的返回值。例如:

using namespace testing;class CLS {static int sfn() { return 0; }
};TEST {NF_SMOCK(0, ADDR(CLS, sfn), Return(1)); // 设置静态函数的存根,返回1// do somethingSMOCK_CLEAR; // 清除存根
}

3 stub.h 的介绍

stub.h 是来自第三方库 coolxv/cpp-stub 的内容,该库提供了一种替换函数实现的方法,允许我们在运行时动态地替换函数的实现。stub.h 的主要功能是通过直接修改内存中的函数代码,实现函数的替换和恢复。以下是其主要特性:

  1. 跨平台支持:支持Windows和Linux操作系统,兼容多种CPU架构(如x86、ARM、MIPS、RISC-V等)。
  2. 高效的指令缓存刷新:根据平台的不同,使用适当的方法刷新指令缓存,确保函数替换后的代码能立即生效。
  3. 灵活的函数替换:使用不同的替换策略(近跳转和远跳转),根据具体情况选择合适的方式来替换函数。
  4. 内存保护机制:在修改函数代码前后,修改内存保护属性以确保安全性和正确性,防止非法内存访问导致的崩溃。
  5. 自动管理和恢复:维护一个std::map来记录所有被替换的函数信息,支持函数的自动恢复和清理。

以下是stub.h的一些关键实现细节:

3.1 替换函数实现

根据不同的CPU架构,选择合适的替换策略:

// x86_64架构
#define REPLACE_FAR(t, fn, fn_stub)            \*fn = 0x49;                                  \*(fn + 1) = 0xbb;                            \*(long long *)(fn + 2) = (long long)fn_stub; \*(fn + 10) = 0x41;                           \*(fn + 11) = 0xff;                           \*(fn + 12) = 0xe3;                           \CACHEFLUSH((char *)fn, CODESIZE);// 5 byte(jmp rel32)
#define REPLACE_NEAR(t, fn, fn_stub)                     \*fn = 0xE9;                                            \*(int *)(fn + 1) = (int)(fn_stub - fn - CODESIZE_MIN); \CACHEFLUSH((char *)fn, CODESIZE);

3.2 内存保护机制

在修改函数代码前后,修改内存保护属性:

#ifdef _WIN32DWORD lpflOldProtect;if (0 != VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READWRITE, &lpflOldProtect))
#elseif (0 == mprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_WRITE | PROT_EXEC))
#endif{// 修改函数代码// 恢复内存保护
#ifdef _WIN32VirtualProtect(pageof(pstub->fn), m_pagesize * 2, PAGE_EXECUTE_READ, &lpflOldProtect);
#elsemprotect(pageof(pstub->fn), m_pagesize * 2, PROT_READ | PROT_EXEC);
#endif}

4 结论

通过结合stub.h和gmock进行封装,stub_mock.h提供了一个更加灵活、强大和高效的模拟框架。它不仅扩展了GMock的功能,使其能够支持更多的场景,还简化了测试代码的编写和管理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/856796.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

How to use ModelSim

How to use ModelSim These are all written by a robot

Apache DolphinScheduler 亚马逊云科技联合Meetup: 基于云上的最佳实践

引言 随着大数据和云计算技术的快速发展&#xff0c;企业在数据管理和任务调度上面临着前所未有的挑战&#xff0c;包括复杂的配置过程、性能优化和成本管理等问题。同时&#xff0c;确保数据在传输和存储过程中的安全性&#xff0c;并满足合规性要求&#xff0c;也是一个重要…

深入理解TCP协议:工作原理、报文结构及应用场景

TCP协议详解 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是因特网协议套件中最重要的协议之一。它为应用程序提供了可靠、面向连接的通信服务。TCP协议确保数据包按顺序到达&#xff0c;并且没有丢失或重复。本文将详细介绍TCP协议的工…

纯前端实现导出excel

项目背景&#xff1a; vue2 插件&#xff1a; xlsx&#xff1b;xlsx-style&#xff1b;file-saver 说明&#xff1a; 单独使用 xlsx插件&#xff0c;也可以将网页上的table导出成excel&#xff0c;但是导出的excel&#xff0c;没有样式 结合xlsx-style&#xff1b;file-saver&a…

Zookeeper 集群节点故障剔除、切换、恢复原理

Zookeeper 集群节点故障剔除、切换、恢复原理 zookeeper 集群节点故障时,如何剔除节点,如果为领导节点如何处理,如何进行故障恢 复的,实现原理? 在 Zookeeper 集群中,当节点故障时,集群需要自动剔除故障节点并进行故障恢复,确保集群的高 可用性和一致性。具体来说,…

手机数据如何恢复?11 款最佳安卓手机恢复软件

媒体可能由于各种原因而从您的设备中删除&#xff0c;可能是意外或病毒攻击。 在这些情况下&#xff0c;照片恢复应用程序是唯一的解决方案。理想的照片恢复应用程序取决于各种因素&#xff0c;例如存储设备的损坏程度、删除照片后的持续时间以及应用程序使用的恢复算法的有效性…

【案例分析:基于 Python 的几种神经网络构建 一维的和二维的全介质和金属SPR 材料的光谱预测与逆向设计】

案例分析&#xff1a;传播相位与几何相位超构单元仿真与器件库提取与二维超构透镜设计与传播光场仿真 案例分析&#xff1a; 片上的超构单元仿真与光学参数提取 案例分析&#xff1a;基于粒子群方法的耦合器设计 案例分析&#xff1a;基于 Python 的几种神经网络构建 一维的和二…

【netty】三万字详解!JAVA高性能通信框架,关于netty,看这一篇就够了

目录 1.概述 2.hello world 3.EventLoop 4.channel 4.1.同步 4.2.异步 4.3.调试 4.4.关闭 4.5.为什么要用异步 5.future 6.promise 7.pipeline 8.byteBuf 8.1.创建 8.2.内存模式和池化 8.2.1.内存模式 8.2.2.池化 8.3.组成 8.4.操作 8.4.1.读写 8.4.2.释放…

Zookeeper 集群节点选举原理实现(二)

Zookeeper 集群节点选举原理实现(二) #集群中每个 zookeeper 节点zxid 如何实现的? ZXID(Zookeeper Transaction ID) ZXID(Zookeeper Transaction ID)是 Zookeeper 中用于唯一标识每个事务的 ID。 它是一个 64 位的数字,表示事务的顺序。ZXID 在 Zookeeper 中有两个主…

深度学习神经网络协同过滤模型(NCF)与用户协同过滤(UCF)的区别

一、效果图 点我查看在线demo 二、启发式推荐系统 推荐系统的核心是根据用户的兴趣需求&#xff0c;给用户推荐喜欢的内容。常用的推荐算法有启发式推荐算法&#xff0c;可分为基于用户的 协同过滤&#xff0c;基于物品的协同过滤。 1、基于用户的协同过滤&#xff08;UCF…

【笔记】打卡01 | 初学入门

初学入门:01-02 01 基本介绍02 快速入门库处理数据集网络构建模型训练保存模型加载模型打卡-时间 01 基本介绍 MindSpore Data&#xff08;数据处理层&#xff09; ModelZoo&#xff08;模型库&#xff09; MindSpore Science&#xff08;科学计算&#xff09;&#xff0c;包含…

DP:完全背包+多重背包问题

完全背包和01背包的区别就是&#xff1a;可以多次选 一、完全背包&#xff08;模版&#xff09; 【模板】完全背包_牛客题霸_牛客网 #include <iostream> #include<string.h> using namespace std; const int N1001; int n,V,w[N],v[N],dp[N][N]; //dp[i][j]表示…

openstack使用

1.若虚机带磁盘&#xff0c;先卸载磁盘 for i in cinder list --all-tenants |awk {print $2} |grep -v ID|grep -v ^$; do jcinder list --all |grep $i | awk {print $16} |grep -v Attached|grep -v ^$ nova volume-detach $j $i done 2.删除磁盘 for i in cinder list …

【机器学习 复习】第6章 支持向量机(SVM)

一、概念 1.支持向量机&#xff08;support vector machine&#xff0c;SVM&#xff09;&#xff1a; &#xff08;1&#xff09;基于统计学理论的监督学习方法&#xff0c;但不属于生成式模型&#xff0c;而是判别式模型。 &#xff08;2&#xff09;支持向量机在各个领域内的…

CentOS Linux 7系统中离线安装MySQL5.7步骤

预计数据文件存储目录为&#xff1a;/opt/mysql/data 1、文件下载&#xff1a; 安装文件下载链接&#xff1a;https://downloads.mysql.com/archives/community/ 2、检查当前系统是否安装过MySQL [rootcnic51 mysql]# rpm -qa|grep mariadb mariadb-libs-5.5.68-1.el7.x86_6…

详释 Promise

当涉及到处理异步操作时&#xff0c;JavaScript 中的 Promise 是一个非常强大且常用的工具。下面详细解释 Promise 的相关内容&#xff0c;并举例说明&#xff1a; 1. 解决回调地狱的问题 Promise 的链式调用风格可以有效解决回调地狱的问题&#xff0c;使得代码更加清晰和易…

xss-lab靶场level1-level10

level1&#xff1a; 无过滤形式 直接 <script>window.alert(123)</script> level2: htmlspecialchars函数将预定义的小于和大于号转换为html实体 < &#xff08;小于&#xff09;成为 < > &#xff08;大于&#xff09;成为 > 源代码 <?…

深入探索Llama 2:下一代开源语言模型的革新与影响

Llama 2是Meta AI发布的一款先进的开源大模型&#xff0c;属于大型语言模型&#xff08;LLM&#xff09;类别。它是Transformer架构的一种变体&#xff0c;经过预先训练并在多种文本和代码数据集上进行微调&#xff0c;旨在提升功能和安全性。Llama 2的关键特点包括&#xff1a…

06. 多线程 yield 方法

1. 前言 本节对 yield 方法进行深入的剖析&#xff0c;主要内容点如下&#xff1a; 首先要了解什么是 CPU 执行权&#xff0c;因为 yield 方法与 CPU 执行权息息相关&#xff1b;了解 yield 方法的作用&#xff0c;要明确 yield 方法的使用所带来的运行效果&#xff1b;了解什…

简述http/https、tcp/ip、SSL/TLS介绍

HTTP/HTTPS、TCP和IP之间的区别主要体现在它们的作用层次、功能特点以及在网络通信中的角色。 一. 作用层次&#xff1a; HTTP/HTTPS&#xff1a;HTTP&#xff08;Hyper Text Transfer Protocol&#xff0c;超文本传输协议&#xff09;和HTTPS&#xff08;HTTP Secure&#x…