测试 gRPC 服务
Testing a gRPC service
我想测试一个用 Go 编写的 gRPC 服务。我使用的示例是 grpc-go repo.
中的 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_server
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_server_test.go
:
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)
}
}
}
我可能是凭记忆搞乱了原型语法,但这就是我的想法。
顺便说一句:作为新贡献者,我不能添加评论。所以我在这里添加一个新的答案。
我可以通过没有 运行ning 服务的接口进行测试,确认@Omar 方法适用于测试非流式 gRPC 服务。
但是这种方法不适用于流。由于 gRPC 支持双向流,因此需要启动服务并通过网络层连接到服务以进行流测试。
@joscas 采用的方法适用于 gRPC 流(即使 helloworld 示例代码不使用流)使用 goroutine 启动服务。但是,我注意到在 Mac OS X 10.11.6 上,当从 goroutine 调用时,它不会一致地释放服务使用的端口(据我了解,该服务将阻止 goroutine,也许不会干净地退出)。通过为 运行 中的服务启动一个单独的进程,使用 'exec.Command',并在完成之前终止它,端口被一致地释放。
我使用流将 gRPC 服务的工作测试文件上传到 github:https://github.com/mmcc007/go/blob/master/examples/route_guide/server/server_test.go
您可以在 travis 上看到 运行ning 测试:https://travis-ci.org/mmcc007/go
如果有任何关于如何改进 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_ListSitesServer。
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_ListSitesServer.
的模拟实现调用服务器代码
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.
}
这种方法的好处是您仍然可以获得网络行为,但是通过内存连接而不使用 OS 级别的资源,例如可能会或可能不会快速清理的端口。它允许您以实际使用的方式对其进行测试,并为您提供正确的流式传输行为。
我脑子里没有流式传输示例,但神奇的酱料都在上面。它为您提供正常网络连接的所有预期行为。诀窍是如图所示设置 WithDialer 选项,使用 bufconn 包创建一个公开其自己的拨号器的侦听器。我一直使用这种技术来测试 gRPC 服务并且效果很好。
你可以使用karate-grpc
来测试grpc服务,你只需要post你的proto jar和grpc server ip/port。 karate-grpc
基于空手道和多语言构建。
一个你好世界的例子:
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'
关于karate-grpc的例子评论:
并且它会生成漂亮的报告,例如:
您可以选择多种方法来测试 gRPC 服务。您可以选择不同的测试方式,具体取决于您希望获得的自信类型。以下是说明一些常见情况的三个案例。
案例 #1:我想测试我的业务逻辑
在这种情况下,您对服务中的逻辑及其与其他组件的交互方式感兴趣。最好的办法是编写一些单元测试。
Alex Ellis 的 introduction to unit testing in Go 很好。
如果您需要测试交互,那么 GoMock is the way to go. Sergey Grebenshchikov wrote a nice GoMock tutorial.
展示了如何对这个特定的 SayHello
示例进行单元测试。
案例 #2:我想手动测试在线服务的 API
在这种情况下,您有兴趣对 API 进行手动探索性测试。通常这样做是为了探索实施、检查边缘情况并确信您的 API 表现符合预期。
您将需要:
- 开始你的gRPC server
- 使用在线模拟解决方案来模拟您拥有的任何依赖项,例如如果您的被测 gRPC 服务对另一个服务进行 gRPC 调用。例如,您可以使用 Traffic Parrot.
- 使用 gRPC API 测试工具。例如,您可以使用 gRPC CLI.
现在您可以使用模拟解决方案模拟真实和假设情况,同时使用 API 测试工具观察被测服务的行为。
案例 #3:我想通过线路自动测试我的 API
在这种情况下,您有兴趣编写自动化 BDD 样式的验收测试,这些测试通过有线 gRPC API 与被测系统交互。这些测试的编写、运行 和维护成本很高,应谨慎使用,请牢记 testing pyramid.
shows how you can use karate-grpc to write those API tests in Java. You can combine this with the Traffic Parrot Maven plugin 模拟任何在线依赖项。
作为一个新的贡献者,我不能发表评论,所以我在这里添加作为答案。
@shiblon 的回答是测试您的服务的最佳方式。我是 grpc-for-production 的维护者,其中一个功能是处理服务器,这使得使用 bufconn 更容易。
这是一个测试迎宾服务的例子
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")
}
你可以找到这个例子here
我想测试一个用 Go 编写的 gRPC 服务。我使用的示例是 grpc-go repo.
中的 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_server
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_server_test.go
:
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)
}
}
}
我可能是凭记忆搞乱了原型语法,但这就是我的想法。
顺便说一句:作为新贡献者,我不能添加评论。所以我在这里添加一个新的答案。
我可以通过没有 运行ning 服务的接口进行测试,确认@Omar 方法适用于测试非流式 gRPC 服务。
但是这种方法不适用于流。由于 gRPC 支持双向流,因此需要启动服务并通过网络层连接到服务以进行流测试。
@joscas 采用的方法适用于 gRPC 流(即使 helloworld 示例代码不使用流)使用 goroutine 启动服务。但是,我注意到在 Mac OS X 10.11.6 上,当从 goroutine 调用时,它不会一致地释放服务使用的端口(据我了解,该服务将阻止 goroutine,也许不会干净地退出)。通过为 运行 中的服务启动一个单独的进程,使用 'exec.Command',并在完成之前终止它,端口被一致地释放。
我使用流将 gRPC 服务的工作测试文件上传到 github:https://github.com/mmcc007/go/blob/master/examples/route_guide/server/server_test.go
您可以在 travis 上看到 运行ning 测试:https://travis-ci.org/mmcc007/go
如果有任何关于如何改进 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_ListSitesServer。
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_ListSitesServer.
的模拟实现调用服务器代码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.
}
这种方法的好处是您仍然可以获得网络行为,但是通过内存连接而不使用 OS 级别的资源,例如可能会或可能不会快速清理的端口。它允许您以实际使用的方式对其进行测试,并为您提供正确的流式传输行为。
我脑子里没有流式传输示例,但神奇的酱料都在上面。它为您提供正常网络连接的所有预期行为。诀窍是如图所示设置 WithDialer 选项,使用 bufconn 包创建一个公开其自己的拨号器的侦听器。我一直使用这种技术来测试 gRPC 服务并且效果很好。
你可以使用karate-grpc
来测试grpc服务,你只需要post你的proto jar和grpc server ip/port。 karate-grpc
基于空手道和多语言构建。
一个你好世界的例子:
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'
关于karate-grpc的例子评论:
并且它会生成漂亮的报告,例如:
您可以选择多种方法来测试 gRPC 服务。您可以选择不同的测试方式,具体取决于您希望获得的自信类型。以下是说明一些常见情况的三个案例。
案例 #1:我想测试我的业务逻辑
在这种情况下,您对服务中的逻辑及其与其他组件的交互方式感兴趣。最好的办法是编写一些单元测试。
Alex Ellis 的 introduction to unit testing in Go 很好。 如果您需要测试交互,那么 GoMock is the way to go. Sergey Grebenshchikov wrote a nice GoMock tutorial.
SayHello
示例进行单元测试。
案例 #2:我想手动测试在线服务的 API
在这种情况下,您有兴趣对 API 进行手动探索性测试。通常这样做是为了探索实施、检查边缘情况并确信您的 API 表现符合预期。
您将需要:
- 开始你的gRPC server
- 使用在线模拟解决方案来模拟您拥有的任何依赖项,例如如果您的被测 gRPC 服务对另一个服务进行 gRPC 调用。例如,您可以使用 Traffic Parrot.
- 使用 gRPC API 测试工具。例如,您可以使用 gRPC CLI.
现在您可以使用模拟解决方案模拟真实和假设情况,同时使用 API 测试工具观察被测服务的行为。
案例 #3:我想通过线路自动测试我的 API
在这种情况下,您有兴趣编写自动化 BDD 样式的验收测试,这些测试通过有线 gRPC API 与被测系统交互。这些测试的编写、运行 和维护成本很高,应谨慎使用,请牢记 testing pyramid.
作为一个新的贡献者,我不能发表评论,所以我在这里添加作为答案。
@shiblon 的回答是测试您的服务的最佳方式。我是 grpc-for-production 的维护者,其中一个功能是处理服务器,这使得使用 bufconn 更容易。
这是一个测试迎宾服务的例子
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")
}
你可以找到这个例子here