- 后端
- RPC框架
- 微服务
【免费下载链接】kitex
Go 微服务 RPC 框架,具有高性能、强可扩展的特点。
Kitex 在internal/mocks目录下维护了一套覆盖框架核心接口的 Mock 代码,配合 gomock 在单元测试中隔离网络、限流、服务发现等外部依赖。本文以 internal/mocks/README.md 为骨架,结合 update.sh 的完整实现与仓库内大量测试用例,讲解如何安装工具链、扩展生成清单、一键重新生成,以及如何在 Kitex 源码测试中正确使用这些 Mock。
一、internal/mocks:Kitex 自测体系的"基础设施"
从 doc.go 的包注释可以看出,internal/mocks的定位非常明确——Package mocks contains mocks of interfaces.(该包包含各接口的 mock 实现)。它处于internal目录下,意味着这套 Mock 仅服务于 Kitex 仓库自身的测试,不对外暴露为公共 API。
整个目录由三类代码构成:
- gomock 自动生成:由 mockgen 从
pkg/下的接口文件批量生成,文件头部带有// Code generated by MockGen. DO NOT EDIT.与// Source: ...标记,例如 limiter/limiter.go、remote/trans_handler.go。 - Kitex 代码生成:由 kitex/thriftgo 工具从 IDL 生成的 mock 服务,例如 thrift/ 下的
test.go、stream.go,以及 proto/kitex_gen/pbapi/mock/mock.go。 - 手写实现:结构体 + 函数字段风格的轻量 Mock,例如 conn.go 中的
Conn(实现net.Conn)、serviceinfo.go 中的MockService,以及 transhandlerclient.go、transhandlerserver.go。
它们共同支撑着 client、pkg/remote、pkg/limiter 等数十个测试文件的运行。
二、运行前提(Running Prerequisites)
1. 安装 gomock / mockgen
go install github.com/golang/mock/mockgen@latest该命令会将mockgen安装到$GOPATH/bin(或GOBIN指定的目录)下,是 update.sh 中批量生成命令所依赖的可执行文件。安装完成后建议确认mockgen已在PATH中,可通过mockgen --version验证。
注意:gomock 生成的代码依赖
github.com/golang/mock/gomock运行时包,Kitex 的 go.mod 已引入该依赖,测试代码中直接 import 即可。
2. 克隆 netpoll 到 kitex 的父目录
git clone github.com/cloudwego/netpoll这一步是目录布局前提:update.sh的生成清单中有一条目标指向../../../netpoll/connection.go(见下文),即从internal/mocks向上三级进入 kitex 仓库的父目录去读取 netpoll 的接口定义。因此 netpoll 必须与 kitex 仓库平级存放,否则sh update.sh会在文件存在性检查处直接报错退出。
三、update.sh:批量生成 gomock 的核心脚本
update.sh 是整个 mock 生成流程的枢纽。其执行逻辑为:
cd到脚本自身所在目录(internal/mocks),保证相对路径稳定;- 遍历
files数组,每三项为一组(source => output => package); - 检查源文件是否存在,不存在则打印
ERROR: ... not existing并退出; - 执行
mockgen -source=$infile -package=$package > $outfile; - 用
sed在生成文件头部插入 Apache 2.0 版权注释。
脚本中按OSTYPE区分了 macOS(darwin)与 Linux 的sed -i语法,且两个分支的版权头年份分别为 2022 与 2021——这是历史遗留差异,不影响功能。
完整的 files 生成清单
files数组以源文件路径 输出文件路径 包名三元组组织,当前仓库中完整的清单如下:
| 源文件(Source) | 输出文件(Output) | 包名(Package) |
|---|---|---|
../../pkg/limiter/limiter.go | limiter/limiter.go | limiter |
../../pkg/stats/tracer.go | stats/tracer.go | stats |
../../pkg/remote/bytebuf.go | remote/bytebuf.go | remote |
../../pkg/remote/trans_handler.go | remote/trans_handler.go | remote |
../../pkg/remote/codec.go | remote/codec.go | remote |
../../pkg/remote/remotecli/conn_wrapper.go | remote/conn_wrapper.go | remote |
../../pkg/remote/connpool.go | remote/connpool.go | remote |
../../pkg/remote/dialer.go | remote/dialer.go | remote |
../../pkg/remote/trans_meta.go | remote/trans_meta.go | remote |
../../pkg/remote/trans_pipeline.go | remote/trans_pipeline.go | remote |
../../pkg/remote/payload_codec.go | remote/payload_codec.go | remote |
../../pkg/generic/generic_service.go | generic/generic_service.go | generic |
../../pkg/klog/log.go | klog/log.go | klog |
../../internal/generic/thrift/thrift.go | generic/thrift.go | generic |
../../pkg/discovery/discovery.go | discovery/discovery.go | discovery |
../../pkg/loadbalance/loadbalancer.go | loadbalance/loadbalancer.go | loadbalance |
../../pkg/proxy/proxy.go | proxy/proxy.go | proxy |
../../pkg/utils/sharedticker.go | utils/sharedticker.go | utils |
../../../netpoll/connection.go | netpoll/connection.go | netpoll |
$GOROOT/src/net/net.go | net/net.go | net |
$GOROOT/src/net/net.go这一项说明:mockgen 也支持直接对 Go 标准库源文件生成,这里用它为net标准库接口生成 mock,输出到 net/net.go。
清单之外的 rpc_info/timeout_provider.go(Source 标记为github.com/cloudwego/kitex/pkg/rpcinfo (interfaces: TimeoutProvider))与 diagnosis/service.go(Source 为.../pkg/diagnosis (interfaces: Service))则是通过 mockgen 的-interfaces参数按接口精确生成的,未出现在当前files数组中。
四、用户指南:如何为新的接口添加 mock
按 README 的用户指南,为一个新接口生成 mock 只需五步:
- 在
files参数下新增一行,即编辑 update.sh,在数组末尾追加一条记录; - 填写待 mock 接口所在的 go 文件路径(即 Source,支持仓库内相对路径、
$GOROOT变量或外部仓库路径); - 填写输出 mock 文件的路径(即 Output,
internal/mocks下的相对路径); - 填写输出 mock 文件的包名(即 Package,决定生成的
// Package xxx声明与目录归属); - 运行
sh update.sh重新生成全部 mock 文件。
以"为pkg/limiter/limiter.go生成 mock"这一真实条目为例,它在数组中对应:
../../pkg/limiter/limiter.go limiter/limiter.go limiter执行sh update.sh后,mockgen 会解析 pkg/limiter/limiter.go 中定义的ConcurrencyLimiter、RateLimiter、Updatable、LimitReporter四个接口,生成 limiter/limiter.go,其中包含NewMockConcurrencyLimiter(ctrl)、NewMockRateLimiter(ctrl)、NewMockUpdatable(ctrl)、NewMockLimitReporter(ctrl)四个构造函数及对应的 Recorder。
脚本本身带有两处健壮性保障:源文件缺失时立即exit 1中断;mockgen执行失败时同样exit 1,避免生成残缺文件后继续污染输出目录。
mockgen 生成代码的结构
从 limiter/limiter.go 可以看出每个生成的 mock 都遵循统一模板:
MockXxx结构体:持有ctrl *gomock.Controller与recorder *MockXxxMockRecorder;NewMockXxx(ctrl)构造函数:接收*gomock.Controller;EXPECT()方法:返回 recorder,用于声明期望调用;- 每个接口方法对应一对
方法名/方法名(recorder):前者记录调用并返回模拟值,后者通过RecordCallWithMethodType声明期望。
生成文件的头部注释// Code generated by MockGen. DO NOT EDIT.明确告知:这类文件不应手工修改,修改应回写到源接口文件后重新运行脚本。
五、生成的 mock 全景盘点
5.1 gomock 生成(按模块)
| 模块目录 | 覆盖的核心接口 | 典型 Mock |
|---|---|---|
| limiter/ | 并发/速率限流、动态更新、过载上报 | MockConcurrencyLimiter、MockRateLimiter、MockUpdatable、MockLimitReporter |
| remote/ | 传输层编解码、读写、连接池、拨号、元信息、流水线 | MockClientTransHandler、MockServerTransHandler、MockTransHandler、MockDialer、MockTransReadWriter、MockGracefulShutdown、MockInvokeHandleFuncSetter等 |
| netpoll/ | netpoll 连接、读写器、拨号器、监听器 | MockConnection、MockConn、MockListener、MockDialer,以及 nocopy.go 中的MockReader、MockWriter、MockReadWriter |
| discovery/ | 服务发现 | MockDiscovery等 |
| loadbalance/ | 负载均衡 | MockLoadbalancer等 |
| klog/ | 日志接口(894 行,覆盖面最大) | MockLogger等 |
| proxy/ | 代理转发 | MockProxy等 |
| stats/ | 链路统计追踪 | MockTracer |
| utils/ | 共享定时任务 | MockTickerTask |
| diagnosis/ | 诊断服务 | MockService |
| rpc_info/ | 超时提供者 | MockTimeoutProvider |
| generic/ | 泛化调用服务 | MockGenericService等 |
| net/ | Go 标准库net包 | MockConn等 |
5.2 Kitex 代码生成(IDL 驱动)
- Thrift 方向:thrift/gen.sh 使用
kitex -thrift no_default_serdes -module github.com/cloudwego/kitex -gen-path ..对 test.thrift 与 stream.thrift 生成 mock 服务代码,生成后清理无用的mock与testservice目录。 - Protobuf 方向:proto/kitex_gen/pbapi/mock/mock.go 由 Kitex v0.9.1 生成,包含
UnaryTest、ClientStreamingTest、ServerStreamingTest、BidirectionalStreamingTest四种方法,覆盖了 unary 与三种流式调用模式,并提供了服务端与客户端两侧的ServiceInfo(NewServiceInfo、NewServiceInfoForClient、NewServiceInfoForStreamClient),可用于构建流式测试服务。
5.3 手写 mock(函数注入风格)
- conn.go:
Conn结构体为net.Conn的每个方法暴露一个XxxFunc函数字段,未设置的函数返回零值,并通过NewIOConn()提供一个基于bytes.Buffer的内存读写连接;文件开头var _ net.Conn = &Conn{}是编译期接口实现断言。 - serviceinfo.go:定义了
MockServiceName、MockMethod、MockExceptionMethod、MockErrorMethod、MockOnewayMethod、MockStreamingMethod等常量,以及ServiceInfo()/Service2Info()/Service3Info()三个服务信息工厂和MyService接口、MockFuncHandler函数式 handler,供服务端测试注册使用。 - transhandlerclient.go、transhandlerserver.go:分别提供客户端/服务端传输处理器的工厂与 mock 实现,如
NewMockCliTransHandlerFactory、MockSvrTransHandler、MockTransServer等。
六、在测试中使用这些 mock 的四种典型姿势
姿势一:gomock 标准流程(限流器场景)
limiter_inbound_test.go 展示了最典型的用法——先创建 Controller,再构造 mock 并声明期望:
ctrl := gomock.NewController(t) defer ctrl.Finish() concurrencyLimiter := mockslimiter.NewMockConcurrencyLimiter(ctrl) rateLimiter := mockslimiter.NewMockRateLimiter(ctrl) limitReporter := mockslimiter.NewMockLimitReporter(ctrl)随后通过EXPECT()链式声明调用期望,例如:
concurrencyLimiter.EXPECT().Acquire(gomock.Any()).Return(true).Times(1) concurrencyLimiter.EXPECT().Release(gomock.Any()).Times(1)这样被测的limiter中间件就能在完全不依赖真实限流实现的前提下验证 acquire/release 的配对逻辑。
姿势二:多模块 mock 组合(连接池场景)
long_pool_test.go 演示了如何将 netpoll 与 remote 两个模块的 mock 组合使用:
ctrl := gomock.NewController(t) d := mocksremote.NewMockDialer(ctrl) conn := mocksnetpoll.NewMockConnection(ctrl)MockDialer的DialConnection期望返回MockConnection,从而让长连接池在"拨号-取连接-归还"的完整流程中脱离真实网络。类似的组合方式也大量出现在 default_client_handler_test.go 等传输层测试中。
姿势三:手写 mock(连接对象场景)
当被测代码只需要一个"能通过接口编译检查、方法调用返回零值"的连接时,直接使用手写的mocks.Conn更轻量:
conn := &mocks.Conn{} ctx, err = handler.Write(ctx, conn, msg) test.Assert(t, err == nil, err)见 default_client_handler_test.go。这种风格适合不需要断言调用次数、只需占位的场景。
姿势四:mock 服务信息(服务端场景)
servicesearcher.go 将mocks.ServiceInfo()与mocks.MockServiceName等常量组合,构造出NewDefaultSvcSearcher():
svcInfo := mocks.ServiceInfo() s := map[string]*serviceinfo.ServiceInfo{ mocks.MockServiceName: svcInfo }MockSvcSearcher实现了按服务名/方法名检索ServiceInfo的逻辑,供服务端路由测试使用;mock、mockException、mockError、mockOneway、mockStreaming等方法覆盖了普通、异常、错误、单向、流式多种调用形态。
七、实践建议与注意事项
- 两类 mock 按需选择:需要严格断言"某个接口方法被调用了几次、以什么参数调用、返回什么"时,用 gomock 生成的
MockXxx+EXPECT();只需要一个"方法调用不 panic、返回零值"的占位实现时,优先用手写 mock(如mocks.Conn),代码更简洁。 - 生成即检查:运行
sh update.sh后,建议立即在仓库根目录执行go build ./...,确认新生成的 mock 与接口签名一致。若改动过源接口,mockgen 生成的代码可能与新签名不匹配,应重新生成而非手工修改DO NOT EDIT文件。 - 目录布局约束:
update.sh依赖../../../netpoll/connection.go与$GOROOT/src/net/net.go,前者要求 netpoll 与 kitex 平级存放,后者要求本地具备完整 Go 工具链源码树(标准 Go 安装默认满足)。 - 新增接口的完整流程回顾:在 update.sh 的
files数组追加一行(源文件、输出文件、包名)→ 执行sh update.sh→ 在测试中import对应包并使用NewMockXxx(ctrl)。README 中给出的这五步是整个流程的可执行规范,与脚本实现一一对应。
- 后端
- RPC框架
- 微服务
【免费下载链接】kitex
Go 微服务 RPC 框架,具有高性能、强可扩展的特点。
相关推荐
django-postgres-extra部署实战:生产环境配置与监控指南
django postgres extra部署实战:生产环境配置与监控指南 想要为你的Django项目解锁PostgreSQL的全部强大功能吗?django p
Kitex测试策略:单元测试到集成测试全流程
Kitex测试策略:单元测试到集成测试全流程 引言:微服务测试的痛点与解决方案 你是否还在为微服务测试中的依赖管理、环境一致性和覆盖率不足而困扰?作为高性能Go
后端RPC框架微服务服务注册发现负载均衡GoMock单元测试与集成测试结合:构建全面测试套件
GoMock单元测试与集成测试结合:构建全面测试套件 你是否还在为Go项目测试中模拟依赖而烦恼?是否想让单元测试与集成测试无缝协作,构建更健壮的测试套件?本文将
测试代码生成
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考