You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 01:40:05