Go言語による「テストの書き方・実行方法」を確認します。Golandによるテストファイル生成、並列実行方法など取り上げます。
Golandでテストファイル生成
テスト対象の処理
aaa/aaa.go
というファイルに以下処理を記述しています。
package aaa
func Add(i, j int) int {
return i + j
}
func Sub(i, j int) int {
return i - j
}
この処理のテストを作成します。
Golandでテストファイル生成
Generate
をクリックします。
Tests for file
をクリックします。
aaa_test.go
というファイルが生成されました。aaa.go
に記載した関数をテストするための処理が記述されています。
TODO: Add test cases.
の箇所を自身で実装する必要があります。
goのテストは生成されたファイルの通り、以下特徴があります。
1. ファイル名の末尾を _test.go
にする。
2. テストの関数名の頭に Test
をつける。
3. testingパッケージを利用する。
生成ファイルにテストケースを記述
TODO: Add test cases.
の箇所を以下のように実装しました。
package aaa
import "testing"
func TestAdd(t *testing.T) {
type args struct {
i int
j int
}
tests := []struct {
name string
args args
want int
}{
{
name: "Both positive values",
args: args{5, 3},
want: 8,
},
{
name: "Contains negative values",
args: args{5, -3},
want: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.args.i, tt.args.j); got != tt.want {
t.Errorf("Add() = %v, want %v", got, tt.want)
}
})
}
}
func TestSub(t *testing.T) {
type args struct {
i int
j int
}
tests := []struct {
name string
args args
want int
}{
{
name: "Both positive values",
args: args{5, 3},
want: 2,
},
{
name: "Contains negative values",
args: args{5, -3},
want: 8,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Sub(tt.args.i, tt.args.j); got != tt.want {
t.Errorf("Sub() = %v, want %v", got, tt.want)
}
})
}
}
テスト実行
テストを実行してみます。
./
├── aaa/
│ ├── aaa.go
│ └── aaa_test.go
└── go.mod
$ go test ./aaa
ok test-sample/aaa (cached)
詳細結果表示
-vオプション
をつけると詳細結果が表示されます。
$ go test -v ./aaa
=== RUN TestAdd
=== RUN TestAdd/Both_positive_values
=== RUN TestAdd/Contains_negative_values
--- PASS: TestAdd (0.00s)
--- PASS: TestAdd/Both_positive_values (0.00s)
--- PASS: TestAdd/Contains_negative_values (0.00s)
=== RUN TestSub
=== RUN TestSub/Both_positive_values
=== RUN TestSub/Contains_negative_values
--- PASS: TestSub (0.00s)
--- PASS: TestSub/Both_positive_values (0.00s)
--- PASS: TestSub/Contains_negative_values (0.00s)
PASS
ok test-sample/aaa (cached)
全テスト実行
今回テストファイルは1つだけですが、カレントディレクトリ配下の全テストを実行したい場合、go test ./...
と指定します。
$ go test ./...
ok test-sample/aaa (cached)
カバレッジ表示
-coverオプション
をつけるとカバレッジも計測されます。
$ go test -cover ./aaa
ok test-sample/aaa 0.204s coverage: 100.0% of statements
Testifyの利用
先述のテストでは、 t.Errorf
内でエラーを書き出していますが、https://github.com/stretchr/testify を利用すると、以下のように簡潔に書き直すことができます。
# 修正前
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.args.i, tt.args.j); got != tt.want {
t.Errorf("Add() = %v, want %v", got, tt.want)
}
})
# 修正後
# "github.com/stretchr/testify/assert" のimportが必要です。
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, Add(tt.args.i, tt.args.j), "they should be equal")
})
並列実行
異なるパッケージのテストを並列実行
( -p(-parallel)オプション )
「bbbパッケージのテスト」と「cccパッケージのテスト」が存在します。
./
├── bbb/
│ ├── bbb.go
│ └── bbb_test.go
└── ccc/
├── ccc.go
└── ccc_test.go
-p(-parallel)オプション
で並列数を指定できます。まずは、-p=1
で実行してみます。
( -count=1
はcacheを利用させないために指定しています。)
$ /usr/bin/time go test -count=1 -p=1 -v ./...
=== RUN TestAdd
--- PASS: TestAdd (3.00s)
PASS
ok test-sample/parallel/bbb 3.242s
=== RUN TestAdd
--- PASS: TestAdd (3.00s)
PASS
ok test-sample/parallel/ccc 3.209s
7.13 real 0.45 user 0.29 sys
bbbパッケージのテストに約3秒、cccパッケージのテストに約3秒かかり、合計で約7秒かかっています。
次に、-p=2
で実行してみます。
$ /usr/bin/time go test -count=1 -p=2 -v ./...
=== RUN TestAdd
--- PASS: TestAdd (3.00s)
PASS
ok test-sample/parallel/bbb 3.088s
=== RUN TestAdd
--- PASS: TestAdd (3.00s)
PASS
ok test-sample/parallel/ccc 3.131s
3.73 real 0.42 user 0.29 sys
bbbパッケージのテストに約3秒、cccパッケージのテストに約3秒かかるのは変化ないですが、合計で約4秒ほどで完了するようになりました。
同一パッケージ内のテストを並列実行
( t.Parallel() )
先の例では、パッケージ単位での並列実行でした。
今度は、同一パッケージ内のテストを並列実行する方法を確認します。
以下、動作確認コードです。
./
└── ddd/
├── ddd.go
└── ddd_test.go
package ddd
import "time"
func Add(i, j int) int {
time.Sleep(3 * time.Second)
return i + j
}
package ddd
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAdd(t *testing.T) {
type args struct {
i int
j int
}
tests := []struct {
name string
args args
want int
}{
{name: "1", args: args{1, 1}, want: 2},
{name: "2", args: args{2, 2}, want: 4},
{name: "3", args: args{3, 3}, want: 6},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fmt.Printf("tt.args: %v tt.want: %v\n", tt.args, tt.want)
assert.Equal(t, tt.want, Add(tt.args.i, tt.args.j), "they should be equal")
})
}
}
テストを実行してみます。
$ /usr/bin/time go test -count=1 -v ./...
=== RUN TestAdd
=== RUN TestAdd/1
tt.args: {1 1} tt.want: 2
=== RUN TestAdd/2
tt.args: {2 2} tt.want: 4
=== RUN TestAdd/3
tt.args: {3 3} tt.want: 6
--- PASS: TestAdd (9.00s)
--- PASS: TestAdd/1 (3.00s)
--- PASS: TestAdd/2 (3.00s)
--- PASS: TestAdd/3 (3.00s)
PASS
ok test-sample/parallel2/ddd 9.260s
9.89 real 0.36 user 0.52 sys
トータルで約10秒ほどかかっています。
t.Parallel()で並列化(NG例)
t.Parallel()
を入れると並列化させることができます。
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // 追記
fmt.Printf("tt.args: %v tt.want: %v\n", tt.args, tt.want)
assert.Equal(t, tt.want, Add(tt.args.i, tt.args.j), "they should be equal")
})
}
テストを実行します。
$ /usr/bin/time go test -count=1 -v ./...
=== RUN TestAdd
=== RUN TestAdd/1
=== PAUSE TestAdd/1
=== RUN TestAdd/2
=== PAUSE TestAdd/2
=== RUN TestAdd/3
=== PAUSE TestAdd/3
=== CONT TestAdd/1
tt.args: {3 3} tt.want: 6
=== CONT TestAdd/2
tt.args: {3 3} tt.want: 6
=== CONT TestAdd/3
tt.args: {3 3} tt.want: 6
--- PASS: TestAdd (0.00s)
--- PASS: TestAdd/2 (3.00s)
--- PASS: TestAdd/1 (3.00s)
--- PASS: TestAdd/3 (3.00s)
PASS
ok test-sample/parallel2/ddd 3.238s
3.77 real 0.34 user 0.49 sys
トータルで約4秒ほどで完了するようになりました。
しかし、fmt.Printf
で出力している箇所が、全て tt.args: {3 3} tt.want: 6
になってしまっています。
変数tt
が、forループの最後の値を保持しているためです。
t.Parallel()で並列化(OK例)
以下のように、forループ内で tt := tt
を再宣言して、ループごとの値を保持させます。
for _, tt := range tests {
tt := tt // 追記
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
fmt.Printf("tt.args: %v tt.want: %v\n", tt.args, tt.want)
assert.Equal(t, tt.want, Add(tt.args.i, tt.args.j), "they should be equal")
})
}
テストを実行します。
$ /usr/bin/time go test -count=1 -v ./...
=== RUN TestAdd
=== RUN TestAdd/1
=== PAUSE TestAdd/1
=== RUN TestAdd/2
=== PAUSE TestAdd/2
=== RUN TestAdd/3
=== PAUSE TestAdd/3
=== CONT TestAdd/1
tt.args: {1 1} tt.want: 2
=== CONT TestAdd/2
tt.args: {2 2} tt.want: 4
=== CONT TestAdd/3
tt.args: {3 3} tt.want: 6
--- PASS: TestAdd (0.00s)
--- PASS: TestAdd/2 (3.00s)
--- PASS: TestAdd/1 (3.00s)
--- PASS: TestAdd/3 (3.00s)
PASS
ok test-sample/parallel2/ddd 3.251s
3.88 real 0.37 user 0.50 sys
意図通りにテストが並列実行されました。
TestMainでテスト前後に処理追加
TestMain関数 を定義すると、テスト前後に処理を入れることができます。
以下コードで実行タイミングを確認します。
package eee
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAdd(t *testing.T) {
assert.Equal(t, 8, Add(5, 3))
}
func TestSub(t *testing.T) {
assert.Equal(t, 2, Sub(5, 3))
}
func TestMain(m *testing.M) {
teardown := setupSample()
defer teardown()
m.Run()
}
func setupSample() func() {
fmt.Println("################## setup Sample ##################")
return func() {
fmt.Println("################## teardown Sample ##################")
}
}
実行してみます。
$ go test -v
################## setup Sample ##################
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestSub
--- PASS: TestSub (0.00s)
PASS
################## teardown Sample ##################
ok test-sample/eee 0.245s
参考
- テストの作成 | GoLand
- test package – cmd/go/internal/test – pkg.go.dev
- testing package – testing – pkg.go.dev
- How to Write Go Code – The Go Programming Language > Testing
- Add a test – The Go Programming Language
- stretchr/testify: A toolkit with common assertions and mocks that plays nicely with the standard library