Airで始めるGo開発

![](https://storage.googleapis.com/zenn-user-upload/4b135b4d23ea-20231206.png)

[RetailAI Adventurers Advent Calendar 2023](https://qiita.com/advent-calendar/2023/rai-adventurers)の18日目の記事になります。

今回、僕の担当しているプロジェクトで、.air.tomlという見慣れないファイルを見つけたので、それについて調べてみました。

.air.tomlとは、Go言語のホットリロードツールであるAirの設定ファイルです。

https://github.com/cosmtrek/air

ホットリロードとは、ソースコードを更新するたびにソースをビルドし直して実行するという手間を省くため、ソースコードを更新し、保存したら自動でビルドと実行を行うということです。

プロジェクトのルートディレクトリでairを実行し、放っておくだけでコードに集中できるというわけです。

https://github.com/urakawa-jinsei/air-tutorial

## Installation

- Airをインストールします
```shell
$ go install github.com/cosmtrek/air@latest
```

- パスに追加します
```shell
$ export PATH=$PATH:$(go env GOPATH)/bin
```

- .air.tomlファイルをデフォルト設定で作成します
```shell
$ air init
```

```toml:.air.toml
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
  args_bin = []
  bin = "./tmp/main"
  cmd = "go build -o ./tmp/main ."
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  include_file = []
  kill_delay = "0s"
  log = "build-errors.log"
  poll = false
  poll_interval = 0
  post_cmd = []
  pre_cmd = []
  rerun = false
  rerun_delay = 500
  send_interrupt = false
  stop_on_error = false

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  main_only = false
  time = false

[misc]
  clean_on_exit = false

[screen]
  clear_on_rebuild = false
  keep_scroll = true
```

<details><summary> .air.tomlの詳細 </summary>
.air.tomlファイルの各設定値の詳細は以下の通りです：

- `root`：プロジェクトのルートディレクトリを指定します。
- `testdata_dir`：テストデータが格納されているディレクトリを指定します。
- `tmp_dir`：一時ファイルが格納されるディレクトリを指定します。

- `[build]`：ビルドに関する設定を行います。
  - `args_bin`：ビルドコマンドに渡す引数を指定します。
  - `bin`：ビルド後の実行ファイルの出力先を指定します。
  - `cmd`：ビルドコマンドを指定します。
  - `delay`：ファイル変更検出後、ビルドを開始するまでの遅延時間（ミリ秒）を指定します。
  - `exclude_dir`：ビルドから除外するディレクトリを指定します。
  - `exclude_file`：ビルドから除外するファイルを指定します。
  - `exclude_regex`：ビルドから除外する正規表現を指定します。
  - `include_dir`：ビルドに含めるディレクトリを指定します。
  - `include_ext`：ビルドに含める拡張子を指定します。
  - `include_file`：ビルドに含めるファイルを指定します。
  - `kill_delay`：プロセス終了後、新しいプロセスを開始するまでの遅延時間を指定します。
  - `log`：ビルドエラーのログを出力するファイルを指定します。

- `[color]`：ログの色設定を行います。
  - `app`：アプリケーションのログ色を指定します。
  - `build`：ビルドのログ色を指定します。
  - `main`：メインのログ色を指定します。
  - `runner`：ランナーのログ色を指定します。
  - `watcher`：ウォッチャーのログ色を指定します。

- `[log]`：ログに関する設定を行います。
  - `main_only`：メインのログのみを出力するかどうかを指定します。
  - `time`：ログに時間を表示するかどうかを指定します。

- `[misc]`：その他の設定を行います。
  - `clean_on_exit`：終了時にクリーンアップを行うかどうかを指定します。

- `[screen]`：スクリーンに関する設定を行います。
  - `clear_on_rebuild`：再ビルド時にスクリーンをクリアするかどうかを指定します。
  - `keep_scroll`：スクロールを保持するかどうかを指定します。

</details>

## Preparation
```go:main.go
package main

import "fmt"

func main() {
	fmt.Println(Hello())
}

func Hello() string {
	return "Hello"
}

```

```Dockerfile:Dockerfile
FROM golang:1.21-alpine

WORKDIR /app

RUN go install github.com/cosmtrek/air@latest

COPY go.mod go.sum ./
RUN go mod download

CMD ["air", "-c", ".air.toml"]
```

```yaml:docker-compose.yaml
version: "3.8"
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 8080:8080
    volumes:
      - ./:/app
```

## Execution
それでは、実際に変化を見ていきましょう

- コンテナを起動します
```shell
$ docker-compose up
```

- ファイルを編集します
```diff_go:main.go
package main

import "fmt"

func main() {
	fmt.Println(Hello())
}

func Hello() string {
-	return "Hello"
+	return "Hello, World!"
}

```

- 自動でビルドされ、実行されます
![Untitled (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3337464/224deb33-6b8b-7c1c-782f-6428904568ba.png)

## Testing
ソースコードを変更すると、自動でテストが走るようにしたいと思います。

- テストファイルを作成します。
```go:main_test.go
package main

import (
	"testing"
)

func TestHello(t *testing.T) {
	result := Hello()
	expected := "Hello, World!"

	if result != expected {
		t.Errorf("Expected %s, but got %s", expected, result)
	}
}

```

- .air.tomlにテストのコマンドを追加します。

```diff_toml:.air.toml
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
  args_bin = []
  bin = "./tmp/main"
- cmd = "go build -o ./tmp/main ."
+ cmd = "go test ./... && go build -o ./tmp/main ."
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  include_file = []
  kill_delay = "0s"
  log = "build-errors.log"
  poll = false
  poll_interval = 0
  post_cmd = []
  pre_cmd = []
  rerun = false
  rerun_delay = 500
  send_interrupt = false
  stop_on_error = false
  [以下略]
```

- 実行します。

![Untitled (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3337464/b97fd3fe-cc91-dae9-5e98-ef18e8ebf72e.png)

自動でテストが走りました。

## Finally

これのおかげで、開発がだいぶ楽になった気がします。
これからの開発でも積極的に使用していきたいと思いました。
簡単ですが、以上になります。ご覧いただきありがとうございました。