go test時の環境変数の設定で使える静的解析ツールtenvを作りました
はじめまして、しぶちゃりです。
今回はGoの静的解析ツール tenv
の紹介をさせていただきます。
Go1.17から使えるtesting.Setenv
Go1.17からtesting.Setenvというメソッドが追加されました。
Go1.17以前ではtest内で環境変数を使用する際にos.Setenvを用いていました
しかし、os.Setenvを用いた場合、テストごとの環境変数が引き継がれてしまいます。
例えば、このようなテストコードを実行してみます
package main_test
import (
"fmt"
"os"
"testing"
)
func TestMain(t *testing.T) {
fmt.Println(os.Getenv("GO"))
os.Setenv("GO", "HACKING GOPHER")
}
func TestMain2(t *testing.T) {
fmt.Println(os.Getenv("GO"))
}
go test ./... -v
=== RUN TestMain
--- PASS: TestMain (0.00s)
=== RUN TestMain2
HACKING GOPHER
--- PASS: TestMain2 (0.00s)
PASS
ok a 0.154s
このようにTestMainで設定した環境変数がTestMain2でも引き継がれてしまっています。
上記のケースを回避するためには、事前にos.Getenvで取得した値をt.Cleanupなどで戻してあげる必要があります。
Go1.17から実装されたtesting.Setenvを用いた場合、テストが終了すると設定した環境変数が破棄されるようになります。
以下がサンプルコードです。vi
package main_test
import (
"fmt"
"os"
"testing"
)
func TestMain(t *testing.T) {
fmt.Println(os.Getenv("GO"))
t.Setenv("GO", "HACKING GOPHER")
}
func TestMain2(t *testing.T) {
fmt.Println(os.Getenv("GO"))
}
go test ./... -v
=== RUN TestMain
--- PASS: TestMain (0.00s)
=== RUN TestMain2
--- PASS: TestMain2 (0.00s)
PASS
ok
TestMain2ではTestMainで設定した値が引き継がれていないことがわかります。
tenv
僕が開発した静的解析ツールである tenv
を利用することで、既存のテストコードでtesting.Setenvを用いていない箇所を検出してくれます。
デフォルトでは、 *testing.T
*testing.B
testing.TB
を実装している箇所を全て検出します。
package main
import (
"fmt"
"os"
"testing"
)
func TestMain(t *testing.T) {
fmt.Println(os.Getenv("GO"))
os.Setenv("GO", "HACKING GOPHER")
}
func TestMain2(t *testing.T) {
fmt.Println(os.Getenv("GO"))
}
func helper() {
os.Setenv("GO", "HACKING GOPHER")
}
こちらのコードにtenvのチェックを入れるとこのようになります。
go vet -vettool=(which tenv) ./...
# a
./main_test.go:11:2: os.Setenv() can be replaced by `t.Setenv()` in TestMain
加えて -tenv.all
というオプションをつけることで、 *_test.go
のファイル内で os.Setenv
を用いている箇所を全て検出します。
package main
import (
"fmt"
"os"
"testing"
)
func TestMain(t *testing.T) {
fmt.Println(os.Getenv("GO"))
os.Setenv("GO", "HACKING GOPHER")
}
func TestMain2(t *testing.T) {
fmt.Println(os.Getenv("GO"))
}
func helper() {
os.Setenv("GO", "HACKING GOPHER")
}
go vet -vettool=(which tenv) -tenv.all ./...
# a
./main_test.go:11:2: os.Setenv() can be replaced by `t.Setenv()` in TestMain
./main_test.go:19:2: os.Setenv() can be replaced by `testing.Setenv()` in helper
このツールはCIにも組み込むことが可能です。
CircleCI
- run:
name: install tenv
command: go get github.com/sivchari/tenv
- run:
name: run tenv
command: go vet -vettool=(which tenv) -tenv.all ./...
GitHub Actions
- name: install tenv
run: go get github.com/sivchari/tenv
- name: run tenv
run: go vet -vettool=(which tenv) -tenv.all ./...
golangci-lint経由でも利用可能
上記のlinterはgolangci-lintにも組み込まれているため、golangci-lint経由で利用することも可能です。
golangci-lintで実際にMergeされたPRが以下になります。
自分の作成したlinterを今後PRで出したい!と思う方の例にもなると思います。
まとめ
今回ご紹介したlinterはGitHubで公開しています。
もしいいと思ったらスターをいただけるとモチベーションにつながるのでとても嬉しいです。
最後までお読みいただきありがとうございました。
Discussion