Тестирование службы gRPC
Я хотел бы проверить службу gRPC, написанную в Go. Пример, который я использую, - пример сервера Hello World из grpc-go repo.
определение 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
главное:
// 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
}
Я искал примеры, но я не мог найти ни одного о том, как реализовать тесты для службы gRPC в Go.
5 ответов
Если вы хотите проверить, что реализация службы gRPC делает то, что вы ожидаете, то вы можете просто написать стандартные модульные тесты и полностью игнорировать сеть.
например, make 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)
}
}
}
возможно, я немного испортил синтаксис proto, делая это из памяти, но это идея.
Я придумал следующую реализацию, которая может быть не лучшим способом сделать это. В основном с помощью TestMain
функция для вращения сервера с помощью горутина вот так:
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)
}
})
}
BTW: как новый участник, я не могу добавлять комментарии. Поэтому я добавляю новый ответ.
Я могу подтвердить, что подход @Omar работает для тестирования не потоковой службы gRPC путем тестирования через интерфейс без запущенной службы.
однако этот подход не будет работать для потоков. Поскольку gRPC поддерживает двунаправленные потоки, необходимо запустить службу и подключиться к ней через сетевой уровень для тестирования потоков.
в подход, который @joscas принимает работает для потоков gRPC (даже если пример кода helloworld не использует потоки), используя goroutine для запуска службы. Однако я заметил, что в Mac OS X 10.11.6 он не освобождает порт, используемый службой последовательно при вызове из goroutine (как я понимаю, служба заблокирует goroutine и, возможно, не выйдет чисто). Запустив отдельный процесс для запуска службы, используя ' exec.Командование', и убить его, прежде чем закончить, порт выпускается последовательно.
Я загрузил рабочий тестовый файл для службы gRPC с помощью потоков в github: https://github.com/mmcc007/go/blob/master/examples/route_guide/server/server_test.go
вы можете видеть тесты на Тревиса: 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 в файле tests.
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
}
это отвечает .отправить событие и записывает отправленные объекты .Результаты, которые затем можно использовать в утверждениях assert.
наконец, вы вызываете код сервера с издевательским иммплементацией 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 либо для реального, либо в макетной форме.