テスト(go test/testing)
Goでは自動テストの仕組みが備わっています。
自動テストはgo test
コマンドとtesting
パッケージを組み合わせて実現します。
ここでは主に次の内容を説明をします。
testing
パッケージを使ったテストの書き方- ドキュメントにサンプルコードを載せる方法
- ベンチマークテストの方法
go test
コマンドの使い方
最初のテスト
はじめに最小限のテストの作成と実行の仕方を説明します。
テスト対象として、次のコードを利用します。
calc.go
package calc
func Max(x, y int) int {
if x < y {
return y
}
return x
}
テスト用のソースファイルを作成します。
- ファイル名の末尾は
_test.go
とする - パッケージ名はテスト対象と同じにする(別パッケージも可、以降で説明)
- テスト関数のシグネチャは
func TestXxx(t *testing.T)
とする(Xxxは小文字で始まらない) testing.T.Errorf
を使ってログ出力とテストの失敗報告をする
calc_test.go
package calc
import (
"testing"
)
func TestMax(t *testing.T) {
got := Max(1, 2)
want := 2
if got != want {
t.Errorf("Max(1, 2) == %d, want %d", got, want)
}
}
go test
コマンドを使ってテストを実行します。
# ソースファイルがあるディレクトリに移動
cd <your directory>
# 現在のディレクトリのパッケージをテスト
go test
テストの実行結果(合格)が出力されます。
PASS
ok calc 0.011s
以上でテスト作成と実行ができました。 Goのテストはとてもシンプルに始めることができます。
テスト可能なサンプル
パッケージのドキュメントには、実行可能なサンプルコードがあります(例:fmt.Println)。
このサンプルコードは『ドキュメント用のコメント』ではなく『サンプル用のテストコード』を書くことで、ドキュメントに反映されます。 これによりテスト可能なサンプルコードを素早くドキュメントに提供できます。
サンプルコードの関数名はExample
で始めます。
テストの合否判定はtesting.T
の代わりにコメントを使います。
Output:
から始まるコメントとサンプル実行時の標準出力を比較します。
これらが一致するとテスト合格です。
コメントがないとテストは実行されませんので注意してください。
func ExampleMax() {
fmt.Println(Max(1, 2))
// Output:
// 2
}
ドキュメントに掲載される場所は関数名で決まります。
func Example() {} // パッケージ全体
func ExampleMax() {} // Max関数またはMax型
func ExampleFile_Read() {} // File型のReadメソッド
同じ識別子に対して複数のサンプルを提供するには、アンダースコアと小文字が続く接頭辞を使用します。
func ExampleMax() {}
func ExampleMax_second() {}
func ExampleMax_third() {}
Unordered output:
から始まるコメントは、行の順序は無関係に比較できます。
func Example() {
fmt.Println(3)
fmt.Println(2)
fmt.Println(1)
// Unordered output:
// 1
// 2
// 3
}
ベンチマークテスト
Goには処理性能を測るベンチマークテストの仕組みが備わっています。
ベンチマークテストの書き方は次の通りです。
- ベンチマーク関数のシグネチャは
func BenchmarkXxx(b *testing.B)
とする(Xxxは小文字で始まらない) - テスト対象の関数を
b.N
回実行する(b.N
は十分な長さになるよう自動で調整される)
func BenchmarkMax(b *testing.B) {
for i := 0; i < b.N; i++ {
Max(1, 2)
}
}
ベンチマークテストを実行するには、-bench regexp
オプションを指定します。
regexp
には実行対象を指定します。すべて実行する場合は.
を指定します。
go test -bench .
BenchmarkMax-8 1000000000 0.700 ns/op
PASS
ok calc 0.800s
時間の掛かる前処理がある場合、前処理の後にb.ResetTimer
メソッドを実行します。
func BenchmarkMax(b *testing.B) {
// ここに時間の掛かる前処理があるとする
fmt.Println("時間のかかる前処理")
// ベンチマークのタイマーをリセット
b.ResetTimer()
for i := 0; i < b.N; i++ {
Max(1, 2)
}
}
テストの書き方
テストの書き方をさらに詳しく説明します。
テスト用のパッケージ
テスト用のパッケージをテスト対象と同じディレクトリに作成できます。 通常、Goのパッケージとディレクトリは1対1ですが、テスト用パッケージは例外です。
- パッケージ名は
xxx_test
とする - 特別にテスト対象と同じディレクトリに配置する
- 別パッケージのため、テスト対象の非公開識別子にはアクセスできない
- パッケージ利用者視点でのテストができる
calc_test.go
package calc_test
import (
"testing"
"example/calc"
)
func TestMax(t *testing.T) {
got := calc.Max(1, 2)
want := 2
if got != want {
t.Errorf("Max(1, 2) == %d, want %d", got, want)
}
}
テストデータの置き場所
Goのツールはビルドなどでtestdata
という名前のディレクトリを無視します。
テストデータはtestdata
ディレクトリに置きましょう。
testing.Tの主なメソッド
testing.T
の主なメソッドは次の通りです。
Name
:実行中のテスト名取得Error
:ログ出力+テスト失敗報告ErrorF
:整形ログ出力+テスト失敗報告Fatal
:ログ出力+テスト失敗報告+テストケース終了FatalF
:整形ログ出力+テスト失敗報告+テストケース終了Skip
:ログ出力+テストスキップSkipF
:整形ログ出力+テストスキップLog
:ログ出力LogF
:整形ログ出力Fail
:テスト失敗報告FailNow
:テスト失敗報告+テストケース終了SkipNow
:テストスキップ
サブテスト
t.Run
メソッドを使ってサブテストを定義できます。
第一引数はテストの名前、第二引数はテストコードを指定します。
func TestMaxSubtests(t *testing.T) {
t.Run("正の数", func(t *testing.T) {
got := Max(1, 2)
want := 2
if got != want {
t.Errorf("Max(1, 2) == %d, want %d", got, want)
}
})
t.Run("負の数", func(t *testing.T) {
got := Max(-1, -2)
want := -1
if got != want {
t.Errorf("Max(-1, -2) == %d, want %d", got, want)
}
})
t.Run("両方", func(t *testing.T) {
got := Max(1, -2)
want := 1
if got != want {
t.Errorf("Max(1, -2) == %d, want %d", got, want)
}
})
}
テーブル駆動テスト
テストケースのスライスをループしてテストすることをテーブル駆動テストと呼びます。
サブテストと相性が良く、テストケースの追加が簡単にできるのがメリットです。
func TestMaxTableDrivenTests(t *testing.T) {
tests := []struct {
name string
x int
y int
want int
}{
{"正の数", 1, 2, 2},
{"負の数", -1, -2, -1},
{"両方", 1, -2, 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Max(tt.x, tt.y)
if got != tt.want {
t.Errorf("Max(%d, %d) == %d, want %d", tt.x, tt.y, got, tt.want)
}
})
}
}
並列テスト
t.Parallel
メソッドを使ってテストを並列に実行できます。
func TestMaxParallelTesting(t *testing.T) {
// このテストは他の並列テストと並列に実行される
t.Parallel()
tests := []struct {
name string
x int
y int
want int
}{
{"正の数", 1, 2, 2},
{"負の数", -1, -2, -1},
{"両方", 1, -2, 1},
}
for _, tt := range tests {
// ttは並列処理で参照するため、ブロックの内側で変数を宣言する
tt := tt
t.Run(tt.name, func(t *testing.T) {
// サブテストは並列に実行される
t.Parallel()
got := Max(tt.x, tt.y)
if got != tt.want {
t.Errorf("Max(%d, %d) == %d, want %d", tt.x, tt.y, got, tt.want)
}
})
}
}
テストの前処理・後処理
TestMain
関数を使ってテストパッケージ全体の前処理・後処理を実行できます。
func TestMain(t *testing.M) {
fmt.Println("ここに前処理を書く")
code := t.Run()
fmt.Println("ここに後処理を書く")
os.Exit(code)
}
go testコマンドの使い方
go test
コマンドの使い方をさらに詳しく説明します。
テスト対象の指定
テスト対象の指定方法は次の通りです。
# 現在のディレクトリのパッケージ
go test
go test .
# 現在のディレクトリとサブディレクトリのパッケージ
go test ./...
# パッケージを指定
go test fmt
# ディレクトリを指定
go test ./calc
# ファイルを指定
go test calc.go calc_test.go
詳細表示
テストの詳細なログを出力するには-v
オプションを指定します。
go test -v ./...
カバレッジ取得
カバレッジを取得するには-cover
オプションを指定します。
go test -cover ./...
テストの絞り込み
実行するテストを選択するには-run regexp
オプションを指定します。
名前の指定には正規表現を使用できます。
また、スラッシュ区切りで関数名とサブテスト名を指定できます。
# テスト関数名にMaxを含むテストを実行
go test -run Max ./...
# テスト関数名がTestMaxのテストを実行
go test -run "^TestMax$" ./...
# テスト関数名にMaxを含み、サブテスト名にAbcを含むテストを実行
go test -run Max/Abc ./...
# すべてのテスト関数名のうち、サブテスト名にAbcを含むテストを実行
go test -run /Abc ./...
最大並列数
並列実行の最大数を指定するには-parallel n
オプションを指定します。
go test -parallel 2 ./...
ベンチマーク関係
ベンチマークでメモリ割り当ての統計を表示するには-benchmem
オプションを指定します。
go test -bench . -benchmem ./...
ベンチマークの実行時間を指定するには-benchtime t
オプションを指定します。
# 時間はtime.Durationで指定
go test -bench . -benchtime 10s ./...
# 回数指定も可能 形式:Nx
go test -bench . -benchtime 10x ./...
ベンチマークの回数を指定するには-count n
オプションを指定します。
go test -bench . -count 3 ./...
ベンチマークで同時に実行するCPUを指定するには-cpu
オプションを指定します。
go test -bench . -cpu 1,2,4 ./...
参考リンク
- https://golang.org/pkg/testing/
- https://golang.org/cmd/go/#hdr-Test_packages
- https://golang.org/cmd/go/#hdr-Testing_flags
- https://golang.org/cmd/go/#hdr-Testing_functions
- https://blog.golang.org/examples
- https://blog.golang.org/subtests
- https://github.com/golang/go/wiki/TableDrivenTests
- https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables