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 statementsTestifyの利用
先述のテストでは、 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 sysbbbパッケージのテストに約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 sysbbbパッケージのテストに約3秒、cccパッケージのテストに約3秒かかるのは変化ないですが、合計で約4秒ほどで完了するようになりました。
同一パッケージ内のテストを並列実行
( t.Parallel() )
先の例では、パッケージ単位での並列実行でした。
今度は、同一パッケージ内のテストを並列実行する方法を確認します。
以下、動作確認コードです。
./
└── ddd/
├── ddd.go
└── ddd_test.gopackage 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