Go言語 テスト
Go言語の提供するテストはシンプルです。テストのために覚えることは多くはありません。 Go言語の単体テスト用の機能は`testing`という標準パッケージとして提供されています。ベンチマークやカバレッジ、標準出力のテストなどをカバーしています。 また、テストは`go test`コマンドによって実行されます。サードパーティのツールなどは必要ありません。まずはこの`testing`パッケージの内容を見ていきます。
Go言語におけるテスト
Go 言語の提供するテストはシンプルです。テストのために覚えることは多くはありません。
Go 言語の単体テスト用の機能は testing
という標準パッケージとして提供されています。ベンチマークやカバレッジ、標準出力のテストなどをカバーしています。
またテストは go test
コマンドによって実行されます。サードパーティのツールなどは必要ありません。まずはこの testing
パッケージの内容を見ていきます。
testingパッケージによるテスト
テストを実行するためのルール
- xxx_test.go というファイル名で作成する
この命名規則により、
go build
のときは無視され、go test
のときのみビルドされます。 - 関数名は
TestXxx
とし、testing
パッケージを引数で受ける。 つまり、つぎのようになります。Test
の後の単語もキャメルケースとする必要があります。この命名規則を守らない場合、テストが実行されません。
func TestXxx(*testing.T)
- テストファイルはテスト対象ファイルと同一パッケージとする
はじめてのテスト
まずは簡単なテストを書いてみましょう。テスト対象は以下の関数です。
package main
func sum(a, b int) int {
return a + b
}
テストコードは以下のとおりです。
package main
import (
"testing"
)
func TestSum(t *testing.T) {
got := sum(1, 2)
if got != 3 {
// t.Errorfはテストの失敗を告げるメソッドです。
t.Errorf("sum(1, 2) want 3 but got %d", got)
}
}
それではテストを実行してみましょう。コンソール上で go test
でカレントディレクトリのテストを実行するか、go test 相対パス
でテストを実行できます。
go test
PASS
ok src/test 0.007s
無事テストが成功しました。試しに sum 関数の実装を修正してわざとテストが失敗する状態にしてみましょう。
package main
func sum(a, b int) int {
-- return a + b
++ return a - b
}
go test
--- FAIL: TestSum (0.00s)
main_test.go:10: sum(1, 2) want 3 but got -1
FAIL
exit status 1
FAIL src/test 0.007s
t.Errorf
メソッドによってテストの失敗が告げられ、メッセージが出力されました。
詳細なテスト結果の表示
-v
オプションを指定すると、詳細な結果を表示できます。
go test -v
=== RUN TestSum
--- PASS: TestSum (0.00s)
PASS
ok srco/test 0.007s
カバレッジの取得
-cover
オプションでカバレッジを取得できます。
go test -cover
PASS
coverage: 100.0% of statements
ok src/test 0.007s
testing.Tの主な関数
引数として受け取る構造体 testing.T
有用な関数が多くあります。
T.Error/T.Errorf
これらの関数はテストの失敗を記録しますが、処理はそのまま実行されます。
なお f
がついているものとそうでないものの違いは、出力をフォーマットできるかどうかです。
T.Fatal/T.Fatalf
こちらは T.Error/T.Errorf
と同じようにテストの失敗を記録しますが、呼び出された時点で処理を抜けます。
T.Log
エラーログをテキストに記録します。
T.LogF
引数に与えられたフォーマットに従って整形しエラーログに記録します。
T.Fail
テストに失敗した記録を残しますが、処理を継続します。
T.FailNow
テストに失敗した記録を残し、呼び出された時点で処理を抜けます。
すでに気づいたほうもいるかもしれないですが、T.Error/T.Errorf
と T.Fatal/T.Fatalf
は上記 4 つの関数を組み合わせたものです。
関係性は以下のとおりになっています。
Log | Logf | |
---|---|---|
Fail | Error | Erorrf |
FailNow | Fatal | Fatalf |
T.Skip
T.Skip
を使うと、今は実行したくないテストをスキップできます。例えば、テスト駆動開発を実践していて、また未実装の関数がある場合や、非常に時間のかかるテストを実行したくないときに有効です。
package main
import (
"testing"
)
func TestSum(t *testing.T) {
got := sum(1, 2)
if got != 3 {
t.Errorf("sum(1, 2) want 3 but got %d", got)
}
}
func TestMulti(t *testing.T) {
t.Skip("not implemented")
}
go test -v
=== RUN TestSum
--- PASS: TestSum (0.00s)
=== RUN TestMulti
TestMulti: main_test.go:15: not implemented
--- SKIP: TestMulti (0.00s)
PASS
ok src/test 0.013s
go test
コマンドに、-short
オプションを渡してテストケースに条件式を組み込むことでテストの実行をスキップできます。
次のように時間がかかるテストがあるとしましょう。
package main
import (
"testing"
"time"
)
func TestSomeLongRunningTest(t *testing.T) {
if testing.Short() {
t.Skip("Skipping long-running test in short mode")
}
time.Sleep(5 * time.Second)
}
オプションを渡さずに実行した場合、5 秒かかってテストが実行されました。
go test -v
=== RUN TestSomeLongRunningTest
--- PASS: TestSomeLongRunningTest (5.00s)
PASS
ok src/test 5.007s
-short
オプションを渡して実行してみると、次のようになります。
go test -v -short
=== RUN TestSomeLongRunningTest
TestSomeLongRunningTest: main_test.go:10: Skipping long-running test in short mode
--- SKIP: TestSomeLongRunningTest (0.00s)
PASS
ok src/test 0.014s
時間のかかるテストがスキップされていることがわかりました。
T.Parallel
Go 言語のテストは通常逐次実行されますが、並列実行させることによってテスト時間を短縮できます。
テストを並列実行させることができる条件は、それぞれのテストケースが独立して実行可能であることです。
テストケース内で、T.Parallel()
が呼ばれているテストのみが並列実行されます。
package main
import (
"testing"
"time"
)
func TestPrallel_1(t *testing.T) {
t.Parallel()
time.Sleep(1 * time.Second)
}
func TestPrallel_2(t *testing.T) {
t.Parallel()
time.Sleep(2 * time.Second)
}
func TestPrallel_3(t *testing.T) {
t.Parallel()
time.Sleep(3 * time.Second)
}
実行結果は以下のとおりです。
go test -v
=== RUN TestPrallel_1
=== PAUSE TestPrallel_1
=== RUN TestPrallel_2
=== PAUSE TestPrallel_2
=== RUN TestPrallel_3
=== PAUSE TestPrallel_3
=== CONT TestPrallel_3
=== CONT TestPrallel_1
=== CONT TestPrallel_2
--- PASS: TestPrallel_1 (1.00s)
--- PASS: TestPrallel_2 (2.01s)
--- PASS: TestPrallel_3 (3.01s)
PASS
ok src/test 3.013s
またコマンド実行時に -parallel
オプションを渡すことで、並列実行されるテストケースの数を指定できます。
例えば、-parallel 1
とオプションを渡すと、1 つのテストケースしか並列実行しなくなるため、結果的には逐次実行しているのと変わらなくなります。
go test -v -parallel 1
=== RUN TestPrallel_1
=== PAUSE TestPrallel_1
=== RUN TestPrallel_2
=== PAUSE TestPrallel_2
=== RUN TestPrallel_3
=== PAUSE TestPrallel_3
=== CONT TestPrallel_1
--- PASS: TestPrallel_1 (1.00s)
=== CONT TestPrallel_2
--- PASS: TestPrallel_2 (2.00s)
=== CONT TestPrallel_3
--- PASS: TestPrallel_3 (3.00s)
PASS
ok src/test 6.019s
Examples
testing
パッケージには、Examples という機能があります。これは、実行例をそのままテストコードとして記述する機能で、標準出力の内容をテストできます。
テストするためには、// Output:
から始まるコメントを記述します。
package main
import "fmt"
func ExampleHello() {
fmt.Println("Hello")
// Output: Hello
}
通常通り go test
で実行できます。
go test -v
=== RUN ExampleHello
--- PASS: ExampleHello (0.00s)
PASS
ok src/test 0.008s
テストの失敗時には got
と want
が表示されます。
go test -v
=== RUN ExampleHello
--- FAIL: ExampleHello (0.00s)
got:
World
want:
Hello
FAIL
exit status 1
FAIL src/test 0.009s
ベンチマーク
testing
パッケージでベンチマークを実行できます。
ベンチマークの実行は以下の形式で関数を記述します。
func BenchmarkXxx(*testing.B)
基本的な部分は通常のテストとあまり変わりありません。
ベンチマークをとりたい関数を b.N
回繰り返しベンチマークの精度をあげます。
package main
import "testing"
func BenchmarkSum(b *testing.B) {
for i := 0; i < b.N; i++ {
sum(1, 2)
}
}
ベンチマークを実行する際には、-bench
オプションで実行したいベンチマークファイルを指定します。すべてのベンチマークファイルを実行するには .
を指定します。
go test -bench .
goos: darwin
goarch: amd64
BenchmarkSum-4 1000000000 0.612 ns/op
PASS
ok src/test 0.701s
1000000000 回ループが繰り返されて実行されていることを表しています。1 回あたりの処理時間は 0.612 ナノ秒のようです。
機能テストも同時に実行されていることに注意してください。
機能テストを無視したい場合には、オプション -run
を指定します。このオプションはどの機能テストを実行するか指定するので、どの機能テストとも一致しない名前を指定すればすべてのテストを無視できます。
TestMainで共通処理を実行
テストデータの投入など、しばしばテストのなかで常に実行したい前処理や後処理を実装したいときがあります。
そのような場合のために、testing
パッケージは TestMain
関数を提供しています。典型的な TestMain
関数は次のようになります。
func TestMain(m *testing.M) {
setUp()
code := m.Run()
tearDown()
os.Exit(code)
}
func setUp() {
fmt.Println("Set up function")
}
func tearDown() {
fmt.Println("Tear down function")
}
setUp
と tearDown
は慣例的な名前ですので、特に決まりはありません。setUp
と tearDown
はすべてのテストケースで 1 回だけ実行されます。個々のテストケースは m.Run()
によって呼び出されています。関数 Run
を呼び出すと終了コードが返されるので、関数 os.Exit
に渡しています。