ESP32项目中命名空间函数的Mock方案:兼顾单例结构与低开销
问题描述
我正在ESP32微控制器上开发高性能C++项目,代码包含多个交互模块,目前基于Catch2测试框架编写测试,并尝试使用FakeIt Mock框架。现有代码采用命名空间实现单例式组件,示例如下:
Data.h
// I'm using my header files to define a namespace that other files will // load to use the API handles of this component. namespace DataAPI { extern void saveTheThing(void); extern void loadTheThing(void); }
Data.cpp
// I'm using my Data.cpp file to populate those API methods, as well as to store // the private data that the component will use. #include "Data.h" namespace DataAPI { the_thing_t myDataRecord; void saveTheThing(void) { //...saves `myDataRecord` to flash. } void loadTheThing(void) { //...loads data from flash into `myDataRecord` } }
SomeOtherComponent.cpp
#include "Data.h" void theFunctionImGoingToTest(void) { DataAPI::loadTheThing(); }
当前代码运行正常,但FakeIt等Mock框架仅支持对结构体/类的成员函数进行Mock,示例如下:
FakeIt官方示例
// Here's how FakeIt suggests you set up a mock Mock<SomeInterface> mock; // Stub a method to return a value once When(Method(mock,foo)).Return(1);
由于命名空间下的函数无法通过上述方式Mock,我不想为适配框架重构代码(无需多实例,不愿增加内存与CPU开销)。请问如何在保留单例库结构的同时适配Mock框架?是否存在支持someNamespace::someBareFunction()形式的Mock框架?或有无低开销的重构方案?
解决方案
方案1:静态类封装(零额外开销)
无需修改外部调用逻辑,仅将命名空间函数迁移到静态类中,完全兼容原有代码,同时适配FakeIt的Mock机制:
修改后Data.h
namespace DataAPI { struct DataComponent { static void saveTheThing(void); static void loadTheThing(void); }; // 保留原命名空间函数别名,确保现有业务代码无需改动 inline void saveTheThing(void) { DataComponent::saveTheThing(); } inline void loadTheThing(void) { DataComponent::loadTheThing(); } }
修改后Data.cpp
#include "Data.h" namespace DataAPI { the_thing_t myDataRecord; void DataComponent::saveTheThing(void) { // 原业务逻辑完全保留 } void DataComponent::loadTheThing(void) { // 原业务逻辑完全保留 } }
测试时直接Mock静态类:
Mock<DataAPI::DataComponent> mock; When(Method(mock, loadTheThing)).DoNothing(); // 测试编译阶段通过宏替换让原调用指向Mock方法 #define DataAPI::loadTheThing mock.get().loadTheThing // 执行测试逻辑 theFunctionImGoingToTest(); // 验证调用次数 Verify(Method(mock, loadTheThing)).Once();
该方案完全保留原有调用方式,静态类成员函数与原命名空间函数性能一致,无额外内存/CPU开销。
方案2:函数指针注入(极低开销)
在命名空间中添加可替换的函数指针,生产环境指向原实现,测试时替换为Mock逻辑:
修改后Data.h
namespace DataAPI { // 定义函数指针类型 using SaveFunc = void(*)(void); using LoadFunc = void(*)(void); // 对外暴露的可替换指针 extern SaveFunc saveTheThing; extern LoadFunc loadTheThing; // 内部原实现接口 void saveTheThingImpl(void); void loadTheThingImpl(void); }
修改后Data.cpp
#include "Data.h" namespace DataAPI { the_thing_t myDataRecord; void saveTheThingImpl(void) { // 原业务逻辑完全保留 } void loadTheThingImpl(void) { // 原业务逻辑完全保留 } // 初始化指针指向原实现 SaveFunc saveTheThing = saveTheThingImpl; LoadFunc loadTheThing = loadTheThingImpl; }
测试时直接替换指针:
// 定义FakeIt可识别的接口类 struct DataInterface { virtual void saveTheThing() = 0; virtual void loadTheThing() = 0; }; Mock<DataInterface> mock; When(Method(mock, loadTheThing)).DoNothing(); // 替换命名空间中的函数指针为Mock逻辑 DataAPI::loadTheThing = [](){ mock.get().loadTheThing(); }; // 执行测试 theFunctionImGoingToTest(); // 验证调用 Verify(Method(mock, loadTheThing)).Once();
该方案仅增加一次函数指针跳转的开销,在ESP32上可忽略不计,完全保留单例结构,无需修改原有业务代码。
方案3:支持裸函数Mock的框架
若不想修改现有代码,可使用CppUMock或CMock(支持C/C++裸函数Mock),直接对命名空间下的函数进行Mock:
// 测试中Mock DataAPI::loadTheThing mock_function(void, DataAPI_loadTheThing); // 通过宏替换让原调用指向Mock函数 #define DataAPI::loadTheThing DataAPI_loadTheThing // 设置调用期望 expect_call(DataAPI_loadTheThing, 1); // 执行测试 theFunctionImGoingToTest();
这类框架无需封装类,直接适配全局/命名空间裸函数,完全匹配你的需求。
内容的提问来源于stack exchange,提问作者chrispitzer
相关产品推荐
相关产品推荐

