茶艺师学微服务(准备篇3)
前言
在准备篇2中,我们生成好了 proto 文件对应的 go 代码文件与 grpc 代码文件。
但是要怎么使用呢?
有这么一句话,就是“如何学会了一个东西?那就先写出它的测试样例”。
因此,在这我们就试着写一下 grpc 的测试样例。
思路
我们说到底,就是要实现服务端与客户端的通信。
只不过这次用的是 go 的 grpc 而已。
因此,待会我们的测试,还是得做以下事情:
- 实现服务端
- 模拟服务端启动
- 模拟客户端启动,并模拟发送请求
- 查看从服务端返回的信息是否符合预期
这里,我们就模拟这么一个场景:客户端存储着两个人的信息,id 为123的 Bob 和 id 为 456 的 Alice ,然后从客户端带着 id 来访问, id 对了会返回各自的名字, id 错了返回“没有这用户”
实现服务端
实现服务端,这里就按照简单的来。
还记得在proto文件里所定义的 service 吗,这里就把里面的 GetById 给定义出来就好。
在这之前,先设定好用谁来当 server 。
既然是使用 grpc ,那么就用 grpc 生成出来的 UnimplementedUserSvcServer (起不同的名字,生成的名字自然会不同,要在 _grpc.pd.go 文件里找)。
接着按照设想的逻辑,把 GetById 给定义好出来,大致是这样
type Server struct {
UnimplementedUserSvcServer
}
var _ UserSvcServer = &Server{}
func (s *Server) GetById(ctx context.Context, request *GetByIdRep) (*GetByIdResp, error) {
list := map[int64]*GetByIdResp {
123: &GetByIdResp{User: &User{
Id: 123,
Name: "Bob",
}},
456: &GetByIdResp{User: &User{
Id: 456,
Name: "Alice",
}},
}
_, ok := list[request.Id]
if !ok {
// 创建gRPC错误
err := status.Errorf(codes.NotFound, "没有该用户")
return nil, err
}
return list[request.Id], nil
}
其中需要注意的是, grpc 的错误信息,不能用简单的 errors.New() ,得用 grpc 所定义的错误,位于 google.golang.org/grpc/status 里的 status.Errorf() 。
模拟服务端启动
整个模拟过程大致有以下步骤:
- 创建一个新的gRPC服务器对象 server(同时用 defer 关键字设定好该对象的优雅停止)
- 创建一个 userServer 对象,该对象是自定义的服务器实现,用于处理用户服务。
- 使用 RegisterUserSvcServer 函数将 userServer 注册到 server 中,以便服务器能够处理来自客户端的请求。
- 使用 net.Listen 函数在本地的8090端口上监听 TCP 连接,返回一个 Listener 对象 l。(如果监听过程中发生错误,使用 panic 函数抛出异常,并终止程序的执行。)
- 使用 server.Serve(l) 启动服务器,开始监听来自客户端的请求,并将请求分发给相应的处理程序。(当服务器停止监听或发生错误时,server.Serve(l) 将返回一个错误对象 err。)
- 使用 t.Log(err) 将错误信息记录到测试日志中,以便在测试结果中进行查看。
就像这样:
func TestServer(t *testing.T) {
server := grpc.NewServer()
defer func() {
server.GracefulStop()
}()
userServer := &Server{}
RegisterUserSvcServer(server, userServer)
l, err := net.Listen("tcp", ":8090")
if err != nil {
panic(err)
}
err = server.Serve(l)
t.Log(err)
}
在 IDE 里运行该程序后,会是这样的:
这表示着服务端一直跑着,在等着客户端的请求。
模拟客户端启动,并模拟发送请求
先说模拟客服端启动,其过程是:
- 准备一个连接池
- 用连接池准备客户端
- 用客户端发起调用
由于 grpc 错误的特殊性,因此在客户端里,也要按照以下步骤处理 grpc 错误:
- 当存在错误时,先判断是不是 grpc 的 status.FromError() 错误类型(如果不是,就抛出错误)
- 再判断里面的错误码 st.Code() 是不是在服务端里定义的 codes.NotFound,是的话,读出里面的错误信息,如果不是,就抛出错误
代码示例:
// 创建gRPC连接
cc, err := grpc.Dial(":8090",
grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
client := NewUserSvcClient(cc)
_, cancel := context.WithTimeout(context.Background(), time.Second * 30)
defer cancel()
resp, err := client.GetById(context.Background(), &GetByIdRep{Id: tc.reqId})
if err != nil {
// 检查错误类型
st, ok := status.FromError(err)
if !ok {
t.Fatalf("Failed to get gRPC status: %v", err)
}
// 检查错误代码
if st.Code() == codes.NotFound {
t.Log(tc.name, "Error:", st.Message())
} else {
t.Fatalf("Unexpected error: %v", err)
}
} else {
t.Log(tc.name, resp.User)
}
然后把测试样例输进入就好。
查看从服务端返回的信息是否符合预期
实际测试结果符合预期,还行。
结语
通过编写测试样例,试了一下 grpc 的大致用法,为我们的微服务化做准备。
而测试本身,就是微服务化之前的最大准备。
这我们下次再说。