贝利信息

如何在Golang中模拟RPC调用_本地测试实现技巧

日期:2026-01-21 00:00 / 作者:P粉602998670
用 net/rpc 搭建本地 RPC 服务做单元测试的最轻量方式是使用 bytes.Buffer 模拟传输层,配合 gob 编解码,无需网络端口;需确保结构体字段导出、使用独立读写 buffer、正确启停协程,并注意 codec 一致性与 goroutine 生命周期管理。

net/rpc 搭建本地 RPC 服务做单元测试

Go 标准库的 net/rpc 支持内存通道(rpc.NewServer() + server.ServeCodec())直接对接 gob.NewEncoder/Decoder,无需启动 TCP 端口就能完成完整 RPC 流程。这是本地测试最轻量、最可控的方式。

bytes.Buffer 替代网络连接实现零开销编解码

RPC 的核心是请求序列化 → 传输 → 反序列化 → 执行 → 返回序列化 → 传输 → 反序列化。用 bytes.Buffer 模拟“传输层”,能跳过 socket 创建、超时、重连等无关逻辑,只验证业务协议是否正确。

buf := new(bytes.Buffer)
server := rpc.NewServer()
server.RegisterName("Arith", &Arith{})
codec := gob.NewGobServerCodec(buf, buf) // 注意:两个 buf 分别用于读/写
go server.ServeCodec(codec)

// 客户端复用同一个 buf 做通信 client := rpc.NewClientWithCodec(gob.NewGobClientCodec(buf, buf)) defer client.Close()

args := &ArithArgs{A: 10, B: 3} var reply ArithReply err := client.Call("Arith.Multiply", args, &reply)

测试异步通知类方法(如订阅、回调)要手动控制执行顺序

标准 net/rpc 不支持服务端主动推消息。若业务中有类似 Subscribe(req *Req, stream Stream) 这种伪流式接口,本地测试时需把 stream 替换为自定义结构体,内部用 chan 模拟推送行为。

type MockStream struct {
    ch chan interface{}
}

func (m *MockStream) Send(v interface{}) error { m.ch <- v return nil }

// 在测试中: stream := &MockStream{ch: make(chan interface{}, 10)} go func() { // 模拟服务端往 stream 写数据 stream.Send(&Event{ID: "e1"}) stream.Send(&Event{ID: "e2"}) }()

// 客户端从 stream 接收 for i := 0; i < 2; i++ { select { case ev := <-stream.ch: // 处理事件 } }

替换真实 RPC 客户端为 mock 结构体更简单

如果只是想绕过网络调用验证上层逻辑(比如某个 handler 里调用了 userSvc.GetUser()),直接定义一个符合接口的 mock 结构体比启动本地 RPC 服务更轻量。

type MockUserSvc struct {
    GetUserFunc func(ctx context.Context, id int64) (*User, error)
}

func (m MockUserSvc) GetUser(ctx context.Context, id int64) (User, error) { return m.GetUserFunc(ctx, id) }

// 测试中: svc := &MockUserSvc{ GetUserFunc: func(ctx context.Context, id int64) (*User, error) { return &User{Name: "test"}, nil }, } handler := NewHandler(svc) // 调用 handler 方法,内部会走 mock 的 GetUser

本地测试 RPC 最容易忽略的是 codec 一致性与 goroutine 生命周期管理——服务端没启协程、客户端没关连接、buffer 读写错位,都会让测试看似“跑通”实则没触发任何业务逻辑。