Go/EchoでProtocolBuffersを使ったE2Eテストを書く

この記事は[Go 4 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/go4) 6日目の記事です。

業務でGo * ProtocolBuffersなプロダクトのE2Eテストを書くことがあっため、簡単に書かせていただきます。

## はじめに

テストで利用した構成は以下の通りです。

```
・プログラミング言語：Golang 1.13.5
・Webフレームワーク：echo　
・DB：MySQL：
```

サンプルコード全体は[こちら](https://github.com/DAdDY0055/go_protocol_buffers_e2e_test)にご用意しましたのでをこちらをご参照ください。

今回、Webフレームワークとして、`echo`を利用していますが、
おそらく他のWebフレームワークを利用した場合でも同様にできるかと思います。

## テスト対象コード
今回はごく簡単な構成としまして、`echo`でサーバーを起動して、アクセスするとレスポンスを返すようにしました。

```go:main.go
package main

import (
	"net/http"

	pb "github.com/DAdDY0055/go_protocol_buffers_e2e_test/proto"
	"github.com/labstack/echo/v4"

	"google.golang.org/protobuf/proto"
)

func main() {
	e := echo.New()
	e.GET("/hello", func(c echo.Context) error {
		hello := &pb.Hello{Hello: "Hello, World!"}
		data, _ := proto.Marshal(hello)
		return c.Blob(http.StatusOK, "application/protobuf", data)
	})
	e.Logger.Fatal(e.Start(":8080"))
}
```

ProtocolBuffersを使っておりますので、変換に使うProtoのファイルは以下です。

```go:test.proto
syntax = "proto3";

package protofiles;

message Hello {
  string hello = 1;
}
```

上記ファイルを作成した後、`protoc`コマンドを使ってgoのシリアライズ・デシリアライズ用ファイルを生成します。

```
% protoc --go_out=./proto/ ./test.proto
```

※protocコマンドをインストールしていない場合は以下で可能です。

```
% brew install protobuf
```

## テストコード
テストの実行には、
- テスト用サーバーの起動：`net/http/http/test`
- 値の検証：`github.com/stretchr/testify/assert`

を利用しました。

```
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	pb "github.com/DAdDY0055/go_protocol_buffers_e2e_test/proto"
	"github.com/stretchr/testify/assert"
	"google.golang.org/protobuf/proto"
)

func TestHelloHandler(t *testing.T) {
	// 想定する値を定義
	want := &pb.Hello{
		Hello: "Hello, World!",
	}

	router := NewRouter()

	req := httptest.NewRequest("GET", "/hello", nil)
	rec := httptest.NewRecorder()

	router.ServeHTTP(rec, req)

	assert.Equal(t, http.StatusOK, rec.Code)

	res := &pb.Hello{}
	proto.Unmarshal(rec.Body.Bytes(), res)
	// assert.Equal(t, want, res) そのまま比較すると一致しない
	assert.Equal(t, want.Hello, res.Hello)

	fmt.Println("res", res) // 味気ないので出力
}
```

ここで鍵となるのは、ProtocolBuffersはバイナリで通信をしているため、**assertでチェックをする前に値を変換する**必要があるという点です。

`proto.Unmarshal`でデシリアライズ変換した値を使うことで、`assert`で検証できます。

## 実際にテスト実行

テストファイルがあるディレクトリにて、以下で実行します。

> go test -v

**実行結果**

```
-> % go test -v
=== RUN   TestHelloHandler
res hello:"Hello, World!"
--- PASS: TestHelloHandler (0.00s)
PASS
ok  	github.com/DAdDY0055/go_protocol_buffers_e2e_test	0.019s
```

この通り、値が合っていた場合OKとなります。

ちなみにですが、`assert.Equal(t, want, res)`と直接比較したい気がしますが、その場合は`Not equal`となりFAILとなります。

```
-> % go test -v
=== RUN   TestHelloHandler
res hello:"Hello, World!"
--- FAIL: TestHelloHandler (0.00s)
    main_test.go:30:
        	Error Trace:	main_test.go:30
        	Error:      	Not equal:
        	            	expected: &protofiles.Hello{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(0xc0000da640)}, sizeCache:0, unknownFields:[]uint8(nil), Hello:"Hello, World!"}
        	            	actual  : &protofiles.Hello{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(0xc0000da640)}, sizeCache:0, unknownFields:[]uint8(nil), Hello:"Hello, World!"}

        	            	Diff:
        	Test:       	TestHelloHandler
FAIL
exit status 1
FAIL	github.com/DAdDY0055/go_protocol_buffers_e2e_test	0.019s
```

`expected`と`actual`を比較すると同じに見えるのですが。。
なぜこれがequalにならないのかわからよくわかっていないため、もしご存知な方いらしたら教えていただけると幸いです。

## 最後に
ProtocolBuffersのE2Eテストは変換する手間が発生しますが、
わかってしまえば特に苦戦することもなかったです。
(`assert.Equal(t, want, res)`で値が一致しないのは)

これは`net/http/httptest`が使いやすく、簡単にテストサーバーを立てることができることが大きいかと感じました。

`"net/http/httptest"`と`"testing"`を使って色々テストを書いたので本当はそのノウハウも書きたかったのですが、今回時間がなく書けなかったのでまた次回書きたいと思います。

#### 今回参考にさせていただいた記事
- [Go言語のHTTPサーバのテスト事始め](https://qiita.com/theoden9014/items/ac8763381758148e8ce5)
- [Protocol Buffers の Marshal と Unmarshal の小さなサンプル
](https://qiita.com/mochizukikotaro/items/3407fcd72b98d120256e)