GitHub Actionsで変更のあるパッケージだけGoテストを走らせる
TL;DR
- 一部パッケージの自動テストにおいてdockertestを導入しているため、コード変更があるたびに全てのパッケージのGoテストを行うとWorkflowの実行に時間がかかる。
-
git diff
を活用して、マージ先のブランチから変更のあるパッケージを取得し、そのパッケージのみのテストを実行することで自動テストのWorkflowにかかかる時間を短縮した。 -
詰まりどころ:
git diff
で取得しできる変更のあるパッケージ一覧が複数行の文字列になるため、適切にフォーマットしてWorkflowの環境変数に格納する必要があった。
背景
私の参加しているプロジェクトでは、dockertestを用いることで、RDBMSやRedisといったインフラ環境に関わるテストをモックコードを使わずに行なっていました。
dockertestを用いると、テスト環境に必要なコンテナの立ち上げから削除までをコードベースで管理できるようになるため、本番に近い環境でのテストを気軽に行えるようになります。
dockertestの詳細な使い方は他の記事を参照してください。
一方で、dockertestを使用しているパッケージのテストでは、コンテナの起動を伴うためテストの実行に時間がかかるという問題も抱えていました。
func TestMain(m *testing.M) {
// docker containerを立ち上げるのに時間がかかる.
pool, err := dockertest.NewPool("")
...
}
もちろん、重要なパッケージにおいては時間をかけてでも正確にテストを行うべきですが、些細な変更やdockertestを使用していないパッケージの変更の際には自動テストの実行を高速化したいというモチベーションがありました。
やったこと
git diff
コマンドを使用してマージするブランチとHEADブランチ間の差分を取得し、変更のあったパッケージに対してのみ自動テストを実行するようにしました。
最終的な設定ファイルは以下のようになります。
name: Test
on:
pull_request:
paths:
- 'cmd/**.go'
- 'internal/**.go'
- 'go.mod'
- 'go.sum'
env:
GO_VERSION: 1.22
jobs:
test:
name: test
runs-on: ubuntu-latest
services:
dind:
image: docker:23.0-rc-dind-rootless
ports:
- 2375:2375
defaults:
run:
working-directory: .
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
with:
go-version: ">=${{ env.GO_VERSION }}"
- name: Get changed packages
id: get_changed_packages
run: |
git fetch origin ${{ github.base_ref }}
{
echo "changed_packages<<\n"
git diff origin/${{ github.base_ref }} HEAD --name-only | grep -E 'cmd|internal' | sed -E 's|/[^/]+$||' | sort -u
echo "\n"
} >> $GITHUB_OUTPUT
- name: Test with Docker
run: |
readarray -t packages <<EOF
${{ steps.get_changed_packages.outputs.changed_packages }}
EOF
if [ -z ${{ steps.get_changed_packages.outputs.changed_packages }} ]; then
echo "No changes in cmd or internal"
else
for pkg in "${packages[@]}"; do
go test -v ./$pkg/...
done
fi
1番のポイントは差分を取得し、変更のあるパッケージに対してだけテストを実行する以下の部分です。
- name: Get changed packages
id: get_changed_packages
run: |
git fetch origin ${{ github.base_ref }}
{
echo "changed_packages<<\n"
git diff origin/${{ github.base_ref }} HEAD --name-only | grep -E 'cmd|internal' | sed -E 's|/[^/]+$||' | sort -u
echo "\n"
} >> $GITHUB_OUTPUT
- name: Test with Docker
run: |
readarray -t packages <<EOF
${{ steps.get_changed_packages.outputs.changed_packages }}
EOF
if [ -z ${{ steps.get_changed_packages.outputs.changed_packages }} ]; then
echo "No changes in cmd or internal"
else
for pkg in "${packages[@]}"; do
go test -v ./$pkg/...
done
fi
実装の際のつまりどころとしては、git diff ...
以下のコマンドで差分のファイル一覧を取得し、grep
などで成形して得られるパッケージ一覧が改行文字\n
で区切られた複数行の文字列になるため、Workflowの環境変数に設定する際に注意が必要であるという点です。
ドキュメントによると、複数行の値をWorkflowの環境変数に設定する際には
{name}<<{delimiter}
{value}
{delimiter}
という形式で値を設定する必要があるようでした。
今回の改行文字で区切られた複数行の文字列のケースでは、
{
echo "changed_packages<<\n"
git diff origin/${{ github.base_ref }} HEAD --name-only | grep -E 'cmd|internal' | sed -E 's|/[^/]+$||' | sort -u
echo "\n"
} >> $GITHUB_OUTPUT
のような形でWorkflowの環境変数に設定することでうまくいきました。
まとめ
複数行の出力をWorkflowの環境変数に設定する際には適切にフォーマットする必要あり!
参考
- dockertest
- dockertest のススメ
-
sqlcとdockertestでデータベースを使ったテストを書こう
- 参加しているプロジェクトでもsqlcとdockertestを組み合わせたテスト行っていました。
- Github Actions
- github actionsを使って特定のディレクトリに差分があるときにテストを行う
-
GitHub Actions: Deprecating save-state and set-output commands
-
set-output
は廃止されて環境変数に移行する。
-
Discussion