web-dev-qa-db-ja.com

gRPCサービスのテスト

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メインのタイプは次のとおりです。

// 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サービスのテストを実装する方法が見つかりませんでした。

28
joscas

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

私はメモリからプロトシンタックスを少し台無しにしたかもしれませんが、それはアイデアです。

34
Omar

実際のポート番号でサービスを開始することを回避するのに役立つ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(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.
}

このアプローチの利点は、ネットワークの動作が引き続き得られることですが、ポートなどのOSレベルのリソースを使用せずにメモリ内接続を介して、すぐにクリーンアップできる場合としない場合があります。そして、実際に使用されている方法でテストでき、適切なストリーミング動作を提供します。

頭の上のストリーミングの例はありませんが、魔法のソースはすべて上にあります。通常のネットワーク接続で予想されるすべての動作を提供します。トリックは、示されているようにWithDialerオプションを設定し、bufconnパッケージを使用して、独自のダイヤラーを公開するリスナーを作成します。私はこの手法を常にgRPCサービスのテストに使用していますが、うまく機能しています。

23
shiblon

これは、ストリーミングサービスをテストするだけの簡単な方法です。いくつかの実行中のコードからこれを改作しているので、タイプミスがあればおApびします。

次の定義を考えます。

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サービスを実行する手間をかけずに、サーバー側のコードを健全性チェックできます。

8
Simon B

次の実装を思いついたのですが、これは最善の方法ではないかもしれません。主に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)
        }

    })
}
8
joscas

ところで:新しい貢献者として、私はコメントに追加することはできません。そこで、ここに新しい回答を追加します。

サービスを実行せずにインターフェイス経由でテストすることで、@ Omarアプローチが非ストリーミングgRPCサービスのテストに有効であることを確認できます。

ただし、このアプローチはストリームでは機能しません。 gRPCは双方向ストリームをサポートしているため、ストリームのテストを行うには、サービスを起動し、ネットワーク層を介してサービスに接続する必要があります。

@joscasがとるアプローチは、サービスを開始するゴルーチンを使用して、gRPCストリームに対して機能します(helloworldサンプルコードはストリームを使用しませんが)。ただし、Mac OS X 10.11.6では、ゴルーチンから呼び出されたときに一貫してサービスが使用するポートを解放しないことに気付きました(理解したように、サービスはゴルーチンをブロックし、おそらく正常に終了しません)。 「exec.Command」を使用してサービスを実行する別のプロセスを起動し、終了する前に終了することにより、ポートが一貫して解放されます。

ストリームを使用してgRPCサービスの動作テストファイルをgithubにアップロードしました: https://github.com/mmcc007/go/blob/master/examples/route_guide/server/server_test.go

Travisで実行されているテストを確認できます。 https://travis-ci.org/mmcc007/go

GRPCサービスのテストを改善する方法について何か提案があれば教えてください。

7
mmccabe

GRPCサービスをテストするために選択できる方法は多数あります。達成したい自信の種類に応じて、さまざまな方法でテストすることを選択できます。一般的なシナリオを示す3つのケースを次に示します。

ケース1:ビジネスロジックをテストしたい

この場合、サービスのロジックと、それが他のコンポーネントとどのように対話するかに興味があります。ここで行うのに最適なことは、いくつかの単体テストを書くことです。

Goでの単体テストの概要 Alex Ellisがあります。相互作用をテストする必要がある場合は、 GoMock が最適です。 Sergey GrebenshchikovがNice GoMock tutorial を書きました。

Omarからの回答 は、この特定のSayHelloの例をテストするユニットにアプローチする方法を示しています。

ケース#2:ネットワーク経由でライブサービスのAPIを手動でテストしたい

この場合、手動でAPIの探索的テストを行うことに興味があります。通常、これは実装を調査し、Edgeのケースをチェックし、APIが期待どおりに動作するという信頼を得るために行われます。

あなたがする必要があります:

  1. gRPCサーバー を起動します
  2. ワイヤー経由のモックソリューションを使用して、依存関係をモックします。テスト対象のgRPCサービスが別のサービスにgRPC呼び出しを行う場合。たとえば、 Traffic Parrot を使用できます。
  3. GRPC APIテストツールを使用します。たとえば、 gRPC CLI を使用できます。

これで、モックソリューションを使用して、APIテストツールを使用して、テスト中のサービスの動作を観察しながら、実際の状況と仮想の状況をシミュレートできます。

ケース#3:APIの有線テストを自動化したい

この場合、ワイヤgRPC APIを介してテスト対象システムと対話する自動BDDスタイルの受け入れテストを作成することに興味があります。これらのテストは、記述、実行、保守に費用がかかるため、 テストピラミッド を念頭に置いて、控えめに使用する必要があります。

thinkerouからの回答 は、 karate-grpc を使用してこれらのAPIテストをJavaで記述する方法を示しています。これを Traffic Parrot Mavenプラグイン と組み合わせて、有線の依存関係をモックできます。

6
Liam Williams