LoginSignup
2
0

More than 3 years have passed since last update.

GitLab CI で Cloud Datastore のテストをする

Posted at

TL;DR

  • golang でのテストのお話
  • Cloud Datastore は gRPC なので http.Client の変更でモック化する httpmock とかが使えない
  • Cloud Datastore Emulator を GitLab CI の services として起動しておけばテストできる

ことの発端

以下のようなプログラムがあったとして、 httpmock を利用して Cloud Datastore へのリクエストをモック化してテストコードを書こうとしたら datastore.Client の作成でエラーが出た.

  • main.go
package main

import (
    "context"
    "log"
    "os"

    // golang.org
    "golang.org/x/oauth2/google"

    // google.golang.org
    "google.golang.org/api/option"

    // cloud.google.com
    "cloud.google.com/go/datastore"
)

func main() {
    ctx := context.Background()

    // 1. Get google application credentials.
    var credOption option.ClientOption
    credentials, err := google.FindDefaultCredentials(ctx)
        if err != nil {
        log.Fatal(err)
    }
    if credentials.JSON != nil {
        credOption = option.WithCredentialsJSON(credentials.JSON)
        } else {
                credOption = option.WithTokenSource(credentials.TokenSource)
        }

    // 2. Put value.
    err = PutValue("xxxxxxxxxxx", "example", credOption)
    if err != nil {
        log.Fatal(err)
    }
}

type Item struct{
    Key *datastore.Key `datastore:"__key__"`
    Value string       `datastore:"value"`
}

func PutValue(nameKey, value string, options ...option.ClientOption) error {
    ctx := context.Background()

    // 1. Create client.
    client, err := datastore.NewClient(ctx, os.Getenv("DATASTORE_PROJECT_ID"), options...)
    if err != nil {
        return err
    }
    defer client.Close()

    // 2. Put item.
    _, err = client.Put(
        ctx,
        datastore.NameKey("Entity", nameKey, nil),
        &Item{
            Value: value,
        },
    )
    if err != nil {
        return err
    }

    return nil
}
  • main_test.go
package main

import (
    "net/http"
    "testing"

    // github.com
    "github.com/jarcoal/httpmock"
    "github.com/stretchr/testify/assert"

    // google.golang.org
    "google.golang.org/api/option"
)

func TestDatastore(t *testing.T) {
    credOption := option.WithCredentialsJSON([]byte("hoge"))

    t.Run("PutValue", func(t *testing.T) {
        mockHTTPClient := &http.Client{}
        httpmock.ActivateNonDefault(mockHTTPClient)
        defer httpmock.Reset()

        err := PutValue(
            "xxxxxxxxxxx",
            "example",
            credOption,
            option.WithHTTPClient(mockHTTPClient),
        )
        assert.Nil(t, err)
    })
}
  • test
$ go test -v ./...
--- FAIL: TestDatastore (0.00s)
    --- FAIL: TestDatastore/PutValue (0.00s)
        main_test.go:29: 
                Error Trace:    main_test.go:29
                Error:          Expected nil, but got: &errors.errorString{s:"dialing: WithHTTPClient is incompatible with gRPC dial options"}
                Test:           TestDatastore/PutValue
FAIL
FAIL    command-line-arguments  0.036s

ソース読んでみたところ

Cloud Datastore をテストするには

1. main_test.go で httpmock を使うのをやめる

package main

import (
    "testing"

    // github.com
    "github.com/stretchr/testify/assert"

    // google.golang.org
    "google.golang.org/api/option"
)

func TestDatastore(t *testing.T) {
    credOption := option.WithCredentialsJSON([]byte("hoge"))

    t.Run("PutValue", func(t *testing.T) {
        err := PutValue(
            "xxxxxxxxxxx",
            "example",
            credOption,
        )
        assert.Nil(t, err)
    })
}

2. エミュレーターを起動する

$ docker run -d -it --rm --name cloud-datastore-emulator --entrypoint gcloud google/cloud-sdk:latest beta emulators datastore start --project=eg-example-01 --host-port=0.0.0.0:8081

3. go test

$ export DATASTORE_PROJECT_ID=eg-example-01
$ export DATASTORE_EMULATOR_HOST=datastore:8081

$ go test -v ./...
=== RUN   TestDatastore
=== RUN   TestDatastore/PutValue
--- PASS: TestDatastore (0.08s)
    --- PASS: TestDatastore/PutValue (0.08s)
PASS
ok      command-line-arguments  0.158s

GitLab CI 化

  • .gitlab-ci.yml
image: docker:stable

stages:
  - testing

variables:
  GIT_SUBMODULE_STRATEGY: recursive
  GO_VERSION: "1.12"

Test golang:
  stage: testing

  image: golang:${GO_VERSION}

  variables:
    DOCKERIZE_VERSION: v0.6.1
    DATASTORE_EMULATOR_HOST: datastore:8081
    DATASTORE_PROJECT_ID: eg-example-01

  services:
    - name: google/cloud-sdk:latest
      alias: datastore
      entrypoint: ["gcloud", "beta", "emulators", "datastore"]
      command: ["start", "--project", "eg-example-01", "--host-port", "0.0.0.0:8081"]

  before_script:
    - curl -sfL https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz | tar -C /usr/local/bin -xzv
    - dockerize -wait tcp://${DATASTORE_EMULATOR_HOST} -timeout 1m

  script:
    - go test -v ./...
  1. services に Cloud Datastore Emulator のコンテナを指定
    • ローカルでテストするときは --host-port=localhost:8081 とか指定してもいいけど、それだとコンテナ外部から通信できないので --host-port=0.0.0.0:8081 にする
  2. 環境変数に DATASTORE_PROJECT_ID, DATASTORE_PROJECT_ID を追加する
    • DATASTORE_PROJECT_IDservices で指定したエイリアスをホスト名にする
  3. エミュレーターがちゃんと起動してからテストしたいので dockerize で待つ
  4. あとはテストするだけ
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0