Go 测试gRPC服务

Go 测试gRPC服务,go,grpc,Go,Grpc,我想测试用Go编写的gRPC服务。我使用的示例是来自的Hello World服务器示例 protobuf的定义如下: syntax = "proto3"; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request me

我想测试用Go编写的gRPC服务。我使用的示例是来自的Hello World服务器示例

protobuf的定义如下:

syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
greeter\u服务器
main中的类型是:

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

我已经查找了一些示例,但找不到关于如何在Go中实现gRPC服务测试的任何示例。

我提出了以下实现,这可能不是最好的实现方法。主要使用
TestMain
函数使用如下goroutine启动服务器:

const (
    port = ":50051"
)

func Server() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
func TestMain(m *testing.M) {
    go Server()
    os.Exit(m.Run())
}
然后在其余测试中实现客户端:

func TestMessages(t *testing.T) {

    // Set up a connection to the Server.
    const address = "localhost:50051"
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        t.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Test SayHello
    t.Run("SayHello", func(t *testing.T) {
        name := "world"
        r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
        if err != nil {
            t.Fatalf("could not greet: %v", err)
        }
        t.Logf("Greeting: %s", r.Message)
        if r.Message != "Hello "+name {
            t.Error("Expected 'Hello world', got ", r.Message)
        }

    })
}

如果您想验证gRPC服务的实现是否符合您的预期,那么您可以只编写标准单元测试并完全忽略网络

例如,进行
greeter\u server\u测试。转到

func HelloTest(t *testing.T) {
    s := server{}

    // set up test cases
    tests := []struct{
        name string
        want string
    } {
        {
            name: "world",
            want: "Hello world",
        },
        {
            name: "123",
            want: "Hello 123",
        },
    }

    for _, tt := range tests {
        req := &pb.HelloRequest{Name: tt.name}
        resp, err := s.SayHello(context.Background(), req)
        if err != nil {
            t.Errorf("HelloTest(%v) got unexpected error")
        }
        if resp.Message != tt.want {
            t.Errorf("HelloText(%v)=%v, wanted %v", tt.name, resp.Message, tt.want)
        }
    }
}

我可能在内存中对原型语法有点混乱,但这就是我的想法。

顺便说一句:作为一个新的贡献者,我不能添加到注释中。因此,我在这里添加了一个新的答案

我可以通过在没有运行服务的情况下通过接口进行测试来确认@Omar方法可用于测试非流式gRPC服务

但是,这种方法不适用于流。由于gRPC支持双向流,因此有必要启动服务并通过网络层连接到该服务,以便对流进行测试

@joscas采用的方法适用于使用goroutine启动服务的gRPC流(即使helloworld示例代码不使用流)。但是,我注意到,在Mac OS X 10.11.6上,当从goroutine调用时,它不会一致地释放服务使用的端口(据我所知,该服务将阻止goroutine,并且可能不会干净地退出)。通过使用“exec.Command”启动一个单独的进程以运行服务,并在完成之前终止它,端口将被一致地释放

我使用streams将gRPC服务的工作测试文件上载到github:

您可以看到在travis上运行的测试:


如果您对如何改进gRPC服务的测试有任何建议,请告诉我。

这里可能是测试流媒体服务的简单方法。抱歉,如果有任何打字错误的,因为我正在适应一些运行的代码这一点

给出以下定义

rpc ListSites(Filter) returns(stream sites) 
使用以下服务器端代码

// ListSites ...
func (s *SitesService) ListSites(filter *pb.SiteFilter, stream pb.SitesService_ListSitesServer) error {
    for _, site := range s.sites {
        if err := stream.Send(site); err != nil {
            return err
        }
    }
    return nil
}
现在,您所要做的就是模拟测试文件中的pb.SitesService\u列表sitesserver

type mockSiteService_ListSitesServer struct {
    grpc.ServerStream
    Results []*pb.Site
}

func (_m *mockSiteService_ListSitesServer) Send(site *pb.Site) error {
    _m.Results = append(_m.Results, site)
    return nil
}
这将响应.send事件,并将发送的对象记录在.Results中,然后您可以在断言语句中使用这些结果

最后,通过模拟的pb.SitesService\u ListSitesServer的immplementation调用服务器代码

func TestListSites(t *testing.T) {
    s := SiteService.NewSiteService()
    filter := &pb.SiteFilter{}

    mock := &mockSiteService_ListSitesServer{}
    s.ListSites(filter, mock)

    assert.Equal(t, 1, len(mock.Results), "Sites expected to contain 1 item")
}

不,它不会测试整个堆栈,但它确实允许您检查服务器端代码,而无需麻烦地运行完整的gRPC服务,无论是真实的还是模拟的形式。

我认为您正在寻找
google.golang.org/gRPC/test/bufconn
包来帮助您避免启动具有真实端口号的服务,但仍然允许测试流式RPC

import "google.golang.org/grpc/test/bufconn"

const bufSize = 1024 * 1024

var lis *bufconn.Listener

func init() {
    lis = bufconn.Listen(bufSize)
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    go func() {
        if err := s.Serve(lis); err != nil {
            log.Fatalf("Server exited with error: %v", err)
        }
    }()
}

func bufDialer(context.Context, string) (net.Conn, error) {
    return lis.Dial()
}

func TestSayHello(t *testing.T) {
    ctx := context.Background()
    conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
    if err != nil {
        t.Fatalf("Failed to dial bufnet: %v", err)
    }
    defer conn.Close()
    client := pb.NewGreeterClient(conn)
    resp, err := client.SayHello(ctx, &pb.HelloRequest{"Dr. Seuss"})
    if err != nil {
        t.Fatalf("SayHello failed: %v", err)
    }
    log.Printf("Response: %+v", resp)
    // Test for output here.
}
这种方法的好处是,您仍然可以获得网络行为,但是通过内存连接,而不使用操作系统级资源,例如可能会或可能不会快速清理的端口。它允许您以实际使用的方式对其进行测试,并提供适当的流行为


我脑子里没有一个流媒体的例子,但神奇的酱汁就在上面。它提供了正常网络连接的所有预期行为。诀窍是如图所示设置WithDialer选项,使用bufconn包创建一个公开自己拨号程序的侦听器。我一直使用这种技术来测试gRPC服务,效果非常好。

您可以使用
空手道gRPC
来测试gRPC服务,您只需要发布您的原型jar和gRPC服务器ip/端口<代码>空手道grpc
基于空手道和多语种构建

一个hello world示例:

Feature: grpc helloworld example by grpc dynamic client

  Background:
    * def Client = Java.type('com.github.thinkerou.karate.GrpcClient')
    * def client = Client.create('localhost', 50051)

  Scenario: do it
    * def payload = read('helloworld.json')
    * def response = client.call('helloworld.Greeter/SayHello', payload)
    * def response = JSON.parse(response)
    * print response
    * match response[0].message == 'Hello thinkerou'
    * def message = response[0].message

    * def payload = read('again-helloworld.json')
    * def response = client.call('helloworld.Greeter/AgainSayHello', payload)
    * def response = JSON.parse(response)
    * match response[0].details == 'Details Hello thinkerou in BeiJing'
关于空手道grpc的示例评论:

它将生成漂亮的报告,如:


更多详细信息请参见:

您可以选择多种方法来测试gRPC服务。你可以选择不同的方式进行测试,这取决于你想要达到的自信程度。这里有三个案例说明了一些常见的场景

案例1:我想测试我的业务逻辑 在本例中,您感兴趣的是服务中的逻辑以及它如何与其他组件交互。这里要做的最好的事情就是编写一些单元测试

亚历克斯·埃利斯写了一篇好文章。 如果您需要测试交互,那么这是一条路要走。谢尔盖·格雷本希科夫写了一篇很好的文章

显示了如何对这个特定的
SayHello
示例进行单元测试

案例2:我想通过网络手动测试我的实时服务的API 在本例中,您感兴趣的是对API进行手动探索性测试。通常,这样做是为了探索实现、检查边缘情况并确信API的行为符合预期

您需要:

  • 开始你的
  • 使用线上模拟解决方案模拟您的任何依赖关系,例如,如果您的受测gRPC服务对另一个服务进行gRPC调用。例如,您可以使用
  • 使用gRPC API测试工具。例如,您可以使用
  • 现在,您可以使用模拟解决方案来模拟真实和假设的情况,同时使用API测试工具观察被测服务的行为

    案例3:我想对我的API进行自动化的在线测试 在本例中,您感兴趣的是编写自动BDD样式的验收测试,该测试通过在线gRPC API与被测系统交互。这些测试的编写、运行和维护成本都很高,并且应该节约使用,同时要记住以下几点

    这本书展示了你是如何
    var server GrpcInProcessingServer
    
    func serverStart() {
        builder := GrpcInProcessingServerBuilder{}
        builder.SetUnaryInterceptors(util.GetDefaultUnaryServerInterceptors())
        server = builder.Build()
        server.RegisterService(func(server *grpc.Server) {
            helloworld.RegisterGreeterServer(server, &testdata.MockedService{})
        })
        server.Start()
    }
    
    //TestSayHello will test the HelloWorld service using A in memory data transfer instead of the normal networking
    func TestSayHello(t *testing.T) {
        serverStart()
        ctx := context.Background()
        clientConn, err := GetInProcessingClientConn(ctx, server.GetListener(), []grpc.DialOption{})
        if err != nil {
            t.Fatalf("Failed to dial bufnet: %v", err)
        }
        defer clientConn.Close()
        client := helloworld.NewGreeterClient(clientConn)
        request := &helloworld.HelloRequest{Name: "test"}
        resp, err := client.SayHello(ctx, request)
        if err != nil {
            t.Fatalf("SayHello failed: %v", err)
        }
        server.Cleanup()
        clientConn.Close()
        assert.Equal(t, resp.Message, "This is a mocked service test")
    }