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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 16:15:41