👻
GoでGitの差分だけFmt,Test,Lintを走らせて高速化する
Fmt,Test,Lintで使用するパッケージ
種類 | パッケージ |
---|---|
Fmt | gofmt(標準コマンド), goimports |
Test | testing(標準ライブラリ) |
Lint | golangci-lint |
この記事を書いたきっかけ
運用が続いてコードが増えてくと、上記の3つがまあまあ遅くなる。
(例えば単体テストのケースが4000~5000になったりとかサービスによっては珍しくない)
純粋に上記の3つをそれぞれ高速化出来たら良いけど、中々開発工数を確保出来なくて、手が出せずにそのまま放置してしまっていた。
- サクッとやりたい
- 毎回のPRの差分だけでも、高速化出来れば
と思い、実際に現場で使ってみたらかなり開発しやすくなったので記事を書く事にした。
ローカルでやる場合
Makefileに定義して、他の人が簡単にコマンドを叩けるようにする。
(Windowsだとmakeコマンドが使えないので注意)
.PHONY: help
help: ## 使い方(運用が増えるとmakeコマンド多くなるので、確認出来るように付けておくと便利)
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
.PHONY: dtest
dtest: ## 差分testの実行
go test -v `$(call diff) | xargs -I{} echo ./{}`
.PHONY: dfmt
dfmt: ## 差分fmtの実行
gofmt -s -w `$(call diff_files)`
goimports -w -local "github.com/xx/xxx" `$(call diff)`
.PHONY: dlint
dlint: ## 差分lintの実行
go mod download
golangci-lint run -v -c .golangci.yml `$(call diff)`
# 変更差分のあるディレクトリを取得
define diff
git diff --name-only --diff-filter=ACMRT | grep .go$ | xargs -I{} dirname {} | sort | uniq
endef
実行するときはこんな感じ↓
$ make help
$ make dtest
$ make dfmt
$ make dlint
.golanci.yamlはチームによって違うけど、こんな感じ↓
linters-settings:
goimports:
local-prefixes: "github.com/xx/xxx"
gocritic:
enabled-tags:
- performance
gosimple:
go: "1.17"
staticcheck:
go: "1.17"
stylecheck:
go: "1.17"
unused:
go: "1.17"
linters:
enable:
- deadcode
- typecheck
- gofmt
- goimports
- gocritic
- gosec
- govet
- errcheck
- staticcheck
- unused
- gosimple
- structcheck
- varcheck
- ineffassign
run:
timeout: 30m
skip-dirs:
- pkg/xx
skip-files:
- "pkg/proto/.*\\.xx\\.go$"
issues:
exclude-rules: [ ]
GitHubActionsでやる場合
CIツールでPRごとにやってくれれば、レビューする時にlintやtestが通っているか第三者が確認出来るので、レビューしやすくなる。
test.yaml
name: Pull Request Go Tester
on:
pull_request:
branches:
- '**'
paths:
- 'cmd/**'
- 'pkg/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.17
uses: actions/setup-go@v3
with:
go-version: 1.17.8
id: go
- name: Checkout code
uses: actions/checkout@v3
with:
# HEAD^ と HEADの差分をテストするため
fetch-depth: 2
- name: Cache Go Modules
uses: actions/cache@v2
id: cache
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Download Modules
if: steps.cache.outputs.cache-hit != 'true'
run: go mod download
# masterブランチは通常通り全てのテストを走らせる
- name: Test for master branch
if: github.base_ref == 'master'
run: go test -v `go list ./... | grep -v -e pkg/xxx`
# それ以外のブランチは差分だけのテストを走らせる
- name: Test for develop branch
if: github.base_ref != 'master'
run: go test -v `git diff --name-only --diff-filter=ACMRT ${{ github.sha }}^ ${{ github.sha }} | grep .go$ | xargs -I{} dirname {} | sort | uniq | xargs -I{} echo ./{}`
lint.yaml
name: Pull Request Go Linter
on:
pull_request:
branches:
- '**'
paths:
- 'cmd/**'
- 'pkg/**'
jobs:
lint:
runs-on: ubuntu-latest
env:
GOGC: 50
steps:
- name: Set up Go 1.17
uses: actions/setup-go@v3
with:
go-version: 1.17.8
id: go
- name: Checkout code
uses: actions/checkout@v3
with:
# HEAD^ と HEADの差分をテストするため
fetch-depth: 2
# masterブランチは通常通り全てのテストを走らせる
- name: golangci-lint for master branch
if: github.base_ref == 'master'
uses: golangci/golangci-lint-action@v3
with:
version: v1.44.2
args: -v -c .golangci.yml cmd/... pkg/...
# それ以外のブランチは差分だけのテストを走らせる
- name: golangci-lint for develop branch
if: github.base_ref != 'master'
uses: golangci/golangci-lint-action@v3
with:
version: v1.44.2
args: -v -c .golangci.yml `git diff --name-only --diff-filter=ACMRT ${{ github.sha }}^ ${{ github.sha }} | grep .go$ | xargs -I{} dirname {} | sort | uniq`
Discussion