基于CppUTest的嵌套调用函数单元测试如何替换被调函数为Mock版本
C++单元测试中替换全局依赖函数的实现方法
普通全局函数是编译期静态绑定的地址常量,本身不是可修改的左值,所以你写的b = mock_b这种直接赋值的写法语法上就不成立。要让a()在测试时调用mock_b(),有三种常用实现方案,按改造成本从低到高排序:
方案1:函数指针间接调用(最贴合你的预期写法,运行时可动态切换)
给b的调用加一层可修改的函数指针中间层,默认指向真实实现,测试时直接修改指针指向mock即可,改造成本极低:
修改后的头文件 source.h
// 声明函数指针类型、可替换的全局指针 typedef int(*BFunc)(int); extern BFunc b_entry; // 原有函数声明保留 int b(int x); bool a();
修改后的源文件 source.cpp
#include "source.h" // 函数指针默认指向真实的b实现 BFunc b_entry = b; int b(int x) { return x; } bool a() { // 原代码直接调用b(5),改为调用函数指针 return b_entry(5) > 0; }
测试文件 tester.cpp
#include "source.h" #include <cassert> int mock_b(int x) { return -8; } void unit_test() { // 默认走真实b逻辑,b(5)=5>0,返回true assert(true == a()); // 和你原本想写的赋值逻辑完全一致,替换指针指向mock b_entry = mock_b; // 此时调用a()会走mock_b,返回-8>0即false assert(false == a()); // 测试完成后可以切回原函数,不影响其他用例 b_entry = b; }
这个方案支持运行时动态切换真实/mock实现,不需要额外测试框架,轻量场景下非常好用。
方案2:链接期符号替换(零侵入业务源码)
如果你完全不想改业务代码,可以在编译测试程序时,不链接真实b函数的目标文件,直接在测试代码里实现同签名的b函数,链接器会自动把a()里对b的调用绑定到你写的mock实现上。
操作注意点:
- 需要把真实
b的实现从a所在的源文件里拆出来单独编译,测试链接时跳过这个真实实现的目标文件即可 - 缺点是全局替换,同一个测试进程里没法同时跑真实b和mock b的用例,灵活性差
方案3:接口抽象+依赖注入(大型项目推荐)
如果b是数据库、IO这类外部依赖,长期维护的项目建议把依赖抽象成接口,业务逻辑依赖抽象接口而非具体实现,测试时传入mock实例即可:
// 抽象依赖接口 class IB { public: virtual int b(int x) = 0; virtual ~IB() = default; }; // 生产环境用真实实现 class RealB : public IB { public: int b(int x) override { return x; } }; // 测试用mock实现 class MockB : public IB { public: int b(int x) override { return -8; } }; // 全局注入依赖实例,生产环境传RealB实例,测试传MockB实例 extern IB* b_instance; bool a() { return b_instance->b(5) > 0; }
这个方案扩展性最好,但是改造成本最高,适合中大型项目做全量单元测试覆盖。
补充说明:不要尝试直接修改内存里的函数指令、或者改函数地址做热补丁,这类写法是未定义行为,不同编译器、不同系统下兼容性极差,单元测试场景完全没必要用。
内容的提问来源于stack exchange,提问作者Ronen333
相关产品推荐
相关产品推荐

