0
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

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

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

テーブル定義

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

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構造体のテスト

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

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に対してテストを実行していきたいと思います。
なのでセットアップ用に以下のユーティリティ関数、変数を用意します。

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を用意します。

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

テストの実行

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

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でデフォルトのスキーマバージョンを更新しておくのもお忘れなく。

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

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

$ 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を以下のように実装します。

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
}

再度テストを実行。

$ 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()関数も定義しておきます。

server.go
func newServer(todoMgr todoManager) *server {
    return &server{
        todoMgr: todoMgr,
    }
}
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を立ち上げてテストできるようにします。

$ 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でポッドを繋いで、

$ 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でリクエストの送信。

Screenshot 2019-11-28 at 18.39.57.png

DBの覗き見。

$ 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エンドポイントの実装が完了しました。
盛りだくさんでしたがいかがでしたか?

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

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
Sign upLogin
0
Help us understand the problem. What are the problem?