猿问

使用 WithBlock() 选项创建 gRPC 客户端连接到异步启动的 gRPC

我想编写一个单元测试,在其中运行一个临时 gRPC 服务器,该服务器在测试中的一个单独的 Goroutine 中启动,并在测试运行后停止。为此,我尝试将本教程(https://grpc.io/docs/languages/go/quickstart/)中的“Hello, world”示例改编为其中的一个,而不是服务器和客户端main.gos,有一个测试函数异步启动服务器,随后使用该grpc.WithBlock()选项建立客户端连接。


我已将简化示例放在此存储库中,https://github.com/kurtpeek/grpc-helloworld;这是main_test.go:


package main


import (

    "context"

    "fmt"

    "log"

    "net"

    "testing"

    "time"


    "github.com/stretchr/testify/require"

    "google.golang.org/grpc"

    "google.golang.org/grpc/examples/helloworld/helloworld"

)


const (

    port = ":50051"

)


type server struct {

    helloworld.UnimplementedGreeterServer

}


func (s *server) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) {

    log.Printf("Received: %v", in.GetName())

    return &helloworld.HelloReply{Message: "Hello " + in.GetName()}, nil

}


func TestHelloWorld(t *testing.T) {

    lis, err := net.Listen("tcp", port)

    require.NoError(t, err)


    s := grpc.NewServer()

    helloworld.RegisterGreeterServer(s, &server{})

    go s.Serve(lis)

    defer s.Stop()


    log.Println("Dialing gRPC server...")

    conn, err := grpc.Dial(fmt.Sprintf("localhost:%s", port), grpc.WithInsecure(), grpc.WithBlock())

    require.NoError(t, err)

    defer conn.Close()

    c := helloworld.NewGreeterClient(conn)


    ctx, cancel := context.WithTimeout(context.Background(), time.Second)

    defer cancel()


    log.Println("Making gRPC request...")

    r, err := c.SayHello(ctx, &helloworld.HelloRequest{Name: "John Doe"})

    require.NoError(t, err)

    log.Printf("Greeting: %s", r.GetMessage())

}

问题是当我运行这个测试时,它会超时:


> go test -timeout 10s ./... -v

=== RUN   TestHelloWorld

2020/06/30 11:17:45 Dialing gRPC server...

panic: test timed out after 10s

我无法理解为什么没有建立连接?在我看来,服务器已正确启动...


眼眸繁星
浏览 337回答 1
1回答

繁星coding

您在此处发布的代码似乎有错字:fmt.Sprintf("localhost:%s", port)如果我在没有该grpc.WithBlock()选项的情况下运行您的测试功能,c.SayHello则会出现以下错误:rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial tcp: address localhost::50051: too many colons in address"罪魁祸首似乎是localhost::50051从const声明中删除多余的冒号后(或从 fmt.Sprintf("localhost:%s", port),如果您愿意),测试通过。const (    port = "50051" // without the colon)输出:2020/06/30 23:59:01 Dialing gRPC server...2020/06/30 23:59:01 Making gRPC request...2020/06/30 23:59:01 Received: John Doe2020/06/30 23:59:01 Greeting: Hello John Doe但是,从文档grpc.WithBlock()如果没有这个,Dial 会立即返回并在后台连接服务器。因此,使用此选项,应立即从grpc.Dial调用返回任何连接错误:conn, err := grpc.Dial("bad connection string", grpc.WithBlock()) // can't connectif err != nil {    panic(err) // should panic, right?}那么为什么你的代码会挂起?通过查看grpc包的源代码(我针对 构建了测试v1.30.0):    // A blocking dial blocks until the clientConn is ready.    if cc.dopts.block {        for {            s := cc.GetState()            if s == connectivity.Ready {                break            } else if cc.dopts.copts.FailOnNonTempDialError && s == connectivity.TransientFailure {                if err = cc.connectionError(); err != nil {                    terr, ok := err.(interface {                        Temporary() bool                    })                    if ok && !terr.Temporary() {                        return nil, err                    }                }            }            if !cc.WaitForStateChange(ctx, s) {                // ctx got timeout or canceled.                if err = cc.connectionError(); err != nil && cc.dopts.returnLastError {                    return nil, err                }                return nil, ctx.Err()            }        }所以s此时确实处于TransientFailure状态,但FailOnNonTempDialError选项默认为false,并且WaitForStateChange在上下文过期时为 false ,这不会发生,因为Dial与后台上下文一起运行:// Dial creates a client connection to the given target.func Dial(target string, opts ...DialOption) (*ClientConn, error) {    return DialContext(context.Background(), target, opts...)}在这一点上,我不知道这是否是预期的行为,因为其中一些 APIv1.30.0被标记为实验性的。无论如何,最终为了确保你捕捉到这种错误,Dial你也可以将你的代码重写为:    conn, err := grpc.Dial(        "localhost:50051",         grpc.WithInsecure(),        grpc.FailOnNonTempDialError(true),        grpc.WithBlock(),     )如果连接字符串错误,则会立即失败并显示相应的错误消息。
随时随地看视频慕课网APP

相关分类

Go
我要回答