wake-up-neo.com

Testen eines gRPC-Dienstes

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.

28
joscas

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.

34
Omar

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.

23
shiblon

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.

8
Simon B

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)
        }

    })
}
8
joscas

Ü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.

7
mmccabe

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.

Fall 1: Ich möchte meine Geschäftslogik testen

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.

Fall 2: Ich möchte die API meines Live-Dienstes manuell über das Kabel testen

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:

  1. Starten Sie Ihren gRPC-Server
  2. Verwenden Sie eine drahtlose Verspottungslösung, um alle Abhängigkeiten zu verspotten, die Sie z. Wenn Ihr zu testender gRPC-Dienst einen gRPC-Anruf an einen anderen Dienst tätigt. Zum Beispiel können Sie Verkehrspapagei verwenden.
  3. Verwenden Sie ein gRPC-API-Testtool. Beispielsweise können Sie ein gRPC CLI verwenden.

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.

Fall 3: Ich möchte das Testen meiner API über Kabel automatisieren

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.

6
Liam Williams