【问题标题】:Spoofing grpc UnaryHandler for unit testing gRPC in Go欺骗 grpc UnaryHandler 以在 Go 中对 gRPC 进行单元测试
【发布时间】:2020-01-01 00:34:50
【问题描述】:

我正在努力提高我的 Go gRPC 服务器的覆盖率,但我在为服务器的拦截器函数编写测试时遇到了麻烦,因为我无法有意义地满足 UnaryHandler 类型。

我有一个函数Interceptor,签名如下:

Interceptor func(
  ctx context.Context,
  req interface{},
  info *grpc.UnaryServerInfo,
  handler grpc.UnaryHandler, // <- my issue comes from here
) (interface{}, error)

我假设任何 gRPC 方法都会满足 UnaryHandler 的签名:

type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error)

所以我尝试传入一个带有这个签名的方法:

GetToken(ctx context.Context, req *AuthData) (*Token, error)

我认为这会起作用,因为这就是拦截器实际上正在做的事情(转发该 RPC),但出于某种原因,Go 抱怨:

不能在 Interceptor 的参数中使用 authService.GetToken (type func(context.Context, *AuthData) (*Token, error)) 作为类型 grpc.UnaryHandler

我继续写了一个正确满足的虚拟函数:

func genericHandler(ctx context.Context, req interface{}) (interface{}, error) {
    return req, nil
}

这很好,因为我在测试拦截器时并不特别需要运行任何特定的方法。但是我很好奇为什么实际方法不满足约束,因为(根据我的理解)每当我在野外调用该 RPC 时,它都会被传递给幕后的拦截器函数。

最可能的解释是 grpc UnaryHandler 没有做我认为的那样,但是它做了什么?

【问题讨论】:

  • 难道不是因为它需要interface{} 并且您正在传递并返回一个引用吗?也许它会匹配*interface{}?也就是说,在实现方面,它应该匹配一个指针,因为接口假定一个指针,但我不确定语法。

标签: go testing grpc


【解决方案1】:

不,函数

GetToken(ctx context.Context, req *AuthData) (*Token, error)

的类型不同
type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error)

GetToken 中,第二个参数req 的类型为*AuthData,而UnaryHandler 中的req 的类型为interface{}。返回的*Tokeninterface{} 的类型也不相同。这就是不能将GetToken 直接传递给拦截器的原因。

在您的 grpc 服务中,您可以编写类似

的方法
GetToken(ctx context.Context, req *AuthData) (*Token, error)

作为处理程序来执行您的服务器工作。然而,这并不是人们想象的UnaryHandler

大部分转换是由 grpc/protobuf 代码生成器完成的。根据您的 proto 定义,它会生成一个接口,如下所示:

type XXXServer interface {
    GetToken(ctx context.Context, req *AuthData) (*Token, error)
}

您可以看到您的处理程序满足的正是这个接口(不是 UnaryHander)。

在后台,如果您查看生成的xxx.pb.go 文件,您会发现一些_XXX_GetToken_Handler 实际上正在执行处理程序工作。在这个函数中,一个(实际的)UnaryHandler 被定义为:

func _XXX_GetToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
    // skip other preparations...
    // 
    handler := func(ctx context.Context, req interface{}) (interface{}, error) {
        return srv.(XXXServer).GetToken(ctx, req.(*AuthData))
    }
    return interceptor(ctx, in, info, handler)
}

在这个UnaryHandler 中,它会将您的服务器转换为XXXServer 接口,然后调用您的处理程序(您的代码)。这显示了interceptor 的调用方式。

【讨论】:

  • 我理解这一点,我很欣赏你对底层实现中发生的事情的出色解释,但我仍然有一点困惑,不应该有任何值满足interface{} 吗? req *AuthDatareq interface{} 不一样吗?由于 interface{} 用于满足 go 中的未知类型约束,我假设这些函数实际上是相同的类型,所以我想知道类型错误来自哪里
  • @RobbieMilejczak Go 是静态类型的。 Go 中的接口仍然是类型。 req *AuthDatareq interface{} 的静态类型不一样(很明显,一个是 *AuthData,另一个是 interface{})。假设你声明了一个变量var req interface{},你可以将其他类型的值赋给这个变量,只要它们满足这个接口(在这种特殊情况下,任何类型都可以满足)。例如,您可以写x := 1; req = x。但是这一事实并没有使reqx 成为相同的静态类型。
  • 类似地,对于函数 type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error),您可以将任何类型的值作为 req 参数传递给 UnaryHandler,这一事实不会使静态类型接口{} 与 * 相同验证数据。因此我们可以知道我们讨论的两个函数也不是同一个静态类型。
猜你喜欢
  • 2019-10-12
  • 2020-03-21
  • 1970-01-01
  • 2013-04-03
  • 2022-10-24
  • 1970-01-01
  • 2016-09-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多