テストの書き方(Golandで作成, 並列実行, 前後に処理追加)

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でテストファイル生成

Golandでテストファイル生成

Generateをクリックします。

Golandでテストファイル生成

Tests for fileをクリックします。

Golandでテストファイル生成

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

参考

よかったらシェアしてね!
目次