概要
よく忘れるので、ここにメモメモ。。。
Goには、標準でテスト、ベンチマーク、プロファイルを行う機能が付属しています。
どれもコマンドラインで実行できるので便利です。
ただ、ベンチマークとプロファイルに関しては、コマンドラインオプションなども付与しないといけないのでよく忘れがち。
以下、自分用ですがメモです。
今回のサンプル
今回のサンプルは以下のURLにアップしています。よろしければご参考まで。
決まりごと
テスト、ベンチマーク、プロファイルは、いずれも go test
コマンドから行います。
なので、ファイル名は xxx_test.go
としておく必要があります。
テスト対象
以下の関数を lib.go
として保存して、テスト対象とします。
package main func Fib(n int) int { if n < 2 { return n } return Fib(n-1) + Fib(n-2) }
テスト
以下のようなテストコードを lib_test.go
に記載したとします。
package main import ( "fmt" "testing" ) // TestFib -- fib() の ユニットテスト func TestFib(t *testing.T) { tests := []struct { in, want int }{ {1, 1}, {2, 1}, {3, 2}, {10, 55}, {40, 102334155}, {41, 165580141}, {42, 267914296}, {43, 433494437}, } for _, test := range tests { test := test title := fmt.Sprintf("fib:%d", test.in) t.Run(title, func(t *testing.T) { t.Parallel() got := Fib(test.in) if test.want != got { t.Errorf("[want] %v\t[got] %v", test.want, got) } }) } }
テストの実行は以下のようにします。
$ go test .
実際の実行結果は以下のようになります。
gitpod /workspace/try-golang/cmd/test_bench_profile $ go test . ok github.com/devlights/try-golang/cmd/test_bench_profile 2.216s
デフォルトでは結果だけ表示されますが、詳細表示したい場合は -v
オプションを付与します。
gitpod /workspace/try-golang/cmd/test_bench_profile $ go test -v . === RUN TestFib === RUN TestFib/fib:1 === PAUSE TestFib/fib:1 === RUN TestFib/fib:2 === PAUSE TestFib/fib:2 === RUN TestFib/fib:3 === PAUSE TestFib/fib:3 === RUN TestFib/fib:10 === PAUSE TestFib/fib:10 === RUN TestFib/fib:40 === PAUSE TestFib/fib:40 === RUN TestFib/fib:41 === PAUSE TestFib/fib:41 === RUN TestFib/fib:42 === PAUSE TestFib/fib:42 === RUN TestFib/fib:43 === PAUSE TestFib/fib:43 === CONT TestFib/fib:1 === CONT TestFib/fib:10 === CONT TestFib/fib:40 === CONT TestFib/fib:42 === CONT TestFib/fib:3 === CONT TestFib/fib:2 === CONT TestFib/fib:43 === CONT TestFib/fib:41 --- PASS: TestFib (0.00s) --- PASS: TestFib/fib:1 (0.00s) --- PASS: TestFib/fib:10 (0.00s) --- PASS: TestFib/fib:3 (0.00s) --- PASS: TestFib/fib:2 (0.00s) --- PASS: TestFib/fib:40 (0.52s) --- PASS: TestFib/fib:41 (0.85s) --- PASS: TestFib/fib:42 (1.38s) --- PASS: TestFib/fib:43 (2.21s) PASS ok github.com/devlights/try-golang/cmd/test_bench_profile 2.216s
t.Parallel()
を指定しているので、平行でテストが実行されています。
t.Parallel()
は平行でテストを実施したい場合に呼び出しますので、普段は付けなくて良いですね。
Exampleテスト
Goには ExampleXxx
と記載することでもテストを記載できます。
ExampleXxx
の場合は、結果をコメントで記載しておきます。
以下のようになります。
package main import ( "fmt" "testing" ) // Examplefib -- fib() の Exampleテスト func Examplefib() { fmt.Println(fib(40)) // Output: // 102334155 }
これも通常のテストとして扱われます。このテストを用意しておくと go doc でドキュメント作成した際に Example ブロックに表示されるようになります。
関数の使い方を説明するコードとして使えるので便利です。
gitpod /workspace/try-golang/cmd/test_bench_profile $ go test -v . -run ^Example.*$ === RUN ExampleFib --- PASS: ExampleFib (0.52s) PASS ok github.com/devlights/try-golang/cmd/test_bench_profile 0.524s
カバレッジ
カバレッジは go test
に以下のようにオプションを指定することで実行できます。
$ go test . -coverprofile=coverage.out
上記で指定したファイルにカバレッジ結果が出力されます。
そのファイルを今度は go tool cover
に食べさせることで結果を見ることが出来ます。
$ go tool cover -func=coverage.out
実際にやってみると以下のようになります。
gitpod /workspace/try-golang/cmd/test_bench_profile $ go test -coverprofile=coverage.out PASS coverage: 100.0% of statements ok github.com/devlights/try-golang/cmd/test_bench_profile 14.928s gitpod /workspace/try-golang/cmd/test_bench_profile $ go tool cover -func=coverage.out github.com/devlights/try-golang/cmd/test_bench_profile/lib.go:3: Fib 100.0% total: (statements) 100.0%
なお、簡易な出力で良ければ以下でも出来ます。
$ go test -cover ./...
ベンチマーク
ベンチマークはテストと同じ xxx_test.go
に記載します。
通常のテスト関数は
func TestFib(t *testing.T) {}
という様に TestXxx
という命名規則で、引数に *testing.T
を受け取るようにしますが、ベンチマークの場合は
func BenchmarkFib(b *testing.B) {}
という様に BenchmarkXxx
という命名規則で、引数に *testing.B
を受け取るようにします。
例えば、以下のようになります。
// BenchmarkAppend_WithoutLen -- ベンチマークのサンプル func BenchmarkAppend_WithoutLen(b *testing.B) { s := make([]string, 0) b.ResetTimer() for i := 0; i < b.N; i++ { //lint:ignore SA4010 It's ok because this is just a example. s = append(s, fmt.Sprintf("%d", i)) } } // BenchmarkAppend_WithLen -- ベンチマークのサンプル func BenchmarkAppend_WithLen(b *testing.B) { s := make([]string, 0, b.N) b.ResetTimer() for i := 0; i < b.N; i++ { //lint:ignore SA4010 It's ok because this is just a example. s = append(s, fmt.Sprintf("%d", i)) } }
ベンチマークではループを回しますが、ループの上限は b.N
で指定するようにします。
実行は go test
に対して -bench
というオプションを指定します。
gitpod /workspace/try-golang/cmd/test_bench_profile $ go test . -bench . goos: linux goarch: amd64 pkg: github.com/devlights/try-golang/cmd/test_bench_profile cpu: Intel(R) Xeon(R) CPU @ 2.80GHz BenchmarkAppend_WithoutLen-16 5636902 210.7 ns/op BenchmarkAppend_WithLen-16 10482872 107.8 ns/op PASS ok github.com/devlights/try-golang/cmd/test_bench_profile 5.442s
ベンチマーク時にメモリの状況も取得する場合は、さらに -benchmem
を付与します。
gitpod /workspace/try-golang/cmd/test_bench_profile $ go test . -bench . -benchmem goos: linux goarch: amd64 pkg: github.com/devlights/try-golang/cmd/test_bench_profile cpu: Intel(R) Xeon(R) CPU @ 2.80GHz BenchmarkAppend_WithoutLen-16 5524316 214.2 ns/op 111 B/op 1 allocs/op BenchmarkAppend_WithLen-16 10607025 108.3 ns/op 16 B/op 1 allocs/op PASS ok github.com/devlights/try-golang/cmd/test_bench_profile 5.459s
上の出力ですが、実はベンチマークを実行する際にテストも実行されています。
通常はこれで良いのですが、時間が掛かるテストがあったりするとベンチマークが出るまでに時間がかかるので
以下のようにすると、テストの実行をせずにベンチマークだけ実行できます。
$ go test . -bench . -benchmem -run ^$
gitpod /workspace/try-golang/cmd/test_bench_profile $ go test . -bench . -benchmem -run ^$ goos: linux goarch: amd64 pkg: github.com/devlights/try-golang/cmd/test_bench_profile cpu: Intel(R) Xeon(R) CPU @ 2.80GHz BenchmarkAppend_WithoutLen-16 6408944 196.0 ns/op 98 B/op 1 allocs/op BenchmarkAppend_WithLen-16 9027681 115.4 ns/op 16 B/op 1 allocs/op PASS ok github.com/devlights/try-golang/cmd/test_bench_profile 3.723s
トータルの実行時間が減っていますね。
プロファイル
実際のコードに直接 runtime/pprof
パッケージを使ってプロファイルを採取する方法はここでは記載しません。
以下はテストコードを使ってプロファイルを採取するやり方です。
プロファイルはテストかベンチマーク関数があれば実行できます。
メモリのプロファイルを取得する場合は -memprofile mem.pprof
と CPU時間のプロファイルを取得する場合は -cpuprofile cpu.pprof
を付与します。
ファイル名は好きに指定できます。
実際の実行は例えば以下のようにします。
gitpod /workspace/try-golang/cmd/test_bench_profile $ go test . -bench . -benchmem -run ^$ -memprofile mem.pprof -cpuprofile cpu.pprof goos: linux goarch: amd64 pkg: github.com/devlights/try-golang/cmd/test_bench_profile cpu: Intel(R) Xeon(R) CPU @ 2.80GHz BenchmarkAppend_WithoutLen-16 5921113 200.3 ns/op 105 B/op 1 allocs/op BenchmarkAppend_WithLen-16 11097564 121.5 ns/op 16 B/op 1 allocs/op PASS ok github.com/devlights/try-golang/cmd/test_bench_profile 3.061s
これで、結果が出力されました。
次にこの結果を go tool pprof
に食べさせます。
gitpod /workspace/try-golang/cmd/test_bench_profile $ go tool pprof mem.pprof File: test_bench_profile.test Type: alloc_space Time: Dec 27, 2021 at 6:54am (UTC) Entering interactive mode (type "help" for commands, "o" for options) (pprof)
gdbみたいな感じでインタラクティブなCUIが起動するので、ここでコマンドを実行して値を見ることが出来ます。
一番良く使うのが top
です。
(pprof) top Showing nodes accounting for 1054.44MB, 99.72% of 1057.41MB total Dropped 30 nodes (cum <= 5.29MB) Showing top 10 nodes out of 11 flat flat% sum% cum cum% 672.02MB 63.55% 63.55% 700.52MB 66.25% github.com/devlights/try-golang/cmd/test_bench_profile.BenchmarkAppend_WithoutLen 345.42MB 32.67% 96.22% 348.42MB 32.95% github.com/devlights/try-golang/cmd/test_bench_profile.BenchmarkAppend_WithLen 31.50MB 2.98% 99.20% 31.50MB 2.98% fmt.Sprintf 5.51MB 0.52% 99.72% 5.51MB 0.52% runtime.allocm 0 0% 99.72% 5.51MB 0.52% runtime.newm 0 0% 99.72% 5.51MB 0.52% runtime.resetspinning 0 0% 99.72% 5.51MB 0.52% runtime.schedule 0 0% 99.72% 5.51MB 0.52% runtime.startm 0 0% 99.72% 5.51MB 0.52% runtime.wakep 0 0% 99.72% 1048.94MB 99.20% testing.(*B).launch
結果をpngで画像出力することも出来たりします。その場合は Graphviz が必要になります。
こんな感じ
インストールされていない場合は
$ sudo apt update -q ; sudo apt install -y graphviz
とかしてインストールするようにしましょう。
参考情報
- Escape $ dollar sign on Makefiles
- Add a test
- How to write Go code
- TableDrivenTests
- Profiling a Go program
- google/pprof
- Profile your golang benchmark with pprof
- How To Write Unit Tests in Go
- pprofでのプロファイル(計測)のやり方を改めて整理した
- Goのpprofの使い方【基礎編】
- go標準のbenchmark機能の使い方
- Go pprof 入門編 (CPU Profile とコマンドラインツール)
- Goメモ-138 (staticcheck で警告をコメントで抑制する)
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場