SRS 中的 gMock(Google Mock)C++ 模拟框架实战指南
本指南以 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 中定义了 MockSdpFactory、MockRtcSourceManager、MockLiveSource、MockAppStatistic 等几十个 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 宏自动生成这些样板代码,你只需要:
- 在类中声明与真实接口签名一致的方法;
- 用
MOCK_METHOD替换普通方法声明; - 在测试中通过
EXPECT_CALL声明期望。
从 SRS 源码可以看到这一模式的应用。例如 srs_utest_manual_mock.hpp 中 MockRtcSourceManager 继承自 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.h 与 gmock-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.h 与 gmock-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 允许表达调用顺序约束(InSequence 与 Sequence),这对于 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)特别说明:
NiceMock、NaggyMock、StrictMock会"继承"基类构造函数,因此可以直接传参构造,例如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::InitGoogleTest 与 RUN_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.cpp、srs_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"覆盖了这些场景:
- 构造测试输入:
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
...
}
- 替换真实依赖:
MockRtcSourceManager、MockLiveSourceManager、MockSrtSourceManager等替代真实的 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;
}
- 验证调用序列与参数:
MockRtmpServer(实现ISrsRtmpServer接口)对 RTMP 握手的每个阶段(handshake、connect_app、start_play、start_publish)都维护独立的调用计数与可注入错误,测试可断言"恰好调用 N 次"或"参数是否正确"。MockAppStatistic::on_client()则记录last_client_req_、last_client_conn_、last_client_type_,供测试校验传入的请求对象。
从手写 mock 到 gMock 的演进思路
SRS 的 utest 采用了"手写接口 mock + gtest 断言"的组合,这体现了两种风格的互补:
- 手写 mock 的优势是可控性强、无宏展开开销,适合需要精确统计调用次数、保存参数快照的复杂接口(如
ISrsStatistic、ISrsRtmpServer); - 原生 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.a与gtest_main.a:分别对应"用户自带 main()"与"使用 gtest_main 默认 main()"两种链接方式; - SRS 使用自带 main() 的方式(见上文
srs_utest.cpp中的main),因此链接gtest.a而非gtest_main.a; - 将
MODULE_FILES(即srs_utest_ai01~srs_utest_ai24、srs_utest_manual_*等测试源文件)逐一编译为.o,与全部 SRS 源码目标文件一起链接生成最终二进制。
运行测试的基本流程:
cd trunk
./configure --utest=on # 开启单元测试构建
make # 编译(会生成 objs/srs_utest 等)
./objs/srs_utest # 运行全部测试用例
--utest=on 会通过 srs_core.hpp 中 SRS_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.txt(
gmock与gmock_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.cpp、srs_utest_manual_mock.hpp)共同构成了媒体服务器的高质量测试体系。掌握 gMock 的声明式思维,不仅可以直接复用 SRS 的测试基建,还能为后续新功能编写更精炼、更可维护的测试代码。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00