ストレージ層のテスト

ではstore構造体にtodoManagerインターフェイスを実装していきます。

```go:store.go
func (s *store) projectTodo(t todo) (string, error) {
	return "", nil
}
```

## テーブル定義

まずは保存用のテーブルを定義します。シンプルに以下の感じでいいでしょう。

```go:sql.go
package schema

var schemas = map[int]string{
	0: `
...
`,
	1: `
BEGIN;

CREATE TABLE todo ( 
        id          UUID   NOT NULL PRIMARY KEY,
        title       STRING NOT NULL,
        description STRING NOT NULL
);

UPDATE schema_version SET md_curr = false, md_update = EXTRACT(EPOCH FROM current_timestamp)::INT WHERE md_curr = true;
INSERT INTO schema_version VALUES (1);

COMMIT;
`,
}
```

スキーマのバージョンを更新するクエリにも注目してみてください。

## store構造体のテスト

以下のようにテストを書いていきます。

```go:store_test.go
package main

import "testing"

func TestStore_ProjectTodo(t *testing.T) {
	t.Run("project a new todo", func(t *testing.T) {})
}
```

## テスト用データベースのセットアップ

テスト時にはローカルにCockroachDBのインスタンスを立ち上げ、ダミーのDBに対してテストを実行していきたいと思います。
なのでセットアップ用に以下のユーティリティ関数、変数を用意します。

```go:store_test.go
var testDB = fmt.Sprintf("test-%s", uuid.New().String())

func openDB(t *testing.T) *sql.DB {
	connStr := "postgres://root@localhost:26257/?sslmode=disable"
	db, err := sql.Open("postgres", connStr)
	require.NoError(t, err)

	_, err = db.Exec(fmt.Sprintf(`CREATE DATABASE IF NOT EXISTS "%s"; USE "%s"`, testDB, testDB))
	require.NoError(t, err)

	return db
}

func closeDB(t *testing.T, db *sql.DB) {
	require.NoError(t, db.Close())
}
```

テスト実行のたびにデータベースを作成することになるためパフォーマンスはよいとは言えませんが、問題となる規模になるまではこれで十分でしょう。
ローカルでのCockroachDB起動のため以下の`docker-compose.yaml`を用意します。

```yaml:docker-compose.yaml
version: '3'
services:
  cockroachdb:
    image: cockroachdb/cockroach
    container_name: cockroachdb
    ports:
      - "26257:26257"
      - "8080:8080"
    command: ["start", "--insecure"]
```

## テストの実行

準備が整ったためテストを書きます。
一気に書いてしまいます。以下のような感じです。

```go:store_test.go
func TestStore_ProjectTodo(t *testing.T) {
	t.Run("project a new todo", func(t *testing.T) {
		db := openDB(t)
		defer closeDB(t, db)

		sut, err := newStore(db, defaultSchemaVersion)
		require.NoError(t, err)

		input := todo{
			title:       "foo title",
			description: "foo description",
		}

		id, err := sut.projectTodo(input)
		require.NoError(t, err)

		var got todo
		require.NoError(t, db.QueryRow(
			`SELECT title, description FROM todo WHERE id = $1`,
			id,
		).Scan(&got.title, &got.description))
		assert.Equal(t, input, got)
	})
}
```

`main.go`でデフォルトのスキーマバージョンを更新しておくのもお忘れなく。

```diff
var (
        gitHash              = "overriden at compile time"
-       defaultSchemaVersion = 0
+       defaultSchemaVersion = 1
)
```

ではdocker-composeでローカル環境を立ち上げて、テストを実行してみます。

```sh
$ docker-compose up
Creating network "qiita-advent-calendar-2019_default" with the default driver
Creating cockroachdb ... done
Attaching to cockroachdb
...

$ make test
go test -v -cover -timeout 30s ./...
=== RUN   TestServer_CreateTodo
=== RUN   TestServer_CreateTodo/project_a_new_todo
=== RUN   TestServer_CreateTodo/error_in_projection
--- PASS: TestServer_CreateTodo (0.00s)
    --- PASS: TestServer_CreateTodo/project_a_new_todo (0.00s)
    --- PASS: TestServer_CreateTodo/error_in_projection (0.00s)
=== RUN   TestStore_ProjectTodo
=== RUN   TestStore_ProjectTodo/project_a_new_todo
--- FAIL: TestStore_ProjectTodo (0.21s)
    --- FAIL: TestStore_ProjectTodo/project_a_new_todo (0.21s)
        require.go:794: 
                Error Trace:    store_test.go:47
                Error:          Received unexpected error:
                                pq: error in argument for $1: could not parse string "" as uuid
                Test:           TestStore_ProjectTodo/project_a_new_todo
FAIL
coverage: 22.9% of statements
FAIL    github.com/KentaKudo/qiita-advent-calendar-2019 0.897s
?       github.com/KentaKudo/qiita-advent-calendar-2019/internal/pb/service     [no test files]
?       github.com/KentaKudo/qiita-advent-calendar-2019/internal/schema [no test files]
FAIL
make: *** [test] Error 1
```

失敗です。いいですね。

## store.projectTodoの実装

ではいよいよ`store.projectTodo`を以下のように実装します。

```go:store.go
func (s *store) projectTodo(t todo) (string, error) {
	id := uuid.New().String()
	if _, err := s.db.Exec(
		`INSERT INTO todo (id, title, description) VALUES ($1, $2, $3)`,
		id, t.title, t.description,
	); err != nil {
		return "", err
	}

	return id, nil
}
```

再度テストを実行。

```sh
$ make test
go test -v -cover -timeout 30s ./...
=== RUN   TestServer_CreateTodo
=== RUN   TestServer_CreateTodo/project_a_new_todo
=== RUN   TestServer_CreateTodo/error_in_projection
--- PASS: TestServer_CreateTodo (0.00s)
    --- PASS: TestServer_CreateTodo/project_a_new_todo (0.00s)
    --- PASS: TestServer_CreateTodo/error_in_projection (0.00s)
=== RUN   TestStore_ProjectTodo
=== RUN   TestStore_ProjectTodo/project_a_new_todo
--- PASS: TestStore_ProjectTodo (0.55s)
    --- PASS: TestStore_ProjectTodo/project_a_new_todo (0.55s)
PASS
coverage: 24.7% of statements
ok      github.com/KentaKudo/qiita-advent-calendar-2019 1.269s  coverage: 24.7% of statements
?       github.com/KentaKudo/qiita-advent-calendar-2019/internal/pb/service     [no test files]
?       github.com/KentaKudo/qiita-advent-calendar-2019/internal/schema [no test files]
```

通りました:)

## main.goの更新

最後に`main.go`内でserver構造体に依存注入するようにしましょう。
利便性のため`newServer()`関数も定義しておきます。

```go:server.go
func newServer(todoMgr todoManager) *server {
	return &server{
		todoMgr: todoMgr,
	}
}
```

```go:main.go
func main() {
	app := cli.App(appName, appDesc)

	...

	app.Action = func() {
		...

		store, err := newStore(db, *schemaVersion)
		if err != nil {
			log.WithError(err).Fatalln("init store")
		}

		lis, err := net.Listen("tcp", net.JoinHostPort("", strconv.Itoa(*grpcPort)))
		if err != nil {
			log.Fatalln("init gRPC server:", err)
		}
		defer lis.Close()

		gSrv := initialiseGRPCServer(newServer(store))

		...
	}

	if err := app.Run(os.Args); err != nil {
		log.WithError(err).Fatal("app run")
	}
}
```

## CircleCI設定の更新

忘れていました。CircleCI上でもCockroachDBを立ち上げてテストできるようにします。

```diff
$ git diff
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 52436a0..fbeabaa 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -4,6 +4,8 @@ jobs:
     working_directory: ~/qiita-advent-calendar-2019
     docker:
     - image: circleci/golang:1
+    - image: cockroachdb/cockroach
+      command: ["start", "--insecure"]
     steps:
     - checkout
     - run: make all
@@ -12,6 +14,8 @@ jobs:
     working_directory: ~/qiita-advent-calendar-2019
     docker:
     - image: circleci/golang:1
+    - image: cockroachdb/cockroach
+      command: ["start", "--insecure"]
     steps:
     - checkout
     - run: make all
```

## ついでに

やっぱりインテグレーションテストもしてみたかったのでBloomRPCでデバッグしてみました。

デプロイをしなおしたらport-forwardでポッドを繋いで、

```sh
$ kubectl -n qiita scale --replicas=0 deployment qiita-advent-calendar-2019

$ kubectl -n qiita scale --replicas=1 deployment qiita-advent-calendar-2019

$ kubectl -n qiita port-forward qiita-advent-calendar-2019-5bc6786c75-jwl5r 8090:8090
Forwarding from 127.0.0.1:8090 -> 8090
Forwarding from [::1]:8090 -> 8090
```

BloomRPCでリクエストの送信。

<img width="1680" alt="Screenshot 2019-11-28 at 18.39.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84014/eb3a7874-6947-66a2-24ca-2813264e7a34.png">

DBの覗き見。

```sh
$ kubectl -n qiita exec -it cockroachdb-0 -- /cockroach/cockroach sql --url postgres://root@localhost:26257 --insecure
...
root@localhost:26257/defaultdb> use qiita_advent_calendar_2019_db;
SET

Time: 998.634µs

root@localhost:26257/qiita_advent_calendar_2019_db> select * from todo;
                   id                  |      title       |                   description                    
+--------------------------------------+------------------+-------------------------------------------------+
  8644af1a-d16a-4b92-911f-19e03905dffe | wash your hands! | wash your hands when you get back from outside!  
(1 row)

Time: 26.989411ms
```

いいですねいいいですね。

---

というわけで`CreateTodo`エンドポイントの実装が完了しました。
盛りだくさんでしたがいかがでしたか？

明日からはトピックがまた変わります。お楽しみに。