Ich möchte einen in Go geschriebenen gRPC-Service testen. Das Beispiel, das ich verwende, ist das Hello World-Server-Beispiel aus grpc-go repo .
Die Protobuf-Definition lautet wie folgt:
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;
}
Und der Typ in der greeter_server
main ist:
// 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
}
Ich habe nach Beispielen gesucht, konnte jedoch keine Anleitungen zum Implementieren von Tests für einen gRPC-Dienst in Go finden.
Wenn Sie überprüfen möchten, ob die Implementierung des gRPC-Dienstes Ihren Erwartungen entspricht, können Sie einfach Standard-Komponententests schreiben und das Netzwerk vollständig ignorieren.
Zum Beispiel machen 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)
}
}
}
Ich hätte die Protosyntax vielleicht ein bisschen durcheinander gebracht, aber das ist die Idee.
Ich denke, Sie suchen nach dem google.golang.org/grpc/test/bufconn
-Paket, um zu vermeiden, dass ein Dienst mit einer echten Portnummer gestartet wird, das Testen von Streaming-RPCs jedoch weiterhin möglich ist.
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(string, time.Duration) (net.Conn, error) {
return lis.Dial()
}
func TestSayHello(t *testing.T) {
ctx := context.Background()
conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithDialer(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.
}
Der Vorteil dieses Ansatzes besteht darin, dass Sie immer noch Netzwerkverhalten erhalten, jedoch über eine In-Memory-Verbindung, ohne Ressourcen auf Betriebssystemebene wie Ports zu verwenden, die möglicherweise schnell bereinigt werden oder nicht. Und Sie können es testen, wie es tatsächlich verwendet wird, und es gibt Ihnen das richtige Streaming-Verhalten.
Ich habe kein Streaming-Beispiel auf dem Kopf, aber die magische Sauce ist alles darüber. Sie erhalten alle erwarteten Verhaltensweisen einer normalen Netzwerkverbindung. Der Trick besteht darin, die Option WithDialer wie gezeigt festzulegen und mit dem Paket bufconn einen Listener zu erstellen, der seinen eigenen Dialer verfügbar macht. Ich verwende diese Technik die ganze Zeit zum Testen von gRPC-Diensten und sie funktioniert hervorragend.
Hier ist möglicherweise eine einfachere Möglichkeit, nur einen Streaming-Dienst zu testen. Entschuldigung, wenn es Tippfehler gibt, da ich dies von einem laufenden Code anpasse.
Angesichts der folgenden Definition.
rpc ListSites(Filter) returns(stream sites)
Mit dem folgenden serverseitigen Code.
// 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
}
Jetzt müssen Sie nur noch das pb.SitesService_ListSitesServer in Ihrer Testdatei verspotten.
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
}
Dieser reagiert auf das Ereignis . Send und zeichnet die gesendeten Objekte in .Results auf, die Sie dann in Ihren assert-Anweisungen verwenden können.
Schließlich rufen Sie den Servercode mit der verspotteten Implementierung von pb.SitesService_ListSitesServer auf.
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")
}
Nein, es wird nicht der gesamte Stack getestet, aber Sie können Ihren serverseitigen Code sicher überprüfen, ohne einen vollständigen gRPC-Service entweder in realer oder in nachgemachter Form ausführen zu müssen.
Ich habe die folgende Implementierung gefunden, die möglicherweise nicht die beste Art ist, dies zu tun. Hauptsächlich wird die Funktion TestMain
verwendet, um den Server mit einem Goroutine wie folgt hochzufahren:
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())
}
und implementieren Sie dann den Client in den restlichen Tests:
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)
}
})
}
Übrigens: Als neuer Mitwirkender kann ich keine Kommentare hinzufügen. Deshalb füge ich hier eine neue Antwort hinzu.
Ich kann bestätigen, dass der @ Omar-Ansatz zum Testen eines Nicht-Streaming-gRPC-Dienstes funktioniert, indem er über die Schnittstelle getestet wird, ohne dass ein Dienst ausgeführt wird.
Dieser Ansatz funktioniert jedoch nicht für Streams. Da gRPC bidirektionale Streams unterstützt, muss der Dienst gestartet und über die Netzwerkschicht mit ihm verbunden werden, um Streams zu testen.
Der Ansatz, den @joscas verfolgt, funktioniert für gRPC-Streams (obwohl der Beispielcode von helloworld keine Streams verwendet), die eine Goroutine zum Starten des Diensts verwenden. Ich habe jedoch festgestellt, dass unter Mac OS X 10.11.6 der vom Dienst verwendete Port nicht konsistent freigegeben wird, wenn er von einer Goroutine aufgerufen wird (soweit ich weiß, blockiert der Dienst die Goroutine und beendet sie möglicherweise nicht ordnungsgemäß). Wenn Sie einen separaten Prozess starten, in dem der Dienst ausgeführt werden soll, indem Sie 'exec.Command' verwenden und ihn vor dem Abschluss beenden, wird der Port konsistent freigegeben.
Ich habe eine funktionierende Testdatei für einen gRPC-Dienst mit Streams zu github hochgeladen: https://github.com/mmcc007/go/blob/master/examples/route_guide/server/server_test.go
Sie können die Tests auf Travis sehen: https://travis-ci.org/mmcc007/go
Bitte lassen Sie mich wissen, wenn Sie Vorschläge zur Verbesserung der Tests für gRPC-Dienste haben.
Es gibt viele Möglichkeiten, einen gRPC-Dienst zu testen. Abhängig von der Art des Vertrauens, das Sie erreichen möchten, können Sie auf verschiedene Arten testen. In den folgenden drei Fällen werden einige häufige Szenarien veranschaulicht.
In diesem Fall interessieren Sie sich für die Logik des Dienstes und dessen Interaktion mit anderen Komponenten. Schreiben Sie hier am besten einige Unit-Tests.
Es gibt eine gute Einführung in Unit-Tests in Go von Alex Ellis. Wenn Sie Interaktionen testen müssen, ist GoMock der richtige Weg. Sergey Grebenshchikov hat ein Nice GoMock-Tutorial geschrieben.
Das Antwort von Omar zeigt, wie Sie dieses spezielle SayHello
Beispiel testen können.
In diesem Fall möchten Sie Ihre API manuell testen. In der Regel wird dies durchgeführt, um die Implementierung zu untersuchen, Edge-Fälle zu überprüfen und sicherzustellen, dass sich Ihre API wie erwartet verhält.
Du wirst brauchen:
Jetzt können Sie Ihre Spottlösung verwenden, um reale und hypothetische Situationen zu simulieren, während Sie mithilfe des API-Testtools das Verhalten des zu testenden Dienstes beobachten.
In diesem Fall möchten Sie automatisierte BDD-Akzeptanztests schreiben, die über die over the wire gRPC-API mit dem zu testenden System interagieren. Diese Tests sind teuer in Schreiben, Ausführen und Warten und sollten unter Berücksichtigung der Testpyramide sparsam eingesetzt werden.
Das Antwort von thinkero zeigt, wie Sie karate-grpc verwenden können, um diese API-Tests in Java zu schreiben. Sie können dies mit dem Traffic Parrot Maven-Plugin kombinieren, um über die Drahtabhängigkeiten zu spotten.