こんにちは。
Golangが一般的に使われるようになってきてもう久しいですね。
最近作られたSWET製のツールでも、Golangを採用したものがあります。
そこで、Golangの標準テストパッケージtesting
やその他についてまとめたいと思います。
今回から3回にわたり、
- testingパッケージを使ったユニットテスト(testing)
- テストにおける共通処理(testing)
- アプリケーションのテスト(gomock, httptest)
を紹介します。
この記事を読んで一通りGolangでテストがかけるようになると嬉しいです。
この文章中に登場するサンプルは GitHub にありますので、実際に動作させることが可能です。
$ go get github.com/duck8823/sample-go-testing $ cd $GOPATH/src/github.com/duck8823/sample-go-testing $ git checkout refs/tags/blog $ dep ensure # 依存パッケージのダウンロード
なお、文章中のコマンドは全てバージョン1.9.2
での動作を確認しています。
testingパッケージ
testing
パッケージはGolangが標準で提供している、テスト全般を支援するパッケージです。
ベンチマークやカバレッジ、標準出力のテストなどカバーしている範囲は広く、サードパーティ製のテストフレームワークに頼らずともテストが記述できます。
標準なのでGolangをインストールしたら使えます。パッケージマネージャなどで指定する必要はありません。
以下のtypeについてユニットテストを書いてみましょう。
animals/animal.go
package animals import ( "fmt" "github.com/duck8823/sample-go-testing/foods" ) type Duck struct { name string } func NewDuck(name string) *Duck { return &Duck{name} } func (duck *Duck) Say() string { return fmt.Sprintf("%s says quack", duck.name) } func (duck *Duck) Eat(food foods.Food) string { return fmt.Sprintf("%s ate %s", duck.name, food.Name()) }
foods/food.go
package foods type Food interface { Name() string } type Apple struct { cultivar string } func NewApple(cultivar string) *Apple { return &Apple{cultivar} } func (apple *Apple) Name() string { return apple.cultivar }
testing
では、 _test.go
で終わる名前のファイルにテストコードを書きます。
また、TestXxx
で始まる関数についてテストを実行します。
なお、テストコードは対応するソースコードと同一のパッケージにすることでpackage privateな変数や関数を呼び出すことができ、 APIを必要以上公開せずにテストを書くことができます。
animals/animals_test.go
package animals import "testing" func TestDuck_name(t *testing.T) { duck := &Duck{"tarou"} actual := duck.name expected := "tarou" if actual != expected { t.Errorf("got: %v\nwant: %v", actual, expected) } }
パッケージ名_test
をテストコードを記述するパッケージ名として利用することもできます。
パッケージ外に公開したAPIを通してのみテストしたい場合は、こちらを利用するとよさそうです。
この場合、テスト対象のパッケージもインポートする必要があります。
animals/animals_01_test.go
package animals_test import "testing" import "github.com/duck8823/sample-go-testing/animals" func TestDuck_Say(t *testing.T) { duck := animals.NewDuck("tarou") actual := duck.Say() expected := "tarou says quack" if actual != expected { t.Errorf("got: %v\nwant: %v", actual, expected) } }
テストを記述したら実行しましょう。
go test
の引数にはテストが記述されたファイルを指定することができます。
テストがプロダクトコードと同一のパッケージの場合、引数にテスト対象のファイルを含める必要があります。
$ go test animals/animals_test.go animals/animal.go
プロダクトコードとテストコードのパッケージを分けてimport
している場合は、単一のファイルでも実行可能です。
$ go test animals/animals_01_test.go
他の言語ではビルドツールやテストランナーを介して実行するものが多い中、
go
のサブコマンドとしてtest
を実行できることからも、言語として積極的にサポートしているのがわかります。
go test
の引数はファイルの他、github.com/duck8823/sample-go-testing/animals
のようにパッケージを指定することもできます。
上記の記述だと冗長的になってしまいますが、パッケージの指定はディレクトリからの相対パスとして記述することもできます。
$ go test ./animals
パッケージングされたアプリケーションを作成している場合は ./...
を指定することで全ディレクトリのテストを再帰的に実行してくれます。
$ go test ./...
(1.9より以前のバージョンの場合は)
$ go test $(go list ./... | grep -v /vendor)
実行結果は以下のように表示されます。
ok command-line-arguments 0.007s
詳細な結果を見たい場合は、-v
オプションが有効です。
$ go test -v animals/animals_01_test.go === RUN TestDuck_Say --- PASS: TestDuck_Say (0.00s) PASS ok command-line-arguments 0.007s
アサーション
標準のtestingパッケージでは、アサーション用の関数が用意されていません。
Errorf
などの関数を利用し、自分でエラーメッセージを書く必要があります。
duck := animals.NewDuck("tarou") actual := duck.Say() expected := "tarou says quack" if actual != expected { t.Errorf("got: %v\nwant: %v", actual, expected) }
testify のようなサードパーティ製のアサーションライブラリも存在しています。
これらのライブラリを利用することで、記述が容易になります。
import "github.com/stretchr/testify/assert"
duck := animals.NewDuck("tarou") actual := duck.Say() expected := "tarou says quack" assert.Equals(t, expected, actual)
サブテスト
テストを階層構造にすることができます。
サブテストはテストケース内で t.Run
をコールすることで実現できます。
第一引数にはテストケース名、第二引数には無名関数を渡すことができます。
animals/animals_02_test.go
package animals_test import ( "github.com/duck8823/sample-go-testing/animals" "github.com/duck8823/sample-go-testing/foods" "testing" ) func TestDuck(t *testing.T) { duck := animals.NewDuck("tarou") t.Run("it says quack", func(t *testing.T) { actual := duck.Say() expected := "tarou says quack" if actual != expected { t.Errorf("got: %v\nwant: %v", actual, expected) } }) t.Run("it ate apple", func(t *testing.T) { apple := foods.NewApple("sunfuji") actual := duck.Eat(apple) expected := "tarou ate sunfuji" if actual != expected { t.Errorf("got: %v\nwant: %v", actual, expected) } }) }
テスト結果は以下のように表示されます。
$ go test -v ./animals/animals_02_test.go === RUN TestDuck === RUN TestDuck/it_says_quack === RUN TestDuck/it_ate_apple --- PASS: TestDuck (0.00s) --- PASS: TestDuck/it_says_quack (0.00s) --- PASS: TestDuck/it_ate_apple (0.00s) PASS ok command-line-arguments 0.006s
ベンチマーク
Golangのtestingパッケージでは、ベンチマークも標準でサポートしており、簡単に記述することができます。 ベンチマークは、関数を以下のように定義することで実現できます。
func BeanchmarkXxx(b *testing.B) { for i := 0; i < b.N; i++ { // ここに処理を記述 } }
animals/animals_06_test.go
package animals_test import ( "github.com/duck8823/sample-go-testing/animals" "github.com/duck8823/sample-go-testing/foods" "testing" ) func BenchmarkDuck_Say(b *testing.B) { duck := animals.NewDuck("tarou") b.ResetTimer() for i := 0; i < b.N; i++ { duck.Say() } b.StopTimer() } func BenchmarkDuck_Eat(b *testing.B) { duck := animals.NewDuck("tarou") food := foods.NewApple("sunfuji") b.ResetTimer() for i := 0; i < b.N; i++ { duck.Eat(food) } b.StopTimer() }
ベンチマークの実行は -bench
オプションをつけます。
$ go test -v -bench=. ./animals/animals_06_test.go goos: darwin goarch: amd64 BenchmarkDuck_Say-4 10000000 139 ns/op BenchmarkDuck_Eat-4 5000000 407 ns/op PASS ok command-line-arguments 3.965s
出力結果はループの回数とループ1回あたりの処理時間です。
-benchmem
オプションをつけることで、メモリに関するベンチマークも取得することができます。
前処理や後処理がある場合は ResetTimer
や StopTimer
関数を呼び出して正しく測定できるようにしましょう。
func BeanchmarkXxx(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { // ここに処理を記述 } b.StopTimer() }
カバレッジ
カバレッジも標準でサポートされています。
-cover
オプションをつけることで、テスト実行結果にカバレッジ情報が付加されます。
$ go test -cover ./animals ok github.com/duck8823/sample-go-testing/animals 10.014s coverage: 100.0% of statements
カバレッジレポートの出力
カバレッジレポートを出力するためにには -coverprofile
オプションを利用しますが、
バージョン1.9(2018/01/08現在)では複数のパッケージにまたがって利用することができません。
$ go test -cover -coverprofile cover.out ./animals ./foods cannot use test profile flag with multiple packages
1.10からはサポートされるようなので、試してみましょう。
beta版のインストールは以下の手順で行います。
go get golang.org/x/build/version/go1.10beta1 go1.10beta1 download
go
コマンドをインストールした go1.10beta1
に置き換えて実行してみましょう。
$ go1.10beta1 test -cover -coverprofile cover.out ./animals ./foods ok github.com/duck8823/sample-go-testing/animals 10.009s coverage: 100.0% of statements ok github.com/duck8823/sample-go-testing/foods 0.006s coverage: 100.0% of statements
cover.out
の出力結果は以下のようになりました。
mode: set github.com/duck8823/sample-go-testing/foods/food.go:11.39,13.2 1 1 github.com/duck8823/sample-go-testing/foods/food.go:15.35,17.2 1 1 github.com/duck8823/sample-go-testing/animals/animal.go:12.33,14.2 1 1 github.com/duck8823/sample-go-testing/animals/animal.go:16.32,18.2 1 1 github.com/duck8823/sample-go-testing/animals/animal.go:20.47,22.2 1 1
-coverprofile
で出力されるカバレッジレポートは見にくいので、 htmlとして出力しましょう。
カバレッジレポートからhtmlを生成するのも標準機能です。
go tool cover -html=cover.out -o cover.html
最後に
今回は紹介しませんでしたが他にもテストのスキップや、標準出力のテストが容易になるExampleといった魅力的な機能があります。 Golangは 公式ドキュメント も充実しているので、是非ご覧ください。
次回はtesting
パッケージを利用して共通処理(Before/After)を実現する方法について紹介します。