Help us understand the problem. What is going on with this article?

[gRPC-Go] Serverのテストをネットワーク接続なしで行う

gRPC Serverのテスト

gRPCサーバーは通常はTCPサーバーを立てる必要があり、テストしようとすると面倒です。

そこで、通信部分をin-memoryに差し替えてくれるgrpc/test/bufconnというパッケージを利用すると便利です。

bufconnはネットワークサーバーのように振る舞うListenerを持ち、さらにDial()メソッドでクライアント接続のように振る舞うnet.Connも持つという特徴があります。

bufconnを使ってネットワーク接続なしでテスト

それでは、実際にbufconnを使ったテストを書いてみます。

ソースコードは次のリポジトリにも公開しています。
https://github.com/castaneai/grpc-testing-with-bufconn

たとえばgRPCの公式チュートリアルにもある次のようなシンプルなRPCで考えます。

syntax = "proto3";

package hello;

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

protoc でGoの定義ファイルを生成して、次のようなディレクトリ構成とします。

$ protoc greeter.proto --go_out=plugins=grpc:.
.
├── go.mod
├── go.sum
├── greeter.pb.go
├── greeter.proto
└── server
   ├── server.go
   └── server_test.go

server/ ディレクトリ以下に2つのファイルを作ります。

  • server.go サーバーの実装
  • server_test.go サーバーのテスト(bufconnを使う部分)

server.go何も特別なことはなく、通常のgRPCサーバーの実装です。

次に、 server_test.go ですがここが重要です!

gRPC-Goではgrpc.Dial()でサーバーへ接続をしますが、そこで grpc.DialContext(grpc.WithCotnextDialer(...)) を使います。

WithContextDialerの中身をbufconnへの接続 lis.Dial() に差し替えるというやり方です。

server_test.go
package main

import (
    "context"
    "log"
    "net"
    "testing"

    "google.golang.org/grpc"
    "google.golang.org/grpc/test/bufconn"

    pb "github.com/castaneai/grpc-testing-with-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.Fatal(err)
        }
    }()
}

func bufDialer(ctx context.Context, address string) (net.Conn, error) {
    return lis.Dial()
}

func TestSayHello(t *testing.T) {
    ctx := context.Background()
    conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
    if err != nil {
        t.Fatal(err)
    }
    defer conn.Close()

    client := pb.NewGreeterClient(conn)
    resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "test"})
    if err != nil {
        t.Fatal(err)
    }

    if resp.GetMessage() != "Hello test" {
        t.Fatal("hello reply must be 'Hello test'")
    }
}

これでテストを実行してみると、即座にテストが通りました! :smile:

$ go test -v ./...
=== RUN   TestSayHello
2019/10/04 12:50:04 Received: test
--- PASS: TestSayHello (0.00s)
PASS
ok      github.com/castaneai/grpc-testing-with-bufconn/server   0.024s
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした