gRPCのクライアントが絡むテスト

golang.tokyo #28

4 December 2019

dice_zu(daisuzu)

gRPCのクライアント

自分たちが作っているgRPCサーバのクライアントとか、
実は3rd-partyパッケージの中で使われていたりとか。

2

テストのやり方

1. DIする
2. ダミーサーバを使う
3. リクエストとレスポンスを記録/再生する

3

1. DIする

自分のコードは pb.SearchClient インタフェースを満たす cli を渡せればOK。

type myHandler struct{ cli pb.SearchClient }

func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // rからreqを作ったり
    reply, err := h.cli.Search(ctx, req)
    // エラーハンドリングしたり、replyを加工したり
}

cli に手を出せない3rd-partyパッケージなどはメソッドごと差し替える必要がある。

func (c *Client) Run(ctx context.Context, q *Query) *Result {
    // qを色々な処理(バリデーションとか)をしてreqに変換する
    resp, err := c.cli.Search(ctx, req)
    // respとerrを色々な処理をしてResult型に変換する
}

その場合、 色々な処理 はテストでスキップされてしまう...

4

2. ダミーサーバを使う

gRPCのサーバは比較的簡単に作れる。

type fakeServer struct {
    cli pb.ServiceServer // ⬅️のinterfaceに対応するメソッドを実装する

    data map[string]interface{}
}
type mockServer struct {
    cli pb.ServiceServer // ⬅️のinterfaceに対応するメソッドを実装する

    f func(context.Context, *pb.Request) (*pb.Response, error)
}

あとはテスト時に接続先を変えるだけ。ただコード量は多くなりがち...

5

3. リクエストとレスポンスを記録/再生する

cloud.google.com/go/rpcreplay を使う。

r, err := rpcreplay.NewRecorder("service.replay", nil)
if err != nil { ... }
defer func() {
    if err := r.Close(); err != nil { ... }
}()
r, err := rpcreplay.NewReplayer("service.replay")
if err != nil { ... }
defer r.Close()

あとは r.DialOptions() を使ってgRPCクライアントを作るだけ。

6

gRPCクライアントを作る時のシグネチャ

こういう形式になっていますよね?

func NewClient(addr string, opts ...grpc.DialOption) (pb.ServiceClient, error) {
    conn, err := grpc.Dial(addr, opts...)
    if err != nil {
        return nil, err
    }
    return pb.NewServiceClient(conn), nil
}

もしなっていなくても ...grpc.DialOption を足す分には既存コードに影響ないはず!

7

効率よく、安心を得られるテストを!

8

Thank you

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)