gmock和cppfreemock原理学习

1.gmock用法

gmock(Google Mock)是 Google Test 的一个扩展库,专门用于 C++ 单元测试中的模拟(mocking) 。它的核心原理是通过 继承和方法重载/覆盖 来模拟 C++ 中的虚函数,从而在测试中隔离依赖对象,使测试更加可控。

gmock 通过 C++ 的多态机制(虚函数表 vtable) 来替换待模拟类的方法,使测试代码可以控制这些方法的行为。

cpp 复制代码
#include <gmock/gmock.h>

class ILogger {
public:
    virtual ~ILogger() = default;
    virtual void Log(const std::string& message) = 0;
};

class MockLogger : public ILogger {
public:
    // MOCK_METHOD(返回类型, 函数名, (参数列表), (限定符));
    // 1.负责定义虚函数的模拟版本,宏展开
    MOCK_METHOD(void, Log, (const std::string& message), (override));
};

// 2.定义mock类
MockLogger mock;
// 3.定义模拟函数期望行为
EXPECT_CALL(mock, Log("Hello")).Times(1);
mock.Log("Hello");  // 4.执行模拟函数,符合期望

MOCK_METHOD 实际上是一个 宏展开,它会生成:

  • 一个虚函数的声明覆盖基类
  • 一个 gmock 代理方法,用于在运行时控制行为

展开的代码类似于:

cpp 复制代码
class MockLogger : public ILogger {
public:
    void Log(const std::string& message) override {
        // gmock 代理方法,用于检查调用
        gmock_Log(message);
    }

    // 由 gmock 生成的 "mock" 版本
    ::testing::MockFunction<void(const std::string&)> gmock_Log;
};

1.1 适用情况

(1)模拟依赖对象

  • 当被测代码依赖某个类,而这个类访问数据库、文件系统、网络等,无法直接测试时,可用 gmock 进行模拟。

(2)验证某个方法是否被正确调用

  • 例如:测试中 确保日志写入函数被调用 ,或者 确保某个 API 被正确触发

2.EXPECT_CALL原理

EXPECT_CALL(mock, Method(args)) 主要用于:

  1. 指定期望的调用次数 (如 Times(1) 代表期望调用 1 次)。
  2. 检查参数是否匹配 (可以使用 Eq(5)_(任意值)等)。
  3. 指定返回值或行为 (如 WillOnce(Return(10)) 表示返回 10)。

https://github.com/jwongzblog/myblog/blob/master/c++/单元测试Mock之c++-gmock实现原理.md

cpp 复制代码
// 定义了一个obj.gmock_func.InternalExpectedAt(...)去调用
#define GMOCK_EXPECT_CALL_IMPL_(obj, call) \
    ((obj).gmock_##call).InternalExpectedAt(__FILE__, __LINE__, #obj, #call)
#define EXPECT_CALL(obj, call) GMOCK_EXPECT_CALL_IMPL_(obj, call)


// 会添加一个新的expectations
typedef std::vector<internal::linked_ptr<ExpectationBase> >  UntypedExpectations;
untyped_expectations_.push_back(untyped_expectation);


// willOnce,会添加到,它是TypedExpectation的成员函数
// 当调用的时候会从对应模拟中返回结果并删除
typedef std::vector<const void*> UntypedActions;
untyped_actions_.push_back(new Action<F>(action));

// 其实还是不太明白,具体执行流程是怎么样的。怎么就能invoke了。

3.cppfreemock

gmock只能模拟虚函数,对业务可能有入侵。cppfreemock可以支持mock非虚成员函数、静态成员函数、全局函数、重载函数、模板函数以及其他依赖库的函数时。它的原理是函数指针hook

玩转单元测试之cppfreemock-CSDN博客

cpp 复制代码
class Adder {
public:
    int add(int a, int b) const
    {
        return a + b;
    }
};

TEST(TestCppFreeMock, CaseStaticMemberFunction) 
{
    auto mock = MOCKER(&Adder::add);
    // 针对类的成员函数,要注意占位符会多出一个,即第一个为this指针
    // 而全局函数或者静态成员函数占位符个数等于实际参数个数
    EXPECT_CALL(*mock, MOCK_FUNCTION(_, _, _))
        .WillRepeatedly(Return(2));
    Adder adder;
    EXPECT_EQ(2, adder.add(1, 2));
    EXPECT_EQ(2, adder.add(12, 2));
    mock->RestoreToReal();
    EXPECT_EQ(14, g_func(12, 2));
}

3.1 函数指针hook

cpp 复制代码
#include <iostream>
#include <functional>

class Foo {
public:
    int Bar() { return 42; }  // 目标函数
};

// 函数指针 Hook(初始指向真实方法)
std::function<int(Foo*)> Bar_Hook = [](Foo* obj) { return obj->Bar(); };

// Mock 版本
int MockBar(Foo* obj) {
    return 99;
}

int main() {
    Foo foo;
    
    // 替换 Hook,指向 Mock 版本
    Bar_Hook = MockBar;
    
    // 通过 Hook 调用
    std::cout << "Result: " << Bar_Hook(&foo) << std::endl; // 输出 99

    return 0;
}
  • std::function<int(Foo*)> 作为 可变函数指针 ,用于调用 Foo::Bar
  • 初始时,Bar_Hook 绑定到原始 Bar()
  • 在测试时,替换 Bar_Hook 使其指向 MockBar(),这样所有调用都会执行 Mock 版本。

// 太牛了,还能这么搞,叹为观止。

函数指针 Hook 是一种通过修改函数指针的指向来改变函数行为 的技术。它常用于拦截函数调用 ,在不修改原代码的情况下 ,替换成自定义的行为(如 Mock、日志、监控等)。C++ 中的函数指针 本质上是存储函数地址的变量,可以动态修改。Hook 技术 通过 更改函数指针的值,让程序调用一个新的(Mock)函数,而不是原始函数。

hook普通函数:

cpp 复制代码
#include <iostream>
using namespace std;

// 原始函数
int OriginalFunction(int x) {
    return x * 2;
}

// 定义函数指针
int (*FunctionHook)(int) = OriginalFunction;

// Mock 版本
int MockFunction(int x) {
    return 100;  // 返回固定值
}

int main() {
    cout << "Before Hook: " << FunctionHook(10) << endl;  // 输出 20(调用原函数)

    // Hook:修改函数指针
    FunctionHook = MockFunction;

    cout << "After Hook: " << FunctionHook(10) << endl;  // 输出 100(调用 Mock 版本)

    return 0;
}

3.2 源码初探

cpp 复制代码
// MOCKER的宏定义
#define MOCKER_INTERNAL(function, identity)                                                        \
    ::CppFreeMock::MockerCreator::GetMocker<::CppFreeMock::TypeForUniqMocker<identity>>(function,  \
                                                                                        #function)

// GetMocker会够造一个对应的函数指针,并且存储到map中
static const std::shared_ptr<M> DoGetMocker(F function, const std::string& functionName) {
    const void* address = reinterpret_cast<const void*>((std::size_t&)function);
                    SimpleSingleton<RestoreFunctions>::getInstance().push_back(
                std::bind(MockerCacheType::RestoreCachedMockFunctionToReal));
            MockerCacheType::getInstance().insert(
                {{address, CreateMocker<I>(function, functionName)}});
}
相关推荐
西岸行者6 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意6 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码6 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J6 天前
从“Hello World“ 开始 C++
c语言·c++·学习