skip to content
私的歌詞倉庫

Goのテストを書いてGitHub Actions上でCIを動かす

/ 7 min read

Updated:
Table of Contents

はじめに

最近個人的にGo言語を勉強しています。Go自体は少し触ったことがあったのですが、テストを書いたことがなかったので、今回はテストを書いてGitHub Actions上でCIを動かすというのをやってみました。今回使用したコードはGitHub上に公開しています。

GitHub - Tatsumi0000/gorgon-eye

使っているGoのバージョンは1.23.4です。

テストを書く前に

Goには標準でtestingライブラリが備わっています。ですがアサーション機能はありません。個人的にはこれが結構驚きました。なぜ存在しないかについてはこちらの記事が非常に参考になりました。

Go の Test に対する考え方 - Qiita

たしかに適当にアサーションを使うより、期待する値をifなどで比較して一致しなかったら、エラーメッセージを書いて、なぜそのテストが失敗するかを書いたほうがコード自体の理解度も深まっていいなと納得しました。

テストを書く

ディレクトリ構成はこのようになっています。Goではテストディレクトリを作るのではなく、同じディレクトリに含めることが多いようです(もちろん分ける流派もあるようです)。テストと実装の距離が近いので、すぐ対象のファイルを見つけやすくてお気に入りです。

.
├── config
│   ├── config.go
│   ├── config_test.go
│   └── testdata
│   └── test_config.yaml
├── go.mod
└── go.sum

以下のコードに対してテストを書きます。yamlファイルを読み込んで、ファイルがあったらパースしてConfig構造体のフィールドにセットするという処理です。読み込みに失敗したら適宜errorを返すようにしています。

package config
import (
"os"
"github.com/goccy/go-yaml"
)
type Config struct {
YamlFileName string
Urls []string `yaml:"urls"`
}
func New(yamlFileName string) *Config {
return &Config{
YamlFileName: yamlFileName,
}
}
func (c *Config) Read() error {
buf, err := os.ReadFile(c.YamlFileName)
if err != nil {
return err
}
err = yaml.Unmarshal(buf, c)
if err != nil {
return err
}
return nil
}

実際に書いたテストコードは以下の通りです。存在しないhogehoge.yaml を開こうとしているので、errorを返すことをチェックするテストです。

package config
import (
"testing"
)
func Test_Read(t *testing.T) {
t.Run("Can not open yaml file.", func(t *testing.T) {
fileName := "hogehoge.yaml"
c := New(fileName)
got := c.Read()
if got == nil {
t.Errorf("c.Read() == %s, want error", got)
} else {
t.Logf("got error message: %s", got)
}
})
}

if got == nil でerrorが返ってこない場合は意図していない挙動なのでErrorfを使ってなぜ失敗するかを出力するようにしています。一応elseでエラー内容を出力するようにしています。これはデバッグしやすいかなと思ったため個人的に追加しました。

テストの実行はgo test コマンドを使います。-v をつけてどのテストを実行したか分かりやすくしています。./… はコマンドを実行したディレクトリから再帰的にテストを実行するという意味です。

go test -v ./...
=== RUN Test_Read
=== RUN Test_Read/Can_not_open_yaml_file.
config_test.go:15: got error message: open hogehoge.yaml: no such file or directory
--- PASS: Test_Read (0.00s)
--- PASS: Test_Read/Can_not_open_yaml_file. (0.00s)
PASS
ok github.com/Tatsumi0000/gorgon-eye/config 0.001s

GitHub Actions

GoのテストをGitHub Actions上で実行します。これは特別なことをしてなくて、GitHubのドキュメントをほぼそのままコピペしただけです。

Go でのビルドとテスト - GitHub Docs

name: Go
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.23.4"
cache-dependency-path: go.sum
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...

自分は知らなかったのですが、setup-gocache-dependency-pathに依存関係のファイルパスを指定すればchacheアクションを使わなくても、勝手にキャッシュを効かせてくれました。ただし複雑なキャッシュをしたい場合はcacheアクションを使う必要があります。

Go でのビルドとテスト - GitHub Docs

キャッシュを効かせた結果、約2分ほど時間を短縮できました。

キャッシュなし

キャッシュなし

キャッシュあり

キャッシュあり

終わりに

Goのテストを初めて書きました。最初はアサーションを使わないでどうやってテストをするのかあまり分からなかったのですが、コードを書いていくことで理解が深まりました。

冒頭にも書きましたが、確かにアサーションがない方がなぜこのコードが失敗するか考えながらメッセージを書くので、自然と丁寧なテストになるなと思いました。これは業務のテストコードにも活かせそうです。

Goは同じパッケージ内であればファイルが違ってもprivateな関数のテストができるのがいいなと思いました。Vitest(In-Source-Testing)やRustだと同じファイル内にテストを一緒に書く(場合もある)ので、コードの見やすさ的にもGoのこの仕様は好きです。

Vitest

参考文献