web-dev-qa-db-fra.com

Tester un service gRPC

Je voudrais tester un service gRPC écrit en Go. L'exemple que j'utilise est l'exemple de serveur Hello World du rapport repo de grpc-go .

La définition du protobuf est la suivante:

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

Et le type dans le greeter_server principal est:

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

J'ai cherché des exemples, mais je n'ai trouvé aucun moyen de mettre en œuvre des tests pour un service gRPC dans Go.

28
joscas

Si vous voulez vérifier que l'implémentation du service gRPC répond à vos attentes, vous pouvez simplement écrire des tests unitaires standard et ignorer complètement la mise en réseau.

Par exemple, 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)
        }
    }
}

J'ai peut-être un peu foiré la proto syntaxe en le faisant de mémoire, mais c'est l'idée.

34
Omar

Je pense que vous cherchez le google.golang.org/grpc/test/bufconn package pour vous aider à éviter de démarrer un service avec un numéro de port réel, tout en permettant de tester les RPC en continu.

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

L'avantage de cette approche est que vous obtenez toujours le comportement du réseau, mais via une connexion en mémoire sans utiliser de ressources au niveau du système d'exploitation, telles que des ports, pouvant ou non nettoyer rapidement. Et cela vous permet de tester la façon dont il est réellement utilisé, et vous donne un comportement correct en streaming.

Je n'ai pas d'exemple en streaming, mais la sauce magique est au-dessus. Il vous donne tous les comportements attendus d'une connexion réseau normale. L'astuce consiste à définir l'option WithDialer comme indiqué, en utilisant le paquet bufconn pour créer un écouteur qui expose son propre numéroteur. J'utilise cette technique tout le temps pour tester les services de gRPC et cela fonctionne très bien.

23
shiblon

Voici peut-être un moyen plus simple de simplement tester un service de streaming. Toutes mes excuses s'il y a une faute de frappe car je l'adapte à partir d'un code en cours d'exécution.

Étant donné la définition suivante.

rpc ListSites(Filter) returns(stream sites) 

Avec le code côté serveur suivant.

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

Maintenant, tout ce que vous avez à faire est de simuler le pb.SitesService_ListSitesServer dans votre fichier de test.

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
}

Cela répond à l'événement . Send et enregistre les objets envoyés dans .Results que vous pouvez ensuite utiliser dans vos déclarations d'assert.

Enfin, vous appelez le code serveur avec l’implémentation simulée de 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")
}

Non, il ne teste pas la totalité de la pile, mais vous permet de vérifier intégralement le code de votre serveur sans avoir à exécuter un service complet de gRPC, que ce soit de manière réelle ou fictive.

8
Simon B

Je suis venu avec la mise en œuvre suivante qui peut ne pas être la meilleure façon de le faire. Principalement en utilisant la fonction TestMain pour lancer le serveur en utilisant un goroutine comme ça:

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

puis implémentez le client dans le reste des 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

BTW: en tant que nouveau contributeur, je ne peux pas ajouter de commentaires. J'ajoute donc une nouvelle réponse ici.

Je peux confirmer que l'approche de @Omar fonctionne pour tester un service gRPC sans diffusion en testant via l'interface sans service en cours d'exécution.

Cependant, cette approche ne fonctionnera pas pour les flux. Étant donné que gRPC prend en charge les flux bidirectionnels, il est nécessaire de lancer le service et de le connecter via la couche réseau pour tester les flux.

L'approche adoptée par @joscas fonctionne pour les flux gRPC (même si l'exemple de code de helloworld n'utilise pas de flux) utilisant un goroutine pour démarrer le service. Cependant, j’ai remarqué que, sur Mac OS X 10.11.6, le port utilisé par le service n’est pas libéré de manière cohérente lorsqu’il est appelé depuis un goroutine (si je comprends bien, le service bloquera le goroutine et ne sera peut-être pas sorti proprement). En activant un processus distinct pour l'exécution du service, en utilisant 'exec.Command', et en le supprimant avant la fin, le port est libéré de manière cohérente.

J'ai téléchargé un fichier de test fonctionnel pour un service gRPC utilisant des flux vers github: https://github.com/mmcc007/go/blob/master/examples/route_guide/server/server_test.go

Vous pouvez voir les tests en cours sur travis: https://travis-ci.org/mmcc007/go

S'il vous plaît laissez-moi savoir si des suggestions sur la façon d'améliorer les tests pour les services gRPC.

7
mmccabe

Vous pouvez choisir de tester un service gRPC de plusieurs façons. Vous pouvez choisir de tester de différentes manières en fonction du type de confiance que vous souhaitez atteindre. Voici trois cas illustrant des scénarios courants.

Cas n ° 1: je veux tester ma logique métier

Dans ce cas, la logique du service et son interaction avec les autres composants vous intéressent. La meilleure chose à faire ici est d'écrire des tests unitaires.

Il y a un bon introduction aux tests unitaires dans Go par Alex Ellis. Si vous avez besoin de tester les interactions, alors GoMock est la voie à suivre. Sergey Grebenshchikov a écrit un Nice tutoriel sur GoMock .

Le réponse de Omar montre comment vous pouvez aborder le test unitaire de cet exemple particulier SayHello.

Cas n ° 2: je souhaite tester manuellement l'API de mon service en direct sur le fil

Dans ce cas, vous souhaitez effectuer des tests exploratoires manuels de votre API. En général, cela consiste à explorer l'implémentation, à vérifier les cas Edge et à avoir l'assurance que votre API se comporte comme prévu.

Tu devras:

  1. Démarrez votre serveur gRPC
  2. Utilisez une solution de moquage sur fil pour simuler toute dépendance, par exemple. si votre service gRPC testé effectue un appel gRPC vers un autre service. Par exemple, vous pouvez utiliser Traffic Parrot .
  3. Utilisez un outil de test API gRPC. Par exemple, vous pouvez utiliser un gRPC CLI .

Désormais, vous pouvez utiliser votre solution de moquage pour simuler des situations réelles et hypothétiques tout en observant le comportement du service testé en utilisant l'outil de test API.

Cas n ° 3: je veux tester automatiquement mon API

Dans ce cas, vous souhaitez rédiger des tests d'acceptation automatisés de type BDD qui interagissent avec le système testé via l'API gRPC over the wire. Ces tests coûtent cher en écriture, exécution et maintenance, et doivent être utilisés avec parcimonie, en gardant à l'esprit la pyramide de test .

Le réponse de thinkero montre comment vous pouvez utiliser karate-grpc pour écrire ces tests d'API en Java. Vous pouvez combiner cela avec le plug-in Traffic Parrot Maven pour vous moquer de toutes les dépendances sur le fil.

6
Liam Williams