Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >mock C++ function for unit test

mock C++ function for unit test

作者头像
byronhe
发布于 2021-06-25 02:49:58
发布于 2021-06-25 02:49:58
9930
举报
文章被收录于专栏:Tech ExplorerTech Explorer

单元测试中,我们需要提供业务逻辑的mock版本, 当业务逻辑实现为C++的virtual function时,这是很容易的,我们只需要写一个子类, 实现virtual function就行了,Google 的 gmock就针对这种情况设计。

可是,如果遗留代码中有一般C函数,非virtual的类成员函数,模板函数,inline函数,如何提供mock版本呢?

下面的代码用一点trick实现了上述函数的运行时mock。

原理是,在运行时,修改目标函数的机器码,改为jmp到mock版本的函数中。

实现如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

#include <stdint.h> #include <iostream> #include <string> #include <unistd.h> #include <string.h> #include <sys/mman.h> #include "patch_elf.h" using namespace std; int print_op(void * addr,int leng){ unsigned char * op=(unsigned char *) addr; cout<<endl<<"addr:"<<addr<<" code:"<<endl; for(int i=0;i<leng;++i){ cout<<"0x"<<hex<<(unsigned int)op[i]<<" "; } cout<<endl; return 0; } int patch_func(void * original,void * mock){ /* cout<<endl<<"----------------------------------------------------------------------" <<endl<<__func__<<" ,i am going to patch "<<original<<" to "<<mock <<endl; */ //rax 用于保存函数调用的返回值,所以可以占用 //4010e1: b8 20 0c 40 00 mov $0x400c20,%eax //4010e6: ff e0 jmpq *%rax uint32_t addr=(uint32_t)(uint64_t)mock; const int code_len=7; char inject_code[code_len]={0xb8,0x00,0x00,0x00,0x00,0xff,0xe0}; memcpy(&inject_code[1],(char*)&addr,4); //print_op(inject_code,code_len); //接下来,把inject_code复制到original这个位置 //print_op(original,100); //首先,要改掉内存的权限,增加写权限 const size_t length = sysconf(_SC_PAGESIZE); void * code_addr= (void*) ( ( (long) original/length)*length ); int ret=mprotect(code_addr, length, PROT_READ | PROT_WRITE | PROT_EXEC); if ( 0!=ret ) { cerr<<"mprotect failed! ret="<<ret<<endl; } //修改代码 memcpy( original,inject_code,code_len); //再去掉写权限 ret=mprotect(code_addr, length, PROT_READ | PROT_EXEC); if ( 0!=ret ) { cerr<<"mprotect failed! ret="<<ret<<endl; } //print_op(original,100); //cout<<"----------------------------------------------------------------------" // <<endl<<endl; return 0; }

测试了

1.一般函数 2.inline函数 3.一般成员函数 4.模板函数

并在 :32位,64位; -O2, -O0,参数下编译

除了 inline函数没办法,其它的都有效

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

#pragma once #include <iostream> #include <cstdio> using namespace std; class ST1{ public: uint32_t a; uint64_t b; char c[200]; double d; ST1 * e; ST1():a(0),b(0),d(0),e(0){ c[0]=0; } //类的成员函数 int member_func(); int member_func_mock(); }; int member_func_extern(ST1 * st); //一般函数 int original_func(ST1 * para1,ST1 para2,void * para3); int mock_func(ST1 * para1,ST1 para2,void * para3); //int ref_func(ST1 * para1,ST1 para2,void * para3); class Base{ private: uint32_t b; ST1 st; public: uint32_t a; Base():b(0),a(0){} }; //inline 函数 inline int inline_func(int a,int b){ int c=a+b+ 0x1111 * a + b/0x1111; printf("%s %d\n",__func__,c); return c; } inline int inline_func_mock(int a,int b){ int c=a+b+100; printf("%s %d\n",__func__,c); return c; } //模板函数 template <typename T> uint32_t get_member_a(T & t){ cout<<__func__<<" a="<<t.a<<endl; return t.a; } template <typename T> uint32_t get_member_b(T & t){ cout<<__func__<<" b="<<t.b<<endl; return t.b; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

#include <stdint.h> #include <iostream> #include <string> #include <unistd.h> #include "func.h" using namespace std; int original_func(ST1 * para1,ST1 para2,void * para3){ cout<<__func__<<"\tcalled! " <<" a+a "<<para1->a+para2.a <<" b+b "<<para1->b+para2.b <<" c+c "<<para1->c<<para2.c <<" d+d "<<para1->d+para2.d <<" e+e "<<para1->e<<para2.e <<para3 <<endl; return 0; } int mock_func(ST1 * para1,ST1 para2,void * para3){ cout<<__func__<<"\tcalled!" <<endl; return 0; } int ref_func(ST1 * para1,ST1 para2,void * para3){ return mock_func(para1,para2,para3); } int ST1::member_func(){ cout<<__func__<<" called! " <<" a="<< this->a <<" b="<< this->b <<" c="<< this->c <<" d="<< this->d <<endl; return 0; } int ST1::member_func_mock(){ cout<<__func__<<" called! i do nothing." <<endl; return 0; } int member_func_extern(ST1 * st){ cout<<__func__<<" called! i am not member function." <<endl; return 0; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

#include <stdint.h> #include <iostream> #include <string> #include <unistd.h> #include <string.h> #include <sys/mman.h> #include "func.h" #include "patch_elf.h" using namespace std; int test(){ ST1 s1,s2; char str[]="hello"; s1.a=s1.b=s1.d=1; s1.e=NULL; s2.a=s2.b=s2.d=2; s1.e=NULL; cout<<"----------------------------------------------------------------------"<<endl; //mock original_func,替换成mock_func original_func(&s1,s2,&str[0]); patch_func((void*)&original_func, (void*)&mock_func); original_func(&s1,s2,&str[0]); cout<<"----------------------------------------------------------------------"<<endl; //mock inline 函数貌似不行 int a=s1.a, b=s1.b; inline_func(a,b); patch_func( (void*) &inline_func, (void*) &inline_func_mock); inline_func(a,b); cout<<"----------------------------------------------------------------------"<<endl; s1.member_func(); patch_func( (void*) &ST1::member_func, (void*) &ST1::member_func_mock); s1.member_func(); patch_func( (void*) &ST1::member_func, (void*) &member_func_extern); s1.member_func(); cout<<"----------------------------------------------------------------------"<<endl; get_member_a(s1); patch_func( (void*) & get_member_a<ST1> , (void*) & get_member_b<ST1>); get_member_a(s1); return 0; } int main(){ test(); return 0; }

可以参考

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2014-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C++基础知识(入门章2)
继上一期内容,今天我们将继续来了解C++中的其它基础知识点,话不多说,希望这期内容能得到大家的支持~~
用户11289931
2024/09/24
1200
C++基础知识(入门章2)
[C++] 由C语言过渡到C++的敲门砖
在C/C++中,变量、函数和后⾯要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全 局作⽤域中,可能会导致很多冲突。使⽤命名空间的⽬的是对标识符的名称进⾏本地化,以避免命名冲突或名字污染 。
DevKevin
2024/07/12
1410
[C++] 由C语言过渡到C++的敲门砖
在全志R128上进行HiFi5语音算法部署
在 lichee/rtos-components/thirdparty/Kconfig 中追加:
阿志小管家
2024/02/02
1970
C++ 动态新闻推送 第15期
从reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态。
王很水
2021/08/31
4200
【C++】入门基础(下)
C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能具有重叠性。但是各有自己的特点,互相不可替代。
云边有个稻草人
2024/10/21
830
【C++】入门基础(下)
​浅谈协程
作者:kylinkzhang,腾讯 CSIG 后台开发工程师 什么是协程 我们可以简单的认为:协程就是用户态的线程,但是上下文切换的时机是靠调用方(写代码的开发人员)自身去控制的。 同时,协程和用户态线程非常接近,用户态线程之间的切换不需要陷入内核,但部分操作系统中用户态线程的切换需要内核态线程的辅助。 下面是一个简单的例子: void A() {    cout << 1 << " ";    cout << 2 << " ";    cout << 3 << " "; } void B() {    
腾讯技术工程官方号
2022/07/01
2K0
​浅谈协程
C++入门
        C++ 也就是 c语言plus plus版本 所以兼容很多C语言内容依然可以使用。
2024/08/06
1310
C++入门
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
waves浪游
2024/11/01
1210
C++入门
蓝牙协议栈开发板 STM32F1 跑蓝牙协议栈 –传统蓝牙搜索演示以及实现原理[通俗易懂]
第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。
全栈程序员站长
2022/07/31
1.2K0
蓝牙协议栈开发板 STM32F1 跑蓝牙协议栈 –传统蓝牙搜索演示以及实现原理[通俗易懂]
C++ 中文周刊 2024-03-03 第150期
RSS https://github.com/wanghenshui/cppweeklynews/releases.atom
王很水
2024/07/30
1270
C++ 中文周刊 2024-03-03 第150期
手撕C++入门基础
在C/C++中,变量、函数和后⾯要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全局作⽤域中,可能会导致很多冲突。使⽤命名空间的⽬的是对标识符的名称进⾏本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
Undoom
2024/09/23
1540
手撕C++入门基础
【C++】基础入门
C++兼容C语⾔绝⼤多数的语法,所以C语⾔实现的hello world依旧可以运⾏,C++中需要把定义⽂件代码后缀改为.cpp,vs编译器看到是.cpp就会调⽤C++编译器编译,linux下要⽤g++编译,不再是gcc。当然C++有⼀套⾃⼰的输⼊输出,严格说C++版本的hello world应该是这样写的。(后续会讲到)。如下:
用户11375356
2024/11/22
930
【C++】基础入门
手撕C++类和对象(中)
默认成员函数就是⽤⼾没有显式实现,编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类,我 们不写的情况下编译器会默认⽣成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最 后两个取地址重载不重要,我们稍微了解⼀下即可。其次就是C++11以后还会增加两个默认成员函数, 移动构造和移动赋值,这个我们后⾯再讲解。默认成员函数很重要,也⽐较复杂,我们要从两个⽅⾯ 去学习:
Undoom
2024/09/23
1210
手撕C++类和对象(中)
[C++入门]C++入门建议与C++入门基础
无论是哪种语言的学习,都有优劣之分,java简单只是简单在好理解,但是学完完整的技术栈你就会发现,无论那种语言都很难学难精。
用户11367452
2024/11/21
650
[C++入门]C++入门建议与C++入门基础
【C++入门篇】C++入门基础[必备知识点]
举个例子:在中学时期,我们会给关系好的朋友起一个外号,比如”黑狗“,“阿白”比较有特点的外号以证明关系很铁。那么在日常生活中,我们叫他的真名和他的外号是不是本意都是指向同一个人。对滴!!!这个外号跟别名有着异曲同工之妙!!!
用户11367452
2024/11/21
1.2K0
【C++入门篇】C++入门基础[必备知识点]
__weak关键字:程序模块相互独立的大杀器
在STM32 HAL库开发中,我们经常会看到__weak这个关键字,到底是什么意思呢?出于这个好奇心我们来打开KEIL的帮助手册找到它的出处:
杨源鑫
2020/12/24
1.2K0
【C++篇】迈入新世界的大门——初识C++(下篇)
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间, 它和它引⽤的变量共⽤同⼀块内存空间。
半截诗
2024/10/09
1520
【C++篇】迈入新世界的大门——初识C++(下篇)
【C++】入门基础(上)
提示:第一个链接不是C++官方文档,标准也只是更新到C++11,但是以头文件形式呈现,内容比较易看好懂,后两个链接分别是C++官方文档的中文版和英文版,信息很全,更新到了最新的C++标准,没有第一个那么易看,各有优点,相互结合使用即可。
云边有个稻草人
2024/10/21
960
【C++】入门基础(上)
C++入门基础(二)
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。比如:水浒传中李逵,宋江叫"铁牛",江湖上人称"黑旋风";林冲,外号豹子头;
用户11290648
2024/09/25
1100
C++入门基础(二)
【C++】类和对象——Lesson1
类实例化出的每个对象,都有独立的数据空间,每个对象都有各自独立的成员变量存储各自的数据,那成员函数是否被存储呢? 如果存储的话要存函数指针,但是函数指针不会变,也就是说每个对象中的函数指针都是一样的,如果类实例化很多对象成员函数指针就要被重复存储很多次,有点浪费。 其实函数指针是不需要存储的,函数指针是一个地址,编译器在编译链接时就找到函数的地址,不是在运行时找。
_小羊_
2024/10/16
1080
【C++】类和对象——Lesson1
相关推荐
C++基础知识(入门章2)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档