C++ 工程实践(6):单元测试如何 mock 系统调用

【C/C++】单元测试实战:Stub与Mock框架解析 单元测试框架 阅读详情

陈硕 (giantchen_AT_gmail)

Blog.youkuaiyun.com/Solstice

陈硕关于 C++ 工程实践的系列文章: http://blog.youkuaiyun.com/Solstice/category/802325.aspx

陈硕博客文章合集下载: http://blog.youkuaiyun.com/Solstice/archive/2011/02/24/6206154.aspx

本作品采用“Creative Commons 署名-非商业性使用-禁止演绎 3.0 Unported 许可协议(cc by-nc-nd)”进行许可。http://creativecommons.org/licenses/by-nc-nd/3.0/

摘要:本文讨论了在编写单元测试时 mock 系统调用(以及其他第三方库)的几种做法。

本文只考虑 Linux x86/amd64 平台。

陈硕在《分布式程序的自动化回归测试》 http://blog.youkuaiyun.com/Solstice/archive/2011/04/25/6359748.aspx 一文中曾经谈到单元测试在分布式程序开发中的优缺点(好吧,主要是缺点)。但是,在某些情况下,单元测试是很有必要的,在测试 failure 场景的时候尤显重要,比如:

  • 在开发存储系统时,模拟 read(2)/write(2) 返回 EIO 错误(有可能是磁盘写满了,有可能是磁盘出坏道读不出数据)。
  • 在开发网络库的时候,模拟 write(2) 返回 EPIPE 错误(对方意外断开连接)。
  • 在开发网络库的时候,模拟自连接 (self-connection),网络库应该用 getsockname(2) 和 getpeername(2) 判断是否是自连接,然后断开之。
  • 在开发网络库的时候,模拟本地 ephemeral port 用完,connect(2) 返回 EAGAIN 临时错误。
  • 让 gethostbyname(2) 返回我们预设的值,防止单元测试给公司的 DNS server 带来太大压力。

这些 test case 恐怕很难用前文提到的 test harness 来测试,该单元测试上场了。现在的问题是,如何 mock 这些系统函数?或者换句话说,如何把对系统函数的依赖注入到被测程序中?

系统函数的依赖注入

在Michael Feathers 的《修改代码的艺术 / Working Effectively with Legacy Code》一书第 4.3.2 节中,作者介绍了链接期接缝(link seam),正好可以解决我们的问题。另外,在 Stack Overflow 的一个帖子里也总结了几种做法:http://stackoverflow.com/questions/2924440/advice-on-mocking-system-calls

如果程序(库)在编写的时候就考虑了可测试性,那么用不到上面的 hack 手段,我们可以从设计上解决依赖注入的问题。这里提供两个思路。

其一,采用传统的面向对象的手法,借助运行期的迟绑定实现注入与替换。自己写一个 System interface,把程序里用到的 open、close、read、write、connect、bind、listen、accept、gethostname、getpeername、getsockname 等等函数统统用虚函数封装一层。然后在代码里不要直接调用 open(),而是调用 System::instance().open()。

这样代码主动把控制权交给了 System interface,我们可以在这里动动手脚。在写单元测试的时候,把这个 singleton instance 替换为我们的 mock object,这样就能模拟各种 error code。

其二,采用编译期或链接期的迟绑定。注意到在第一种做法中,运行期多态是不必要的,因为程序从生到死只会用到一个 implementation object。为此付出虚函数调用的代价似乎有些不值。(其实,跟系统调用比起来,虚函数这点开销可忽略不计。)

我们可以写一个 system namespace 头文件,在其中声明 read() 和 write() 等普通函数,然后在 .cc 文件里转发给对应系统的系统函数 ::read() 和 ::write() 等。

// SocketsOps.h
namespace sockets
{
  int connect(int sockfd, const struct sockaddr_in& addr);
}

// SocketsOps.cc
int sockets::connect(int sockfd, const struct sockaddr_in& addr)
{
  return ::connect(sockfd, sockaddr_cast(&addr), sizeof addr);
}

此处的代码来自 muduo 网络库

http://code.google.com/p/muduo/source/browse/trunk/muduo/net/SocketsOps.h
http://code.google.com/p/muduo/source/browse/trunk/muduo/net/SocketsOps.cc

有了这么一层间接性,就可以在编写单元测试的时候动动手脚,链接我们的 stub 实现,以达到替换实现的目的:

// MockSocketsOps.cc
int sockets::connect(int sockfd, const struct sockaddr_in& addr)
{
  errno = EAGAIN;
  return -1;
}

C++ 一个程序只能有一个 main() 入口,所以要先把程序做成 library,再用单元测试代码链接这个 library。假设有一个 mynetcat 程序,为了编写 C++ 单元测试,我们把它拆成两部分,library 和 main(),源文件分别是 mynetcat.cc 和 main.cc。

在编译普通程序的时候:

g++ main.cc mynetcat.cc SocketsOps.cc -o mynetcat

在编译单元测试时这么写:

g++ test.cc mynetcat.cc MockSocketsOps.cc -o test

以上是最简单的例子,在实际开发中可以让 stub 功能更强大一些,比如根据不同的 test case 返回不同的错误。这么做无需用到虚函数,代码写起来也比较简洁,只用前缀 sockets:: 即可。例如应用程序的代码里写 sockets::connect(fd, addr)。

muduo 目前还没有单元测试,只是预留了这些 stubs。

namespace 的好处在于它不是封闭的,我们可以随时打开往里添加新的函数,而不用改动原来的头文件(该文件的控制权可能不在我们手里)。这也是以 non-member non-friend 函数为接口的优点。


以上两种做法还有一个好处,即只 mock 我们关心的部分代码。如果程序用到了 SQLite 或 Berkeley DB 这些会访问本地文件系统的第三方库,那么我们的 System interface 或 system namespace 不会拦截这些第三方库的 open(2)、close(2)、read(2)、write(2) 等系统调用。

链接期垫片 (link seams)

如果程序在一开始编码的时候没有考虑单元测试,那么又该如何注入 mock 系统调用呢?上面第二种做法已经给出了答案,那就是使用 link seam (链接期垫片)。

比方说要 mock connect(2) 函数,那么我们自己在单元测试程序里实现一个 connect() 函数,在链接的时候,会优先采用我们自己定义的函数。(这对动态链接是成立的,如果是静态链接,会报  multiple definition 错误。好在绝大多数情况下 libc 是动态链接的。)

typedef int (*connect_func_t)(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect_func_t connect_func = dlsym(RTDL_NEXT, "connect");

bool mock_connect;
int mock_connect_errno;

// mock connect
extern "C" int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
  if (mock_connect) {
    errno = mock_connect_errno;
	return errno == 0 ? 0 : -1;
  } else {
    return connect_func(sockfd, addr, addrlen);
  }
}


如果程序真的要调用 connect(2) 怎么办?在我们自己的 mock connect(2) 里不能再调用 connect() 了,否则会出现无限递归。为了防止这种情况,我们用 dlsym(RTDL_NEXT, "connect") 获得 connect(2) 系统函数的真实地址,然后通过函数指针 connect_func 来调用它。

例子:ZooKeeper 的 C client library

ZooKeeper 的 C client library 正是采用了 link seams 来编写单元测试,代码见:

http://svn.apache.org/repos/asf/zookeeper/tags/release-3.3.3/src/c/tests/LibCMocks.h
http://svn.apache.org/repos/asf/zookeeper/tags/release-3.3.3/src/c/tests/LibCMocks.cc

其他手法

Stack Overflow 的帖子里还提到一个做法,可以方便地替换动态库里的函数,即使用 ld 的 --wrap 参数,
文档里说得很清楚,这里不再赘述。

       --wrap=symbol
           Use a wrapper function for symbol.  Any undefined reference to
           symbol will be resolved to "__wrap_symbol".  Any undefined
           reference to "__real_symbol" will be resolved to symbol.

           This can be used to provide a wrapper for a system function.  The
           wrapper function should be called "__wrap_symbol".  If it wishes to
           call the system function, it should call "__real_symbol".

           Here is a trivial example:

                   void *
                   __wrap_malloc (size_t c)
                   {
                     printf ("malloc called with %zu/n", c);
                     return __real_malloc (c);
                   }

           If you link other code with this file using --wrap malloc, then all
           calls to "malloc" will call the function "__wrap_malloc" instead.
           The call to "__real_malloc" in "__wrap_malloc" will call the real
           "malloc" function.

           You may wish to provide a "__real_malloc" function as well, so that
           links without the --wrap option will succeed.  If you do this, you
           should not put the definition of "__real_malloc" in the same file
           as "__wrap_malloc"; if you do, the assembler may resolve the call
           before the linker has a chance to wrap it to "malloc".

第三方 C++ 库

link seam 同样适用于第三方 C++ 库

比方说公司某个基础库团队提供了了 File class,但是这个 class 没有使用虚函数,我们无法通过 sub-classing 的办法来实现 mock object。

class File : boost::noncopyable
{
 public:
  File(const char* filename);
  ~File();
  
  int readn(void* data, int len);
  int writen(const void* data, int len);
  size_t getSize() const;
 private:
};

如果需要为用到 File class 的程序编写单元测试,那么我们可以自己定义其成员函数的实现,这样可以注入任何我们想要的结果。

// MockFile.cc
int File::readn(void* data, int len)
{
  return -1;
}

(这个做法对动态库是可行的,静态库会报错。我们要么让对方提供专供单元测试的动态库,要么拿过源码来自己编译一个。)


Java 也有类似的做法,在 class path 里替换我们自己的 stub jar 文件,以实现 link seam。不过 Java 有动态代理,很少用得着 link seam 来实现依赖注入。

Google Mock(GMock):C++单元测试的高效模拟框架详解 Google Mock(GMock)是C++单元测试中的核心工具,能够高效隔离外部依赖并验证复杂交互逻辑。本文详细介绍了GMock的核心功能、典型使用场景、高级用法及注意事项,帮助开发者掌握如何利用GMock构建灵活、可靠的单元测试框架。通过模拟对象创建、行为控制、调用验证等功能,GMock能够显著提升测试的精细度和场景覆盖能力,适用于复杂依赖链测试、异常边界测试及性能敏感测试等场景。 阅读详情

相关推荐

C++单元测试利器Mockcpp:从原理到实战的Mock框架深度解析

在软件工程实践中,单元测试是保障代码质量的核心环节,而依赖隔离是其中的关键技术挑战。通过模拟外部依赖对象的行为,测试替身技术能够将被测代码与数据库、网络服务等复杂依赖解耦,从而构建快速、稳定、可重复的测试环境。Mock框架作为测试替身的高级实现,不仅能够模拟依赖对象的行为,还能精确验证方法调用、参数传递等交互细节,这对于测试驱动开发和持续集成流程具有重要价值。在C++生态中,由于语言特性的复杂性,实现灵活高效的Mock机制尤为关键。Mockcpp作为专为C++设计的Mock框架,通过虚函数表拦截等核心机制,

weixin_34245082的博客 288

C++类对象单元测试中的Mock使用

文章目录前言1. 下载网址2. 为什么选择Google Mock3. C++简单的例子3.1 使用流程4. 指定自己的期望 前言   在进行单元测试时,我们想要测试自己缩写函数A,但是函数A却依赖于函数B,当函数B无法满足预期时就无法对函数A进行测试,主要由于下面几个原因: 函数B依赖于硬件设备 真实的函数B的返回值无法满足我们的预期 团队开发中函数B尚未实现   这时就需要对函数B进行打桩(仿真mock),使其达到我们预期的效果。 但是如同下面这种类型函数时,则需要深入下去,对基层的函数进行打桩: t

lzg_na的博客 1万+

CppUMockGen:用于CppUTestCppUMock的模拟生成器

CppUMockGen 用于模拟生成器。 什么是CppUMockGen? CppUMockGen自动为C / C ++头文件中定义的成员和非成员函数生成模拟,从而避免了在使用CppUTest / CppUMock单元测试框架编写单元测试时必须手动编写这些模拟的负担。 CppUMockGen还会自动生成期望函数,以改善和简化测试中函数的期望声明,从而使期望声明更短且更不易出错。 为什么要使用CppUMockGen? 在现实世界中,项目具有经常更改的许多组件(例如,类)的接口,将有许多必须创建和维护的模拟功能和单元测试。 CppUTest / CppUMock提供了一种非常强大的机制,可以使用MockSupport::actualCall为C / C ++函数定义MockSupport::actualCall并使用MockSupport::expectOneCall / MockSu

单体测试:mock外部依赖对象,并注入到我们的业务类中

实体类: public class ConvertController { @Resource private ConvertServiceImpl convertservice; public APIResult convert() throws Exception { APIResult result = null; result = convertservice.getC...

蜡笔没了小新 1545

IEPE传感器4mA恒流源接口电路

另外传感器的偏置电压大概在10V多点,一般接个电容把直流分量去掉。这里我们要把恒流源的电流设置在4毫安左右,R1取33Ω差不多正好。经过CR的隔直流电路后的波形是这样的。

老白的博客 6112

Android SSL证书固定实战:原理、方案与避坑指南

SSL/TLS协议是保障网络通信安全的基石,其核心原理是通过非对称加密和数字证书实现身份认证与数据加密。在移动应用开发中,为了防止中间人攻击,证书固定技术应运而生,它通过将服务器证书或公钥的哈希值预置在应用内,强制校验服务器身份,从而大幅提升通信链路的安全性。这一技术对于金融、电商等高安全要求场景尤为重要。本文聚焦Android平台,深入解析SPKI固定、Network Security Configuration与OkHttp CertificatePinner等主流方案,并针对证书更新、调试兼容等常见痛

weixin_33935636的博客 403

实现无入侵式C++代码mock工具

为了实现真正无侵入式的mock,我们基于开源Hook框架Frida-gum提供的API,利用C++模板进行封装,作者编写了一个简单实用的mock工具,在此开源分享(代码详见附录)。背景在单元测试中,往往需要减少被测函数的外部依赖,如网络访问、数据库访问等。我们希望有一个mock工具能让我们轻松地屏蔽掉外部依赖。C++的开源mock工具比较少,而且大多是基于多态实现的(如gmock),只支持mock...

淘系技术 3962

SIwave仿真手册——电源完整性仿真之PDN阻抗的提取(四)

(想不到我这个弱鸡的博主有一天也有被催更的时候,看到后台被催的消息,我慌了,赶紧写一篇把。) 文章目录3.1 PDN阻抗的提取3.1.1手动完成PDN阻抗的提取1、创建pin group2、创建端口3、数据文件的清理和检查(参考1.3.4&1.3.5)4、添加电容模型(参考2.2.1)(==这一步很重要==)5、开始仿真6、查看仿真结果3.1.2利用向导工具完成PDN阻抗的提取1、点击图标![在这里插入图片描述](https://img-blog.csdnimg.cn/36d974339ea5449

luohuo9844的博客 1万+

告别测试困境:C++单元测试框架与Mock技术全指南

你是否还在为C++项目的单元测试而烦恼?是否因依赖复杂难以隔离而放弃测试?本文将带你系统掌握主流C++测试框架与Mock技术,让测试效率提升300%。读完本文,你将能够:选择适合项目的测试框架、编写可维护的测试用例、使用Mock技术隔离外部依赖、构建完整的测试体系。 ## 测试框架选型指南 C++生态中有多种成熟的测试框架,各具特色。以下是工业界最常用的几种框架对比: ### 轻量级首选:d...

gitblog_01194的博客 1228

别再只怪贴片厂了!从MLCC内部裂纹,看懂物料质量与工艺应力的博弈

本文深入分析了MLCC内部裂纹的失效原因,提供了从裂纹形态学到生产全流程的失效风险地图的完整分析框架。通过实战案例和检测技术组合,帮助工程师精准锁定MLCC失效背后的真凶,优化物料质量和工艺应力管理。

weixin_33122947的博客 293

Apache Hop环境搭建实战:从零到一的下载、安装与配置指南

本文详细介绍了Apache Hop环境搭建的全过程,包括下载、安装与配置指南。从选择正确的JDK17版本到设置关键环境变量,再到启动GUI界面和解决常见问题,提供了全面的实战指导。适合数据工程师和ETL开发者快速上手这一强大的数据集成工具。

weixin_42634811的博客 574

如何在单元测试中mock系统调用

今天看了陈硕(www.chenshuo.com)的一篇blog,有提及单元测试mock系统调用的问题 StackOverFlow网站上有个用户想要mock系统函数getaddinfo,有以下几种方案: Option 1: Subclass to Test Since you already have your object in a class, you can subclass to te

pecywang的专栏 2140

Mamba-YOLO实战:环境配置+模型训练+精度调优全流程指南(附Colab代码)

在目标检测领域,一场由状态空间模型引发的技术革新正在悄然改写行业格局。近期国际顶级学术期刊连续发布的YOLOv5_mamba、FER-YOLO等突破性成果,不仅刷新了检测精度记录,更开创了结构化状态空间模型(SSM)与经典检测框架的创新融合范式。当前该领域涌现的十大标杆性研究,已在以下维度取得突破性进展:√ 遮挡目标识别准确率提升38%√ 小样本场景泛化能力增强2.7倍√ 视频流检测延迟降低至17ms√ 多目标交叉干扰抑制效果达SOTA。

2501_90555291的博客 1582

google mock C++单元测试框架

Google Mock 入门概述什么是Mock?Mock,更确切地说应该是Mock Object。它究竟是什么?它有什么作用?在这里,我也只能先说说我的理解。 比如当我们在单元测试、模块的接口测试时,当这个模块需要依赖另外一个/几个类,而这时这些个类还没有开发好(那名开发同学比较懒,呵呵),这时我们就可以定义了Mock对象来模拟那些类的行为。说得更直白一些,就是自己实现一个假的依赖类,对这个类的方...

下一站浮华的博客 9227

分布式 Agent 系统设计:高并发场景下的挑战与解决方案

分布式 Agent 系统由多个分布在不同节点上的智能 Agent 组成,这些 Agent 具有自主决策能力,能够通过通信协作完成复杂任务。每个 Agent 可以看作是一个独立的实体,具备感知环境、处理信息、执行动作的功能,它们相互协作,共同实现系统的整体目标。分布式 Agent 系统在高并发场景下面临着资源调度、通信延迟、状态一致性、容错处理和性能瓶颈等诸多挑战。通过采用动态资源分配、高效通信协议、分布式共识算法、冗余备份等解决方案,可以有效应对这些挑战,提高系统的性能、可靠性和可用性。

shuizhudan223的博客 1292

OPENSSL使用方法

预备知识KEY 通常指私钥。CSR 是 Certificate Signing Request 的缩写,即证书签名请求,这不是证书,只是包含申请证书的基本信息。生成证书时要把这个提交给权威的证书颁发机构,颁发机构审核通过之后,再根据这些申请信息生成相应的证书。CRT 即 certificate的缩写,即证书。 X.509 是一种证书格式。对X.509证书来说,认证者总是CA或由CA指定的人,一份...

zxm8513优快云博客 1212

C++中编写UT单元测试用例时如何mock非虚函数?

摘要: 在C++单元测试中,使用GoogleTest的GMock通常只能mock虚函数。针对非虚函数的mock,官方推荐通过模板参数实现依赖注入。此外,开源项目cpp-stub提供了更灵活的解决方案,支持对各类函数(包括非虚成员函数、静态函数、构造函数等)进行mock。该工具通过获取函数地址并替换为桩函数实现mock,支持多种平台和编译器,但无法mock纯虚函数、lambda和部分系统调用。使用时需注意编译选项优化和代码覆盖率收集方法。

ccf19881030的专栏 1496

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

单元测试之mock使用

Mock技术助力高效单元测试 在多层方法调用的复杂程序中,单元测试常受依赖关系限制。Mock技术通过模拟对象返回结果,能有效解决这个问题。其主要优势包括:1)测试效率高,无需启动Spring或连接数据库;2)支持测试驱动开发;3)便于团队并行开发;4)摆脱环境依赖。实现原理是使用桩(Stub)技术动态替换程序功能。实际应用中可通过Mockito等工具模拟方法返回值、异常情况,PowerMock还能处理静态方法调用。这种技术显著提升了单元测试的独立性和可靠性,是软件开发过程中的重要实践。

nhb687096的博客 1442

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

C++单元测试!写的很好!!转

C++单元测试的一点感悟置顶 2015年01月22日 15:37:52阅读数:2872      之前一直在寻找一种合适的方法来做C++单元测试,也尝试了不少的方法。写一点体会提供大家参考(不一定是最好的,但是我想还是能给大家一些启发吧)。JAVA和C#都有强大的IDE支持,而且JAVA和C#的反射机制能够使得Mock更加容易一些。但是由于C/C++语言的独特性,单元测试的过程变得不那么的顺手,特...

下一站浮华的博客 2万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

C++单元测试中Mock外部依赖的方法

博客搬家,原文链接 本文只涉及在Makefile编译环境下在UT中Mock外部编译依赖的方法。 背景 假设现在有一个模块A,目录结构如下: A/Makeifle A/include/a.h A/src/a.cpp Makefile依赖了另外一半模块B,目录与A同级,目录架构如下: B/include/b.h B/lib/libb.a 这里B作为A的外部依赖模块而存在,

Gentle_bird的专栏 1886

【C++ 单元测试】Google Mock 模拟对象指南

在软件开发中,**单元测试**是确保代码质量和可靠性的关键步骤。C++ 开发者常使用 **Google Test**(gtest)框架进行单元测试,而 **Google Mock** 则是其强大的子模块,专门用于创建和管理模拟对象(Mock Objects)。如同哲学家康德所言:“科学是组织化的知识。”在这一章节中,我们将系统地探讨 Google Mock 的基础知识,帮助你构建稳健的单元测试。

探索C++编程的奥秘,分享深入的技术见解和实践,旨在激发读者创造力与解决问题的思维。 2139
上一篇: 分布式系统部署、监控与进程管理的几重境界
下一篇: Muduo 网络编程示例之九:简单的消息广播服务
陈硕
博客等级 码龄26年 1万+粉丝 144原创
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值