首页
/ SRS 中的 gMock(Google Mock)C++ 模拟框架实战指南

SRS 中的 gMock(Google Mock)C++ 模拟框架实战指南

2026-09-09 18:09:43作者:温艾琴Wonderful

本指南以 SRS 仓库内置的 GoogleMock 框架(gMock)为核心,介绍如何利用其声明式语法编写 C++ mock 类、验证函数调用与参数、控制 mock 行为,并展示 SRS 单元测试(utest)中真实使用 gMock 模式构造的 Mock 类与测试骨架,帮助你为实时媒体服务器这类高并发、强时序的系统编写可维护的单元测试。

概述:gMock 是什么

gMock(Googletest Mocking Framework)是 Google 为 C++ 编写和使用 mock 类而设计的框架,用于在单元测试中隔离被测对象与真实依赖。正如其在仓库中的 README 所述,它可以帮助开发者"derive better designs of your system and write better tests"(推导更好的系统设计并写出更好的测试)。其设计灵感来源于 jMock、EasyMock 与 Hamcrest 等测试框架,并针对 C++ 的特性做了专门设计。

在 SRS 仓库中,gMock 与 gtest 一起被打包在 trunk/3rdparty/gtest-fit/ 目录下(googlemock/ 即 gMock 本体,googletest/ 为 gtest 基础框架),SRS 的所有 C++ 单元测试(trunk/src/utest/)均构建于此框架之上。

gMock 的核心能力

gMock 提供以下关键能力(见 README.md):

能力 说明
声明式 mock 定义语法 通过 MOCK_METHOD 宏族声明 mock 方法,无需手写桩代码
部分(混合)mock mock 对象可以是真实对象与 mock 的混合体
任意类型与重载函数 支持任意函数类型和重载函数
丰富的匹配器(matchers) 用于校验函数参数是否符合预期
直观的行为控制语法 通过 EXPECT_CALL 设定 mock 行为
自动期望校验 无需 record-and-replay,测试结束时自动校验期望是否满足
任意(部分)调用顺序约束 可以表达函数调用之间的顺序约束
可扩展性 用户可自定义新的 matcher 与 action
不使用异常 整个框架不依赖 C++ 异常机制
易学易用 语法直观,学习成本低

这些能力在 SRS 的单元测试代码中有大量直接体现,例如 srs_utest_manual_mock.hpp 中定义了 MockSdpFactoryMockRtcSourceManagerMockLiveSourceMockAppStatistic 等几十个 Mock 类,覆盖了 SDP 生成、RTC 源管理、统计、安全校验等媒体服务器核心模块。

gMock 在仓库中的目录结构

gMock 本体位于 trunk/3rdparty/gtest-fit/googlemock/

googlemock/
├── CMakeLists.txt                  # CMake 构建脚本
├── README.md                       # gMock 官方说明(本文档主体)
├── include/gmock/
│   ├── gmock.h                     # 主头文件,包含全部 gMock 能力
│   ├── gmock-actions.h             # action(动作)定义
│   ├── gmock-cardinalities.h       # 调用次数(基数)约束
│   ├── gmock-function-mocker.h     # MOCK_METHOD 宏族的实现
│   ├── gmock-matchers.h            # matcher(匹配器)定义
│   ├── gmock-more-actions.h        # 扩展 action
│   ├── gmock-more-matchers.h       # 扩展 matcher
│   ├── gmock-nice-strict.h         # NiceMock/NaggyMock/StrictMock
│   ├── gmock-spec-builders.h       # EXPECT_CALL 期望构建器
│   └── internal/                   # 内部实现细节
└── src/
    ├── gmock-all.cc                # 编译入口
    ├── gmock-cardinalities.cc
    ├── gmock-internal-utils.cc
    ├── gmock-matchers.cc
    ├── gmock-spec-builders.cc
    ├── gmock.cc
    └── gmock_main.cc               # 提供 main() 入口

各头文件职责明确:gmock-function-mocker.h 负责 MOCK_METHOD 声明宏,gmock-spec-builders.h 负责 EXPECT_CALL 期望设置,gmock-matchers.h 提供参数匹配器,gmock-nice-strict.h 提供 mock 的"宽松/警告/严格"三种模式。

定义 Mock 类:声明式语法

gMock 的核心是 MOCK_METHOD 宏族。对于 C++ 传统语法,SRS 使用的 gtest-fit 版本同时支持 MOCK_METHODn(旧式)与 MOCK_METHOD(新式)两种写法,例如:

#include <gmock/gmock.h>

class MockFoo {
public:
    // 新式语法:返回类型 + 方法名 + 参数列表
    MOCK_METHOD(int, Bar, (const std::string& input));

    // 旧式语法(传统写法)
    // MOCK_METHOD1(Bar, int(const std::string& input));
};

声明式 vs 手写桩代码

传统的手写 mock 需要为每个接口方法实现一份"空壳"代码:记录参数、返回默认值、维护调用计数,代码量巨大且容易出错。gMock 的 MOCK_METHOD 宏自动生成这些样板代码,你只需要:

  1. 在类中声明与真实接口签名一致的方法;
  2. MOCK_METHOD 替换普通方法声明;
  3. 在测试中通过 EXPECT_CALL 声明期望。

从 SRS 源码可以看到这一模式的应用。例如 srs_utest_manual_mock.hppMockRtcSourceManager 继承自 ISrsRtcSourceManager 接口,将 initialize()fetch_or_create()fetch() 三个接口方法替换为带计数与可注入错误的实现:

class MockRtcSourceManager : public ISrsRtcSourceManager
{
public:
    srs_error_t initialize_error_;
    srs_error_t fetch_or_create_error_;
    int initialize_count_;
    int fetch_or_create_count_;
    SrsSharedPtr<SrsRtcSource> mock_source_;

public:
    MockRtcSourceManager();
    virtual ~MockRtcSourceManager();
    virtual srs_error_t initialize();
    virtual srs_error_t fetch_or_create(ISrsRequest *r, SrsSharedPtr<SrsRtcSource> &pps);
    virtual SrsSharedPtr<SrsRtcSource> fetch(ISrsRequest *r);
    void set_initialize_error(srs_error_t err);
    void set_fetch_or_create_error(srs_error_t err);
};

部分(混合)Mock

gMock 支持"部分 mock"——即真实对象与 mock 的混合。这在 SRS 中体现为两类模式:

  • 继承真实实现并覆盖特定方法:如 srs_utest_manual_mock.cpp 中的 MockRtcSource 继承自 SrsRtcSource,只覆写 on_rtp() 以增加音视频包计数,其余逻辑仍然走真实实现:
srs_error_t MockRtcSource::on_rtp(SrsRtpPacket *pkt)
{
    on_rtp_count_++;

    if (pkt->frame_type_ == SrsFrameTypeAudio) {
        rtp_audio_count_++;
    } else if (pkt->frame_type_ == SrsFrameTypeVideo) {
        rtp_video_count_++;
    }

    return SrsRtcSource::on_rtp(pkt);  // 调用真实实现
}
  • 实现接口的空壳 + 计数 + 错误注入:如 MockAppStatistic 实现 ISrsStatistic 接口的全部方法,多数返回默认值,但 on_client() 记录调用次数、保存最后参数,并支持注入错误码 set_on_client_error(),以便测试各种失败分支。

期望与验证:EXPECT_CALL

EXPECT_CALL 是 gMock 的行为控制核心,语法为:

EXPECT_CALL(mock_object, MethodName(matchers...))
    .Times(cardinality)          // 期望调用次数
    .WillOnce(action)            // 第一次调用时的行为
    .WillRepeatedly(action)      // 后续调用行为
    .RetiresOnSaturation();      // 期望满足后失效

与 record-and-replay 模式不同,gMock 采用自动验证:测试结束(mock 对象析构)时,gMock 自动检查所有 EXPECT_CALL 是否被满足(调用次数是否达标),无需手动进入"验证模式"。

次数约束(Cardinalities)

Times() 用于约束调用次数:

写法 含义
.Times(3) 恰好调用 3 次
.Times(AtLeast(1)) 至少调用 1 次
.Times(AtMost(2)) 最多调用 2 次
.Times(Between(1, 3)) 调用 1 到 3 次
.Times(AnyNumber()) 任意次数
省略 Times 默认恰好调用 1 次

次数约束的实现位于 gmock-cardinalities.hgmock-cardinalities.cc

行为设置(Actions)

WillOnce / WillRepeatedly 设置调用时的返回行为:

EXPECT_CALL(mock, GetName())
    .WillOnce(Return("srs"))        // 第一次返回 "srs"
    .WillRepeatedly(Return("rtmp")); // 之后一直返回 "rtmp"

EXPECT_CALL(mock, Process(_, _))
    .WillOnce(DoAll(SaveArg<0>(&captured), Return(true))); // 保存参数并返回 true

常用 action 包括 Return(value)ReturnNull()Throw(exception)(gMock 本身不用异常,但允许被测代码抛)、SaveArg<N>()Invoke(functor) 等,定义于 gmock-actions.hgmock-more-actions.h

匹配器(Matchers)

匹配器用于校验函数实参,定义于 gmock-matchers.h

匹配器 作用
_ 匹配任意值
Eq(x) / Ne(x) 等于 / 不等于
Lt(x) / Le(x) / Gt(x) / Ge(x) 大小比较
StrEq(s) / StrNe(s) 字符串相等 / 不等
HasSubstr(s) 包含子串
StartsWith(s) / EndsWith(s) 前缀 / 后缀
IsNull() / NotNull() 空指针判断
ElementsAre(...) / UnorderedElementsAre(...) 容器元素匹配
AllOf(m1, m2) / AnyOf(m1, m2) 组合匹配

顺序约束

gMock 允许表达调用顺序约束(InSequenceSequence),这对于 SRS 这类协议时序敏感的系统非常关键。例如推流测试要求必须先 connect_app()start_publish()

InSequence seq;
EXPECT_CALL(mock, Connect()).Times(1);
EXPECT_CALL(mock, Publish()).Times(1); // 必须发生在 Connect 之后

NiceMock、NaggyMock 与 StrictMock

默认情况下,gMock 对"无期望的调用"(uninteresting calls)会打印警告(即 Naggy 行为)。三种模式定义于 gmock-nice-strict.h

包装类 无期望调用时的行为
NiceMock<MockFoo> 静默忽略,返回默认值,测试更易维护
NaggyMock<MockFoo> 打印警告(当前默认行为)
StrictMock<MockFoo> 视为测试失败,严格模式

用法:

using ::testing::NiceMock;
using ::testing::StrictMock;

NiceMock<MockFoo> nice_foo;      // 宽松
StrictMock<MockFoo> strict_foo;  // 严格

源码注释(gmock-nice-strict.h)特别说明:

  • NiceMockNaggyMockStrictMock 会"继承"基类构造函数,因此可以直接传参构造,例如 NiceMock<MockFoo>(5, "a")
  • 已知限制:只能作用于直接在类中通过 MOCK_METHOD 宏定义的方法;嵌套使用三种包装不支持。

gMock 的扩展性

gMock 允许用户通过自定义 matcher 与 action 扩展框架:

  • 自定义 matcher:使用 MATCHER(IsValidUrl, "url is valid") { return ...; } 宏或实现 Matcher<T> 接口;
  • 自定义 action:使用 ACTION(MyAction) { ... } 宏或实现 Action<T>

这与 SRS 中大量手写的"计数 + 错误注入"辅助方法(如 set_initialize_error()set_fetch_or_create_error())互补:前者偏重"行为声明",后者偏重"状态断言"。

SRS 中 gMock 的实际应用:utest 测试框架

测试入口与框架初始化

SRS 的单元测试二进制入口在 srs_utest.cpp

// Copy from gtest-1.6.0/src/gtest_main.cc
GTEST_API_ int main(int argc, char **argv)
{
    ...
    testing::InitGoogleTest(&argc, argv);
    int r0 = RUN_ALL_TESTS();
    return r0;
}

入口调用 testing::InitGoogleTestRUN_ALL_TESTS(gtest 宏),并在 prepare_main() 中完成 SRS 全局对象初始化、DTLS 证书初始化、SRT 日志抑制等准备工作,随后运行全部测试用例。所有测试源文件都包含公共头文件 srs_utest.hpp,其中 #include "gtest/gtest.h" 引入 gtest,而全部 Mock 类则统一从 #include <srs_utest_manual_mock.hpp> 引入。

测试骨架与宏

SRS 的每个测试文件(如 srs_utest_ai01.cppsrs_utest_manual_rtmp.cpp)都使用 gtest 的 TEST / TEST_F 宏组织用例。公共头文件 srs_utest.hpp 还封装了针对 SRS 错误体系(srs_error_t)的断言辅助宏:

#define HELPER_EXPECT_SUCCESS(x)  // 期望返回 srs_success,失败时打印错误描述
#define HELPER_EXPECT_FAILED(x)   // 期望返回错误
#define HELPER_ASSERT_SUCCESS(x)  // 断言成功(不继续执行)
#define HELPER_ASSERT_FAILED(x)   // 断言失败

这些宏内部调用 EXPECT_TRUE / ASSERT_TRUE,是 gMock/gtest 断言体系在 SRS 项目中的封装。

Mock 类在测试中的典型用法

srs_utest_manual_mock.cpp 为例,可以看到 SRS 用"手工 mock"覆盖了这些场景:

  1. 构造测试输入MockSdpFactory 生成 Chrome 风格(audio 在前)与 libdatachannel 风格(video 在前)的真实 WebRTC SDP Offer,覆盖 H.264 + Opus、AV1、VP9、G.711 PCMU 等多种编码组合,用于驱动 RTC 连接解析测试:
MockSdpFactory::MockSdpFactory()
{
    audio_ssrc_ = 1001;
    audio_pt_ = 111;
    video_ssrc_ = 2002;
    video_pt_ = 96;
}

std::string MockSdpFactory::create_chrome_publisher_offer_with_h264()
{
    // 生成包含 ICE、DTLS fingerprint、SSRC 的完整 SDP,见源码 L107-L156
    ...
}
  1. 替换真实依赖MockRtcSourceManagerMockLiveSourceManagerMockSrtSourceManager 等替代真实的 Source 管理器,通过 mock_source_ 返回预先构造的 SrsSharedPtr,并通过 set_*_error() 注入错误,覆盖推流/拉流失败分支:
srs_error_t MockRtcSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr<SrsRtcSource> &pps)
{
    srs_error_t err = srs_success;

    if (fetch_or_create_count_ == 0) {
        err = mock_source_->initialize(r);
    }
    fetch_or_create_count_++;

    if (fetch_or_create_error_ != srs_success) {
        return srs_error_copy(fetch_or_create_error_);
    }
    pps = mock_source_;
    return err;
}
  1. 验证调用序列与参数MockRtmpServer(实现 ISrsRtmpServer 接口)对 RTMP 握手的每个阶段(handshakeconnect_appstart_playstart_publish)都维护独立的调用计数与可注入错误,测试可断言"恰好调用 N 次"或"参数是否正确"。MockAppStatistic::on_client() 则记录 last_client_req_last_client_conn_last_client_type_,供测试校验传入的请求对象。

从手写 mock 到 gMock 的演进思路

SRS 的 utest 采用了"手写接口 mock + gtest 断言"的组合,这体现了两种风格的互补:

  • 手写 mock 的优势是可控性强、无宏展开开销,适合需要精确统计调用次数、保存参数快照的复杂接口(如 ISrsStatisticISrsRtmpServer);
  • 原生 gMock 的优势是声明式、代码量少,适合接口方法多、只需简单返回值的场景。

在阅读 srs_utest_manual_mock.cpp 时,可以对照 gMock 的 EXPECT_CALL 思维:每个 xxx_count_ 字段对应 Times() 约束,每个 set_xxx_error() 对应 WillOnce(Return(err)),每个 last_xxx_ 字段对应 SaveArg。理解这种对应关系,就能把 SRS 现有测试改写成更声明式的 gMock 风格,或在新模块测试中直接采用 gMock。

构建与运行 SRS 单元测试

SRS 的构建脚本为 gtest 生成了独立的 Makefile,见 trunk/auto/utest.sh。该脚本的关键逻辑:

  • 指定 gtest 目录:GTEST_DIR=../3rdparty/gtest/googletest(相对 objs/utest 目录);
  • 强制使用 C++11:SRS_CPP_VERSION="-std=c++11"(gtest 运行所需);
  • 生成 gtest.agtest_main.a:分别对应"用户自带 main()"与"使用 gtest_main 默认 main()"两种链接方式;
  • SRS 使用自带 main() 的方式(见上文 srs_utest.cpp 中的 main),因此链接 gtest.a 而非 gtest_main.a
  • MODULE_FILES(即 srs_utest_ai01srs_utest_ai24srs_utest_manual_* 等测试源文件)逐一编译为 .o,与全部 SRS 源码目标文件一起链接生成最终二进制。

运行测试的基本流程:

cd trunk
./configure --utest=on    # 开启单元测试构建
make                     # 编译(会生成 objs/srs_utest 等)
./objs/srs_utest         # 运行全部测试用例

--utest=on 会通过 srs_core.hppSRS_FORCE_PUBLIC4UTEST 宏自动启用 #define private public / #define protected public(见 srs_utest.hpp 注释),使测试代码能直接访问被测类的私有成员,这是 SRS 为单元测试深度定制的特性。

gMock 的使用前提与限制

  • C++11 及以上:本仓库的 gtest-fit 要求 C++11(见 utest.sh);
  • 不使用异常:gMock 自身不依赖异常,适用于 -fno-exceptions 的构建环境;
  • 严格模式限制NiceMock/StrictMock 只对直接声明于本类的 MOCK_METHOD 生效,嵌套使用不受支持(见 gmock-nice-strict.h);
  • CMake 构建:gMock 自身提供 CMakeLists.txtgmockgmock_main 两个目标,可用 -Dgmock_build_tests=ON 构建其自带测试),但 SRS 主项目使用 configure + make 构建体系,二者相互独立。

总结

gMock 为 C++ 单元测试提供了声明式的 mock 定义、丰富的参数匹配器、直观的行为控制与自动的期望验证,并能通过 NiceMock/NaggyMock/StrictMock 调节测试严格度。在 SRS 仓库中,gMock 与 gtest 是全部单元测试的地基:测试入口(srs_utest.cpp)、公共断言宏(srs_utest.hpp)以及覆盖 RTC/RTMP/SRT 等核心模块的大量 Mock 类(srs_utest_manual_mock.cppsrs_utest_manual_mock.hpp)共同构成了媒体服务器的高质量测试体系。掌握 gMock 的声明式思维,不仅可以直接复用 SRS 的测试基建,还能为后续新功能编写更精炼、更可维护的测试代码。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395