Firestore Emulator を使って GitHub Actions でテストを動かしてみる

この記事は [フラー Advent Calendar 2020](https://adventar.org/calendars/5034) の23日目の記事です。
22日目は [@ujikawa1026](https://adventar.org/users/33961) さんで
「[小さな開発組織のマネージャーになってから1on1で考えていることを少しだけ書く](https://namaninotiteti1026.hatenadiary.jp/entry/2020/12/22/000541)」でした。

#はじめに
はじめまして。
今年の4月から、フラー株式会社のエンジニアとしてアルバイトをさせていただいております。
普段は、サーバーサイドエンジニアとしてGoを用いたAPI開発を行っています。

今回は、Firestore EmulatorとGitHub Actionsによる自動テストを作成したので、記事にまとめてみました。

#きっかけ
3ヶ月ほど前にFirestoreとGoで書かれたAPIを作成する機会があり、
ついでにテストも書くことになったので、Github Actionsを使ってテストを自動化することにしました。


#本題
今回作成したAPIはDB（Firestore）が絡んだものでした。
当たり前な話かもしれませんが、
テストを行う際に本番環境で使われるDBに接続するのはよく無いです。テストによってDBの中身に変更が加わったり、壊れるのを避ける必要があります。



##テスト用のDBを用意する
Firestore Emulatorを使用して、テストに用いるDBを作成します。
Emulatorを使うことで、わざわざテスト用にDBを作成する必要がなく、
テストの度に同じ条件のDBをEmulatorに用意することができます。
<br>
Emulatorは`gcloud`コマンドを使って起動します。
コマンド名は、
__gcloud beta emulators firestore__
https://cloud.google.com/sdk/gcloud/reference/beta/emulators/firestore 

使い方は次の通り

```
$ gcloud beta emulators firestore start [--host-port=HOST_PORT]
```
オプションでホスト名とポート番号を指定して、`gcloud`コマンドを実行することでFirestore Emulatorが起動します。
<br>

＊注意点として、コマンド名にも書いてありますがbeta版です...
>This command is currently in BETA and may change without notice.

使用する場合は、急な仕様変更に注意する必要があります。

##Go で Firestore Emulator を使用する
GoでFirestore Emulatorに接続します。
Firestore PackageはEmulatorをサポートしており、GoでEmulatorを使うことができます。

https://godoc.org/cloud.google.com/go/firestore#hdr-Google_Cloud_Firestore_Emulator
>This package supports the Cloud Firestore emulator, which is useful for testing and development.

<br>
Goでは以下のようにしてFirestoreのクライアントを作成します。

```go
ctx := context.Background()
client, err := firestore.NewClient(ctx, "projectID")
if err != nil {
	// TODO: Handle error.
}
```

このとき、ホスト名とポート番号を`FIRESTORE_EMULATOR_HOST`に定義しておくことで、優先的にEmulatorを接続するようになります。

https://github.com/googleapis/google-cloud-go/blob/e0be520e1a5e36548f82823602aacfd3277cb7e9/firestore/client.go#L64-L77
>If this environment variable is defined, configure the client to talk to the emulator.


特にGoの方で明示的にEmulatorに接続する処理を書く必要はないです。

##GitHub Actions で Firestore Emulator を起動する
###GitHub Actions
`.github/workflows/`ディレクトリにワークフローファイルを置いておくことで、指定したイベントがトリガーとなってデプロイやテスト、その他様々なアクションを自動で行うことができます。
詳しくは[ドキュメント](https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/introduction-to-github-actions)参照

今回は自動テストを追加するので、テストを行うワークフローを書いた`test.yaml`ファイルを`.github/workflows/`ディレクトリに置いておきます。


###SetUp
テストで使用するツールのセットアップを行います。

Goのセットアップ

```yaml
- name: Set up Go
  uses: actions/setup-go@v1
  with:
    go-version: "1.13"
```

参考したもの
https://github.com/actions/setup-go
https://github.com/mvdan/github-actions-golang
他にも設定があった気がします。


gcloudとFirestoreのセットアップは以下の通り

```yaml
- name: setup gcloud
  uses: google-github-actions/setup-gcloud@master
  with:
    version: "latest"

- name: setup firestore emulator
  run: |
    gcloud components install beta --quiet
    gcloud components install cloud-firestore-emulator --quiet
```

gcloudのセットアップは[google-github-actions](https://github.com/google-github-actions/setup-gcloud/tree/master/setup-gcloud#setup-gcloud)のREADMEを参考にしました。

Firestore Emulatorのセットアップは[公式](https://cloud.google.com/sdk/docs/components#installing_components)に書いてある通りです。
必要なコンポーネントのインストール↓

```
$ gcloud components install [COMPONENT-ID]
```

`COMPONENT-ID`の検索↓

```
$ gcloud components list
```

オプションで`--quiet`を指定しないと、インタラクティブモードが有効になって、セットアップが進まないです（たしか..）

### Testのフローを追加
最後に、テストを実行するフローを追加します。
今回、テストの作成には`testing`パッケージを使用しました。
https://golang.org/pkg/testing/

テストを実行するには、適切なディレクトリに移動して次のコマンドを実行するだけです。

```
$ go test ./... 
```

また、テストを実行する前にEmulatorを起動しておく必要があります。
起動方法は上で書いた通りで、追加するフローは以下の通りです。

```yaml
- name: Test
  run: |
    gcloud beta emulators firestore start --host-port=localhost:8080 &
    curl http://localhost:8080 --silent --retry 30 --retry-delay 1 --retry-connrefused
    go test ./... -v
  env:
    FIRESTORE_EMULATOR_HOST: localhost:8080
```

`curl`コマンドの部分では、Emulatorが立ち上がる前にテストが実行されるのを防ぐために、ディレイをかけています。
また、Emulatorに接続させるために、環境変数`FIRESTORE_EMULATOR_HOST`を追加しています。
<br>
以上で、トリガーとして指定したイベントが発生した特に、Emulatorの起動からテストの実行まで自動で行ってくれるようになると思います。
ちなみに私は、プッシュするたびにテストが走るように設定しておきました。



#最後に
初めて書きました。
次回（？）は、もっと綺麗にわかりやすく書けたらなと思います。

あと、ここ違う！とか、こうした方が良さそう
っていうところがあったら、指摘していただけると助かります:bow: