テスト
開発者にとって、良好なテストはプログラム中のエラーを事前に発見し、メンテナンス不及时による Bug 発生による精神的負担を回避できるため、テストを適切に記述することは非常に重要です。Go はテストに関して非常に実用的なコマンドラインツール go test を提供しており、標準ライブラリや多くのオープンソースフレームワークでもテストの姿を見ることができます。このツールは非常に使いやすく、現在以下の種類のテストをサポートしています。
- 例示テスト
- ユニットテスト
- ベンチマークテスト
- ファジングテスト
Go では、大部分の API は標準ライブラリ testing によって提供されます。
TIP
コマンドラインで go help testfunc コマンドを実行すると、上記 4 つのテストタイプに関する Go 公式の説明を参照できます。
記述規範
テスト記述を開始する前に、いくつかの規範に注意する必要があります。これにより、後続の学習がより便利になります。
- テストパッケージ:テストファイルは個別のパッケージに配置することをお勧めします。このパッケージは通常
testと命名されます。 - テストファイル:テストファイルは通常
_test.goで終わります。例えば、ある機能をテストする場合はfunction_test.goと命名します。テストタイプによってさらに細かく分類したい場合は、テストタイプをファイルプレフィックスとして使用することもできます。例えばbenchmark_marshaling_test.goやexample_marshaling_test.goなどです。 - テスト関数:各テストファイルには、異なるテストに使用されるいくつかのテスト関数があります。異なるテストタイプに対して、テスト関数の命名スタイルも異なります。例えば、例示テストは
ExampleXXXX、ユニットテストはTestXXXX、ベンチマークテストはBenchmarkXXXX、ファジングテストはFuzzXXXXです。これにより、コメントがなくてもどのようなタイプのテストかがわかります。
TIP
パッケージ名が testdata の場合、そのパッケージは通常テスト用の補助データを保存するためのもので、テスト実行時に Go は testdata という名前のパッケージを無視します。
上記の規範に従い、良好なテストスタイルを養成することで、日々のメンテナンスの手間を大幅に削減できます。
テスト実行
テスト実行は主に go test コマンドを使用します。以下は実際のコード例です。現在、テスト対象ファイル /say/hello.go があります。コードは以下の通りです。
package say
import "fmt"
func Hello() {
fmt.Println("hello")
}
func GoodBye() {
fmt.Println("bye")
}およびテストファイル /test/example_test.go コードは以下の通りです。
package test
import (
"golearn/say"
)
func ExampleHello() {
say.Hello()
// Output:
// hello
}
func ExampleGoodBye() {
say.GoodBye()
// Output:
// bye
}
func ExampleSay() {
say.Hello()
say.GoodBye()
// Output:
// hello
// bye
}これらのテストを実行するにはいくつかの方法があります。例えば、test パッケージ下のすべてのテストケースを実行したい場合は、test ディレクトリで以下のコマンドを実行するだけです。
$ go test ./
PASS
ok golearn/test 0.422s./ は現在のディレクトリを示します。Go は test ディレクトリ下のすべてのテストファイルを再コンパイルした後、すべてのテストケースをすべて実行します。結果から、すべてのテストケースに合格したことがわかります。その後のパラメータには複数のディレクトリを指定することもできます。例えば、以下のコマンドでは、プロジェクトのメインディレクトリには明らかに実行可能なテストファイルがありません。
$ go test ./ ../
ok golearn/test
? golearn [no test files]TIP
実行パラメータに複数のパッケージがある場合、Go はすでに成功したテストケースを再実行しません。実行時に行末に (cached) を追加して、出力結果が前回のキャッシュであることを示します。テストフラグパラメータが以下のセットにある場合、Go はテスト結果をキャッシュします。そうでない場合はキャッシュしません。
-benchtime, -cpu,-list, -parallel, -run, -short, -timeout, -failfast, -vキャッシュを無効にしたい場合は、-count=1 パラメータを追加します。
もちろん、特定のテストファイルを個別に指定して実行することもできます。
$ go test example_test.go
ok command-line-arguments 0.457sまたは、特定のテストファイルの特定のテストケースを個別に指定することもできます。例えば
$ go test -run ExampleSay
PASS
ok golearn/test 0.038s上記の 3 つの状況はすべてテストを完了しましたが、出力結果が簡潔すぎるため、-v パラメータを追加して出力結果をより詳細にできます。例えば
$ go test ./ -v
=== RUN ExampleHello
--- PASS: ExampleHello (0.00s)
=== RUN ExampleGoodBye
--- PASS: ExampleGoodBye (0.00s)
=== RUN ExampleSay
--- PASS: ExampleSay (0.00s)
PASS
ok golearn/test 0.040sこれで、各テストケースの実行順序、所要時間、実行状況、および全体の所要時間を明確に確認できます。
TIP
go test コマンドはデフォルトですべてのユニットテスト、例示テスト、ファジングテストを実行します。-bench パラメータを追加すると、すべてのタイプのテストが実行されます。例えば、以下のコマンドです。
$ go test -bench .したがって、-run パラメータを使用して指定する必要があります。例えば、すべてのベンチマークテストのみを実行するコマンドは以下の通りです。
$ go test -bench . -run ^$よく使用されるパラメータ
Go テストには非常に多くのフラグパラメータがあります。以下ではよく使用されるパラメータのみを紹介します。より多くの詳細を知りたい場合は、go help testflag コマンドを使用して自行で参照することをお勧めします。
| パラメータ | 説明 |
|---|---|
-o file | コンパイル後のバイナリファイル名を指定 |
-c | テストファイルのみをコンパイルし、実行しない |
-json | テストログを json 形式で出力 |
-exec xprog | xprog を使用してテストを実行。go run と同等 |
-bench regexp | regexp に一致するベンチマークテストを選択 |
-fuzz regexp | regexp に一致するファジングテストを選択 |
-fuzztime t | ファジングテストが自動的に終了する時間。t は時間間隔。単位が x の場合、回数を示す。例えば 200x |
-fuzzminimizetime t | モードテストが実行される最小時間。規則は上記と同じ |
-count n | テストを n 回実行。デフォルトは 1 回 |
-cover | テストカバレッジ分析を有効化 |
-covermode set,count,atomic | カバレッジ分析のモードを設定 |
-cpu | テスト実行のために GOMAXPROCS を実行 |
-failfast | 最初のテスト失敗後、新しいテストを開始しない |
-list regexp | regexp に一致するテストケースを一覧表示 |
-parallel n | t.Parallel を呼び出したテストケースの並列実行を許可。n 値は並列実行の最大数 |
-run regexp | regexp に一致するテストケースのみを実行 |
-skip regexp | regexp に一致するテストケースをスキップ |
-timeout d | 単一テストの実行時間が時間間隔 d を超えた場合、panic します。d は時間間隔。例:1s,1ms,1ns など |
-shuffle off,on,N | テストの実行順序をシャッフル。N はランダムシード。デフォルトシードはシステム時間 |
-v | より詳細なテストログを出力 |
-benchmem | ベンチマークテストのメモリ割り当てを統計 |
-blockprofile block.out | テスト中のゴルーチンブロッキング状況を統計し、ファイルに書き込み |
-blockprofilerate n | ゴルーチンブロッキング統計頻度を制御。詳細は go doc runtime.SetBlockProfileRate コマンドを参照 |
-coverprofile cover.out | カバレッジテストの状況を統計し、ファイルに書き込み |
-cpuprofile cpu.out | CPU 状況を統計し、ファイルに書き込み |
-memprofile mem.out | メモリ割り当て状況を統計し、ファイルに書き込み |
-memprofilerate n | メモリ割り当て統計の頻度を制御。詳細は go doc runtime.MemProfileRate コマンドを参照 |
-mutexprofile mutex.out | ロック競合状況を統計し、ファイルに書き込み |
-mutexprofilefraction n | n 個のゴルーチンが 1 つの相互排他ロックを競合する状況を統計 |
-trace trace.out | 実行追跡状況をファイルに書き込み |
-outputdir directory | 上記の統計ファイルの出力ディレクトリを指定。デフォルトは go test の実行ディレクトリ |
例示テスト
例示テストは、他の 3 つのテストがプログラムの問題点を発見するためのものとは異なり、更多的是特定の機能の使用方法を示し、ドキュメントとしての役割を果たします。例示テストは公式に定義された概念ではなく、強制的な規範でもなく、むしろエンジニアリング上の慣習のようなもので、遵守するかどうかは開発者次第です。例示テストは標準ライブラリで非常に多く見られ、通常は公式が記述した標準ライブラリコードの例です。例えば、標準ライブラリ context/example_test.go の ExampleWithDeadline テスト関数は、DeadlineContext の基本的な使用方法を示しています。
// This example passes a context with an arbitrary deadline to tell a blocking
// function that it should abandon its work as soon as it gets to it.
func ExampleWithDeadline() {
d := time.Now().Add(shortDuration)
ctx, cancel := context.WithDeadline(context.Background(), d)
// Even though ctx will be expired, it is good practice to call its
// cancellation function in any case. Failure to do so may keep the
// context and its parent alive longer than necessary.
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
// Output:
// context deadline exceeded
}表面的には、このテスト関数は普通の関数のようですが、例示テストは主に Output コメントによって体现されます。テスト対象関数が 1 行の出力のみの場合、Output コメントを使用して出力を検証します。まず hello.go という名前のファイルを作成し、以下のコードを記述します。
package say
import "fmt"
func Hello() {
fmt.Println("hello")
}
func GoodBye() {
fmt.Println("bye")
}SayHello 関数はテスト対象関数です。次に、テストファイル example_test.go を作成し、以下のコードを記述します。
package test
import (
"golearn/say"
)
func ExampleHello() {
say.Hello()
// Output:
// hello
}
func ExampleGoodBye() {
say.GoodBye()
// Output:
// bye
}
func ExampleSay() {
say.Hello()
say.GoodBye()
// Output:
// hello
// bye
}関数内の Output コメントは、関数の出力が hello かどうかを検証することを示しています。次に、テストコマンドを実行して結果を確認します。
$ go test -v
=== RUN ExampleHello
--- PASS: ExampleHello (0.00s)
=== RUN ExampleGoodBye
--- PASS: ExampleGoodBye (0.00s)
=== RUN ExampleSay
--- PASS: ExampleSay (0.00s)
PASS
ok golearn/test 0.448s結果から、すべてのテストに合格したことがわかります。Output にはいくつかの書き方があります。1 つ目は 1 行の出力のみで、この関数の出力が hello かどうかを検証することを意味します。
// Output:
// hello2 つ目は複数行の出力で、順序通りに出力が一致するかどうかを検証します。
// Output:
// hello
// bye3 つ目は順序なし出力で、順序を気にせずに複数行の出力が一致するかどうかを検証します。
// Unordered output:
// bye
// helloテスト関数に関して、最後の数行が Output コメントである場合にのみ例示テストと見なされ、そうでない場合は普通の関数にすぎず、Go によって実行されないことに注意する必要があります。
ユニットテスト
ユニットテストは、ソフトウェア中の最小テスト可能ユニットをテストするものです。ユニットのサイズの定義は開発者次第で、構造体、パッケージ、関数、またはタイプである可能性があります。以下も例を通じて説明します。まず /tool/math.go ファイルを作成し、以下のコードを記述します。
package tool
type Number interface {
~int8 | ~int16 | ~int32 | ~int64 | ~int
}
func SumInt[T Number](a, b T) T {
return a + b
}
func Equal[T Number](a, b T) bool {
return a == b
}次に、テストファイル /tool_test/unit_test.go を作成します。ユニットテストの場合、命名は unit_test またはテストしたいパッケージまたは機能をファイルプレフィックスとして使用できます。
package test_test
import (
"golearn/tool"
"testing"
)
func TestSum(t *testing.T) {
a, b := 10, 101
expected := 111
actual := tool.SumInt(a, b)
if actual != expected {
t.Errorf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
}
func TestEqual(t *testing.T) {
a, b := 10, 101
expected := false
actual := tool.Equal(a, b)
if actual != expected {
t.Errorf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}ユニットテストの場合、各テストケースの命名スタイルは TestXXXX で、関数の引数は t *testing.T である必要があります。testing.T は testing パッケージが提供するテストを容易にするための構造体で、多くの使用可能なメソッドを提供しています。例の t.Errorf は t.Logf と同等で、テスト失敗のログ情報をフォーマット出力するために使用されます。他のよく使用されるものには t.Fail があり、現在のケースをテスト失敗としてマークするために使用されます。同様の機能には t.FailNow があり、これもテスト失敗としてマークしますが、前者は失敗後も実行を継続し、後者は直接実行を停止します。以下の例では、予想結果を誤った結果に修正します。
package tool_test
import (
"golearn/tool"
"testing"
)
func TestSum(t *testing.T) {
a, b := 10, 101
expected := 110
actual := tool.SumInt(a, b)
if actual != expected {
// Errorf 内部では t.Fail() を使用
t.Errorf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
t.Log("test finished")
}
func TestEqual(t *testing.T) {
a, b := 10, 101
expected := true
actual := tool.Equal(a, b)
if actual != expected {
// Fatalf 内部では t.FailNow() を使用
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
t.Log("test finished")
}上記のテストを実行すると、出力は以下の通りです。
$ go test tool_test.go -v
=== RUN TestSum
tool_test.go:14: Sum(10,101) expected 110,actual is 111
tool_test.go:16: test finished
--- FAIL: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:25: Sum(10,101) expected true,actual is false
--- FAIL: TestEqual (0.00s)
FAIL command-line-arguments 0.037sテストログから、TestSum ケースは失敗しても test finished を出力しますが、TestEqual は出力しないことがわかります。同様に t.SkipNow があり、現在のケースを SKIP としてマークし、実行を停止します。次のラウンドのテストでは继续して実行されます。
package tool_test
import (
"golearn/tool"
"testing"
)
func TestSum(t *testing.T) {
a, b := 10, 101
expected := 110
actual := tool.SumInt(a, b)
if actual != expected {
t.Skipf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
t.Log("test finished")
}
func TestEqual(t *testing.T) {
a, b := 10, 101
expected := true
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
t.Log("test finished")
}テスト実行時に、テスト回数を 2 に修正します。
$ go test tool_test.go -v -count=2
=== RUN TestSum
tool_test.go:14: Sum(10,101) expected 110,actual is 111
--- SKIP: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:25: Sum(10,101) expected true,actual is false
--- FAIL: TestEqual (0.00s)
=== RUN TestSum
tool_test.go:14: Sum(10,101) expected 110,actual is 111
--- SKIP: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:25: Sum(10,101) expected true,actual is false
--- FAIL: TestEqual (0.00s)
FAIL command-line-arguments 0.468s上記の例では、最後の行に test finished を出力してテスト完了を示していますが、実際には t.Cleanup を使用してクリーンアップ関数を登録してこれを行うことができます。この関数はテストケースの終了時に実行されます。以下の通りです。
package tool_test
import (
"golearn/tool"
"testing"
)
func finished(t *testing.T) {
t.Log("test finished")
}
func TestSum(t *testing.T) {
t.Cleanup(func() {
finished(t)
})
a, b := 10, 101
expected := 111
actual := tool.SumInt(a, b)
if actual != expected {
t.Skipf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
}
func TestEqual(t *testing.T) {
t.Cleanup(func() {
finished(t)
})
a, b := 10, 101
expected := false
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}テスト実行後の出力は以下の通りです。
$ go test tool_test.go -v
=== RUN TestSum
tool_test.go:9: test finished
--- PASS: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:9: test finished
--- PASS: TestEqual (0.00s)
PASS
ok command-line-arguments 0.462sHelper
t.Helper() を使用して現在の関数をヘルパー関数としてマークできます。ヘルパー関数は単独のテストケースとして実行されず、ログを記録する際に出力される行番号もヘルパー関数の呼び出し元の行番号になるため、ログ分析時の位置特定がより正確になり、冗長な他の情報を回避できます。例えば、上記の t.Cleanup の例はヘルパー関数に修正できます。以下の通りです。
package tool_test
import (
"golearn/tool"
"testing"
)
func CleanupHelper(t *testing.T) {
t.Helper()
t.Log("test finished")
}
func TestSum(t *testing.T) {
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := 111
actual := tool.SumInt(a, b)
if actual != expected {
t.Skipf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
}
func TestEqual(t *testing.T) {
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := false
t.Helper()
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}テスト実行後の出力情報は以下の通りです。以前との違いは、test finished の行番号が呼び出し元の行番号になったことです。
$ go test tool_test.go -v
=== RUN TestSum
tool_test.go:15: test finished
--- PASS: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:30: test finished
--- PASS: TestEqual (0.00s)
PASS
ok command-line-arguments 0.464sTIP
上記の操作はメインテストでのみ実行可能で、直接実行されるテストケースです。サブテストで使用すると panic します。
サブテスト
一部の状況では、1 つのテストケース内で別のテストケースをテストする必要がある場合があります。このようなネストされたテストケースは一般にサブテストと呼ばれます。t.Run() メソッドを使用します。このメソッドのシグネチャは以下の通りです。
// Run メソッドはサブテストを実行するために新しいゴルーチンを開始し、関数 f の実行が完了するのを待ってから戻ります
// 戻り値はテストに合格したかどうか
func (t *T) Run(name string, f func(t *T)) bool以下は例です。
func TestTool(t *testing.T) {
t.Run("tool.Sum(10,101)", TestSum)
t.Run("tool.Equal(10,101)", TestEqual)
}実行後の結果は以下の通りです。
$ go test -run TestTool -v
=== RUN TestTool
=== RUN TestTool/tool.Sum(10,101)
tool_test.go:15: test finished
=== RUN TestTool/tool.Equal(10,101)
tool_test.go:30: test finished
--- PASS: TestTool (0.00s)
--- PASS: TestTool/tool.Sum(10,101) (0.00s)
--- PASS: TestTool/tool.Equal(10,101) (0.00s)
PASS
ok golearn/tool_test 0.449s出力を通じて、親子の階層構造がはっきりとわかります。上記の例では、最初のサブテストが完了するまで 2 番目のサブテストは実行されません。t.Parallel() を使用してテストケースを並列実行可能としてマークできます。これにより、出力順序は不定になります。
package tool_test
import (
"golearn/tool"
"testing"
)
func CleanupHelper(t *testing.T) {
t.Helper()
t.Log("test finished")
}
func TestSum(t *testing.T) {
t.Parallel()
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := 111
actual := tool.SumInt(a, b)
if actual != expected {
t.Skipf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
}
func TestEqual(t *testing.T) {
t.Parallel()
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := false
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}
func TestToolParallel(t *testing.T) {
t.Log("setup")
t.Run("tool.Sum(10,101)", TestSum)
t.Run("tool.Equal(10,101)", TestEqual)
t.Log("teardown")
}テスト実行後の出力は以下の通りです。
$ go test -run TestTool -v
=== RUN TestToolParallel
tool_test.go:46: setup
=== RUN TestToolParallel/tool.Sum(10,101)
=== PAUSE TestToolParallel/tool.Sum(10,101)
=== RUN TestToolParallel/tool.Equal(10,101)
=== PAUSE TestToolParallel/tool.Equal(10,101)
=== NAME TestToolParallel
tool_test.go:49: teardown
=== CONT TestToolParallel/tool.Sum(10,101)
=== CONT TestToolParallel/tool.Equal(10,101)
=== NAME TestToolParallel/tool.Sum(10,101)
tool_test.go:16: test finished
=== NAME TestToolParallel/tool.Equal(10,101)
tool_test.go:32: test finished
--- PASS: TestToolParallel (0.00s)
--- PASS: TestToolParallel/tool.Sum(10,101) (0.00s)
--- PASS: TestToolParallel/tool.Equal(10,101) (0.00s)
PASS
ok golearn/tool_test 0.444sテスト結果から、ブロッキング待機プロセスがあることがはっきりとわかります。テストケースを並列実行する際、上記の例は明らかに正常に実行できません。後続のコードが同期実行されることが保証されないためです。この場合、t.Run() をもう 1 つネストすることを選択できます。以下の通りです。
func TestToolParallel(t *testing.T) {
t.Log("setup")
t.Run("process", func(t *testing.T) {
t.Run("tool.Sum(10,101)", TestSum)
t.Run("tool.Equal(10,101)", TestEqual)
})
t.Log("teardown")
}再度実行すると、正常な実行結果が確認できます。
$ go test -run TestTool -v
=== RUN TestToolParallel
tool_test.go:46: setup
=== RUN TestToolParallel/process
=== RUN TestToolParallel/process/tool.Sum(10,101)
=== PAUSE TestToolParallel/process/tool.Sum(10,101)
=== RUN TestToolParallel/process/tool.Equal(10,101)
=== PAUSE TestToolParallel/process/tool.Equal(10,101)
=== CONT TestToolParallel/process/tool.Sum(10,101)
=== CONT TestToolParallel/process/tool.Equal(10,101)
=== NAME TestToolParallel/process/tool.Sum(10,101)
tool_test.go:16: test finished
=== NAME TestToolParallel/process/tool.Equal(10,101)
tool_test.go:32: test finished
=== NAME TestToolParallel
tool_test.go:51: teardown
--- PASS: TestToolParallel (0.00s)
--- PASS: TestToolParallel/process (0.00s)
--- PASS: TestToolParallel/process/tool.Sum(10,101) (0.00s)
--- PASS: TestToolParallel/process/tool.Equal(10,101) (0.00s)
PASS
ok golearn/tool_test 0.450sテーブルスタイル
上記のユニットテストでは、テストの入力データはすべて手動で宣言された個々の変数でした。データ量が少ない場合は問題ありませんが、複数のデータセットをテストしたい場合に、変数を宣言してテストデータを作成するのは現実的ではありません。したがって、一般的な状況では構造体スライスの形式を採用することが多く、構造体は一時宣言された匿名構造体です。このようなコーディングスタイルはテーブルのように見えるため、table-driven と呼ばれます。以下は例です。これは手動で複数の変数を宣言してテストデータを作成する例です。データが複数ある場合、直感的ではないため、テーブルスタイルに修正します。
func TestEqual(t *testing.T) {
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := false
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}修正後のコードは以下の通りです。
func TestEqual(t *testing.T) {
t.Cleanup(func() {
CleanupHelper(t)
})
// table driven style
testData := []struct {
a, b int
exp bool
}{
{10, 101, false},
{5, 5, true},
{30, 32, false},
{100, 101, false},
{2, 3, false},
{4, 4, true},
}
for _, data := range testData {
if actual := tool.Equal(data.a, data.b); actual != data.exp {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", data.a, data.b, data.exp, actual)
}
}
}このようにすると、テストデータがはるかに直感的になります。
ベンチマークテスト
ベンチマークテストはパフォーマンステストとも呼ばれ、通常はプログラムのメモリ占有、CPU 使用状況、実行時間などのパフォーマンス指標をテストするために使用されます。ベンチマークテストの場合、テストファイルは通常 bench_test.go で終わり、テストケースの関数は BenchmarkXXXX 形式である必要があります。
以下は、文字列連結の例のパフォーマンス比較をベンチマークテストの例として使用します。まず /tool/strConcat.go ファイルを作成します。周知の通り、文字列を直接使用して + 連結するのはパフォーマンスが非常に低く、strings.Builder を使用するのははるかに良いです。/tool/strings.go ファイルで 2 つの関数を作成して、2 つの方法の文字列連結を実行します。
package tool
import "strings"
func ConcatStringDirect(longString string) {
res := ""
for i := 0; i < 100_000.; i++ {
res += longString
}
}
func ConcatStringWithBuilder(longString string) {
var res strings.Builder
for i := 0; i < 100_000.; i++ {
res.WriteString(longString)
}
}次に、テストファイル /tool_test/bench_tool_test.go を作成します。コードは以下の通りです。
package tool_test
import (
"golearn/tool"
"testing"
)
var longString = "longStringlongStringlongStringlongStringlongStringlongStringlongStringlongString"
func BenchmarkConcatDirect(b *testing.B) {
for i := 0; i < b.N; i++ {
tool.ConcatStringDirect(longString)
}
}
func BenchmarkConcatBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
tool.ConcatStringWithBuilder(longString)
}
}テストコマンドを実行します。コマンドでは詳細ログとメモリ分析を有効化し、使用する CPU コア数リストを指定し、各テストケースを 2 回実行します。出力は以下の通りです。
$ go test -v -benchmem -bench . -run bench_tool_test.go -cpu=2,4,8 -count=2
goos: windows
goarch: amd64
pkg: golearn/tool_test
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
BenchmarkConcatDirect
BenchmarkConcatDirect-2 4 277771375 ns/op 4040056736 B/op 10000 allocs/op
BenchmarkConcatDirect-2 4 278500125 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-4 1 1153796000 ns/op 4040068784 B/op 10126 allocs/op
BenchmarkConcatDirect-4 1 1211017600 ns/op 4040073104 B/op 10171 allocs/op
BenchmarkConcatDirect-8 2 665460800 ns/op 4040077760 B/op 10219 allocs/op
BenchmarkConcatDirect-8 2 679774450 ns/op 4040080064 B/op 10243 allocs/op
BenchmarkConcatBuilder
BenchmarkConcatBuilder-2 3428 344530 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 3579 351858 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-4 2448 736177 ns/op 4128185 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1688 662993 ns/op 4128185 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1958 550333 ns/op 4128199 B/op 29 allocs/op
BenchmarkConcatBuilder-8 2174 552113 ns/op 4128196 B/op 29 allocs/op
PASS
ok golearn/tool_test 21.381s以下は、ベンチマークテストの出力結果の説明です。goos は実行中のオペレーティングシステムを表し、goarh は CPU アーキテクチャを表し、pkg はテストが所在するパッケージを表し、cpu は CPU に関するいくつかの情報です。以下の各テストケースの結果は、各ベンチマークテストの名前で区切られます。最初の列 BenchmarkConcatDirect-2 の 2 は使用する CPU コア数を表し、2 番目の列の 4 はコード内の b.N のサイズ、つまりベンチマークテスト内のループ回数を表し、3 番目の列 277771375 ns/op は各ループに消費される時間を表し、ns はナノ秒です。4 番目の列 4040056736 B/op は各ループで割り当てられるメモリのバイトサイズを表し、5 番目の列 10000 allocs/op は各ループのメモリ割り当て回数を表します。
明らかに、テスト結果によると、strings.Builder を使用するパフォーマンスは + を使用して文字列を連結するよりもはるかに高いです。直感的なデータ比較を通じてパフォーマンスを比較するのがベンチマークテストの目的です。
benchstat
benchstat はオープンソースのパフォーマンステスト分析ツールです。上記のパフォーマンステストのサンプル数は 2 グループのみで、サンプルが多くなると人工分析は非常に時間と労力がかかります。このツールはパフォーマンステストの分析問題を解決するために生まれました。
まず、このツールをダウンロードする必要があります。
$ go install golang.org/x/perf/benchstatベンチマークテストを 2 回実行します。今回はサンプル数を 5 に修正し、それぞれ old.txt と new.txt ファイルに出力して比較します。1 回目の実行結果
$ go test -v -benchmem -bench . -run bench_tool_test.go -cpu=2,4,8 -count=5 | tee -a old.txt
goos: windows
goarch: amd64
pkg: golearn/tool_test
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
BenchmarkConcatDirect
BenchmarkConcatDirect-2 4 290535650 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 298974625 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 299637800 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 276487000 ns/op 4040056784 B/op 10001 allocs/op
BenchmarkConcatDirect-2 4 356465275 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-4 2 894723200 ns/op 4040077424 B/op 10216 allocs/op
BenchmarkConcatDirect-4 2 785830400 ns/op 4040078288 B/op 10225 allocs/op
BenchmarkConcatDirect-4 2 743634000 ns/op 4040077568 B/op 10217 allocs/op
BenchmarkConcatDirect-4 2 953802700 ns/op 4040075408 B/op 10195 allocs/op
BenchmarkConcatDirect-4 2 953028750 ns/op 4040077520 B/op 10217 allocs/op
BenchmarkConcatDirect-8 2 684023150 ns/op 4040086784 B/op 10313 allocs/op
BenchmarkConcatDirect-8 2 634380250 ns/op 4040090528 B/op 10352 allocs/op
BenchmarkConcatDirect-8 2 685030600 ns/op 4040090768 B/op 10355 allocs/op
BenchmarkConcatDirect-8 2 817909650 ns/op 4040089808 B/op 10345 allocs/op
BenchmarkConcatDirect-8 2 600078100 ns/op 4040095664 B/op 10406 allocs/op
BenchmarkConcatBuilder
BenchmarkConcatBuilder-2 2925 419651 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 2961 423899 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 2714 422275 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 2848 452255 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 2612 454452 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-4 974 1158000 ns/op 4128189 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1098 1068682 ns/op 4128192 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1042 1056570 ns/op 4128194 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1280 978213 ns/op 4128191 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1538 1162108 ns/op 4128190 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1744 700824 ns/op 4128203 B/op 29 allocs/op
BenchmarkConcatBuilder-8 2235 759537 ns/op 4128201 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1556 736455 ns/op 4128204 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1592 825794 ns/op 4128201 B/op 29 allocs/op
BenchmarkConcatBuilder-8 2263 717285 ns/op 4128203 B/op 29 allocs/op
PASS
ok golearn/tool_test 56.742s2 回目の実行結果
$ go test -v -benchmem -bench . -run bench_tool_test.go -cpu=2,4,8 -count=5 | tee -a new.txt
goos: windows
goarch: amd64
pkg: golearn/tool_test
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
BenchmarkConcatDirect
BenchmarkConcatDirect-2 4 285074900 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 291517150 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 281901975 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 292320625 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 286723000 ns/op 4040056952 B/op 10002 allocs/op
BenchmarkConcatDirect-4 1 1188983000 ns/op 4040071856 B/op 10158 allocs/op
BenchmarkConcatDirect-4 1 1080713900 ns/op 4040070800 B/op 10147 allocs/op
BenchmarkConcatDirect-4 1 1203622300 ns/op 4040067344 B/op 10111 allocs/op
BenchmarkConcatDirect-4 1 1045291300 ns/op 4040070224 B/op 10141 allocs/op
BenchmarkConcatDirect-4 1 1123163300 ns/op 4040070032 B/op 10139 allocs/op
BenchmarkConcatDirect-8 2 790421300 ns/op 4040076656 B/op 10208 allocs/op
BenchmarkConcatDirect-8 2 659047300 ns/op 4040079488 B/op 10237 allocs/op
BenchmarkConcatDirect-8 2 712991800 ns/op 4040077184 B/op 10213 allocs/op
BenchmarkConcatDirect-8 2 706605350 ns/op 4040078000 B/op 10222 allocs/op
BenchmarkConcatDirect-8 2 656195700 ns/op 4040085248 B/op 10297 allocs/op
BenchmarkConcatBuilder
BenchmarkConcatBuilder-2 2726 386412 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 3439 335358 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 3376 338957 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 3870 326301 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 4285 339596 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1663 671535 ns/op 4128187 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1507 744885 ns/op 4128191 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1353 1097800 ns/op 4128187 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1388 1006019 ns/op 4128189 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1635 993764 ns/op 4128189 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1332 783599 ns/op 4128198 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1818 729821 ns/op 4128202 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1398 780614 ns/op 4128202 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1526 750513 ns/op 4128204 B/op 29 allocs/op
BenchmarkConcatBuilder-8 2164 704798 ns/op 4128204 B/op 29 allocs/op
PASS
ok golearn/tool_test 50.387sbenchstat を使用して比較します。
$ benchstat old.txt new.txt
goos: windows
goarch: amd64
pkg: golearn/tool_test
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
ConcatDirect-2 299.0m ± ∞ ¹ 286.7m ± ∞ ¹ ~ (p=0.310 n=5)
ConcatDirect-4 894.7m ± ∞ ¹ 1123.2m ± ∞ ¹ +25.53% (p=0.008 n=5)
ConcatDirect-8 684.0m ± ∞ ¹ 706.6m ± ∞ ¹ ~ (p=0.548 n=5)
ConcatBuilder-2 423.9µ ± ∞ ¹ 339.0µ ± ∞ ¹ -20.04% (p=0.008 n=5)
ConcatBuilder-4 1068.7µ ± ∞ ¹ 993.8µ ± ∞ ¹ ~ (p=0.151 n=5)
ConcatBuilder-8 736.5µ ± ∞ ¹ 750.5µ ± ∞ ¹ ~ (p=0.841 n=5)
geomean 19.84m 19.65m -0.98%
¹ need >= 6 samples for confidence interval at level 0.95
│ old.txt │ new.txt │
│ B/op │ B/op vs base │
ConcatDirect-2 3.763Gi ± ∞ ¹ 3.763Gi ± ∞ ¹ ~ (p=1.000 n=5)
ConcatDirect-4 3.763Gi ± ∞ ¹ 3.763Gi ± ∞ ¹ -0.00% (p=0.008 n=5)
ConcatDirect-8 3.763Gi ± ∞ ¹ 3.763Gi ± ∞ ¹ -0.00% (p=0.008 n=5)
ConcatBuilder-2 3.937Mi ± ∞ ¹ 3.937Mi ± ∞ ¹ ~ (p=1.000 n=5) ²
ConcatBuilder-4 3.937Mi ± ∞ ¹ 3.937Mi ± ∞ ¹ ~ (p=0.079 n=5)
ConcatBuilder-8 3.937Mi ± ∞ ¹ 3.937Mi ± ∞ ¹ ~ (p=0.952 n=5)
geomean 123.2Mi 123.2Mi -0.00%
¹ need >= 6 samples for confidence interval at level 0.95
² all samples are equal
│ old.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
ConcatDirect-2 9.999k ± ∞ ¹ 9.999k ± ∞ ¹ ~ (p=1.000 n=5)
ConcatDirect-4 10.22k ± ∞ ¹ 10.14k ± ∞ ¹ -0.74% (p=0.008 n=5)
ConcatDirect-8 10.35k ± ∞ ¹ 10.22k ± ∞ ¹ -1.26% (p=0.008 n=5)
ConcatBuilder-2 29.00 ± ∞ ¹ 29.00 ± ∞ ¹ ~ (p=1.000 n=5) ²
ConcatBuilder-4 29.00 ± ∞ ¹ 29.00 ± ∞ ¹ ~ (p=1.000 n=5) ²
ConcatBuilder-8 29.00 ± ∞ ¹ 29.00 ± ∞ ¹ ~ (p=1.000 n=5) ²
geomean 543.6 541.7 -0.33%
¹ need >= 6 samples for confidence interval at level 0.95
² all samples are equal結果から、benchstat がこれを 3 つのグループに分割していることがわかります。それぞれ所要時間、メモリ占有、メモリ割り当て回数です。その中で geomean は平均値で、p はサンプルの有意水準で、臨界区間は通常 0.05 で、0.05 より高いとあまり信頼できません。その中の 1 つのデータを以下に示します。
│ sec/op │ sec/op vs base │
ConcatDirect-4 894.7m ± ∞ ¹ 1123.2m ± ∞ ¹ +25.53% (p=0.008 n=5)old の実行所要時間は 894.7ms で、new の実行所要時間は 1123.2ms で、比較すると 25.53% の所要時間が増加しています。
ファジングテスト
ファジングテストは GO1.18 で導入された新機能で、ユニットテストとベンチマークテストの一種の拡張です。違いは、前者 2 つのテストデータは開発者が手動で記述する必要があるのに対し、ファジングテストはコーパスを通じてランダムなテストデータを生成できることです。Go のファジングテストに関する詳細な概念は Go Fuzzing で参照できます。ファジングテストの利点は、固定されたテストデータに比べて、ランダムデータはプログラムの境界条件をよりよくテストできることです。以下は公式チュートリアルの例を使用して説明します。今回は文字列を反転する関数をテストします。まず /tool/strings.go ファイルを作成し、以下のコードを記述します。
package tool
func Reverse(s string) string {
b := []byte(s)
for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return string(b)
}ファジングテストファイル /tool_test/fuzz_tool_test.go を作成し、以下のコードを記述します。
package tool
import (
"golearn/tool"
"testing"
"unicode/utf8"
)
func FuzzReverse(f *testing.F) {
testdata := []string{"hello world!", "nice to meet you", "good bye!"}
for _, data := range testdata {
f.Add(data)
}
f.Fuzz(func(t *testing.T, str string) {
first := tool.Reverse(str)
second := tool.Reverse(first)
t.Logf("str:%q,first:%q,second:%q", str, first, second)
if str != second {
t.Errorf("before: %q, after: %q", str, second)
}
if utf8.ValidString(str) && !utf8.ValidString(first) {
t.Errorf("Reverse produced invalid UTF-8 string %q %q", str, first)
}
})
}ファジングテストでは、まずコーパスシードライブラリにデータを追加する必要があります。例では f.Add() を使用して追加し、後続のランダムなテストデータの生成に役立てます。次に f.Fuzz(fn) を使用してテストを実行します。関数シグネチャは以下の通りです。
func (f *F) Fuzz(ff any)
func (f *F) Add(args ...any)fn はユニットテスト関数のロジックに似ており、関数の最初の引数は t *testing.T である必要があり、その後に生成したいパラメータが続きます。入力される文字列は予測できないため、ここでは 2 回反転する方法を使用して検証します。以下のコマンドを実行します。
$ go test -run Fuzz -v
=== RUN FuzzReverse
=== RUN FuzzReverse/seed#0
fuzz_tool_test.go:18: str:"hello world!",first:"!dlrow olleh",second:"hello world!"
=== RUN FuzzReverse/seed#1
fuzz_tool_test.go:18: str:"nice to meet you",first:"uoy teem ot ecin",second:"nice to meet you"
=== RUN FuzzReverse/seed#2
fuzz_tool_test.go:18: str:"good bye!",first:"!eyb doog",second:"good bye!"
--- PASS: FuzzReverse (0.00s)
--- PASS: FuzzReverse/seed#0 (0.00s)
--- PASS: FuzzReverse/seed#1 (0.00s)
--- PASS: FuzzReverse/seed#2 (0.00s)
PASS
ok golearn/tool_test 0.539sパラメータに -fuzz がない場合、ランダムなテストデータは生成されず、コーパスからのデータのみがテスト関数に渡されます。結果から、テストがすべて合格したことがわかりますが、これはユニットテストと同等ですが、実際には問題があります。以下に -fuzz パラメータを追加して再度実行します。
$ go test -fuzz . -fuzztime 30s -run Fuzz -v
=== RUN FuzzReverse
fuzz: elapsed: 0s, gathering baseline coverage: 0/217 completed
fuzz: minimizing 91-byte failing input file
fuzz: elapsed: 0s, gathering baseline coverage: 15/217 completed
--- FAIL: FuzzReverse (0.13s)
--- FAIL: FuzzReverse (0.00s)
fuzz_tool_test.go:18: str:"𐑄",first:"\x84\x91\x90\xf0",second:"𐑄"
fuzz_tool_test.go:23: Reverse produced invalid UTF-8 string "𐑄" "\x84\x91\x90\xf0"
Failing input written to testdata\fuzz\FuzzReverse\d856c981b6266ba2
To re-run:
go test -run=FuzzReverse/d856c981b6266ba2
=== NAME
FAIL
exit status 1
FAIL golearn/tool_test 0.697sTIP
ファジングテストで失敗したケースは、現在のテストフォルダ下の testdata ディレクトリ下の特定のコーパスファイルに出力されます。例えば、上記の例の
Failing input written to testdata\fuzz\FuzzReverse\d856c981b6266ba2
To re-run:
go test -run=FuzzReverse/d856c981b6266ba2testdata\fuzz\FuzzReverse\d856c981b6266ba2 は出力されるコーパスファイルのパスで、ファイルの内容は以下の通りです。
go test fuzz v1
string("𐑄")今回は合格しなかったことがわかります。理由は文字列が反転後に非 utf8 形式になったためです。したがって、ファジングテストを通じてこの問題点を発見できました。一部の文字は 1 バイト以上を占有するため、バイト単位で反転すると間違いなく文字化けします。したがって、テスト対象のソースコードを以下に修正し、文字列を []rune に変換することで、上記の問題を回避できます。
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}次に、前回のファジングテストで失敗したケースを直接実行します。
$ go test -run=FuzzReverse/d856c981b6266ba2 -v
=== RUN FuzzReverse
=== RUN FuzzReverse/d856c981b6266ba2
fuzz_tool_test.go:18: str:"𐑄",first:"𐑄",second:"𐑄"
--- PASS: FuzzReverse (0.00s)
--- PASS: FuzzReverse/d856c981b6266ba2 (0.00s)
PASS
ok golearn/tool_test 0.033s今回はテストに合格したことがわかります。再度ファジングテストを実行して、まだ問題があるかどうかを確認します。
$ go test -fuzz . -fuzztime 30s -run Fuzz -v
=== RUN FuzzReverse
fuzz: elapsed: 0s, gathering baseline coverage: 0/219 completed
fuzz: minimizing 70-byte failing input file
failure while testing seed corpus entry: FuzzReverse/d97214ce235bfcf5
fuzz: elapsed: 0s, gathering baseline coverage: 2/219 completed
--- FAIL: FuzzReverse (0.15s)
--- FAIL: FuzzReverse (0.00s)
fuzz_tool_test.go:18: str:"\xe4",first:"",second:""
fuzz_tool_test.go:20: before: "\xe4", after: ""
=== NAME
FAIL
exit status 1
FAIL golearn/tool_test 0.184sまたエラーが発生したことがわかります。今回は文字列を 2 回反転後に等しくならない問題です。元の文字は \xe4 で、期待される結果は 4ex\ ですが、結果は文字化けです。以下
func main() {
fmt.Println("\xe4")
fmt.Println([]byte("\xe4"))
fmt.Println([]rune("\xe4"))
fmt.Printf("%q\n", "\xe4")
fmt.Printf("%x\n", "\xe4")
}実行結果は以下の通りです。
[65533]
"\xe4"
e4原因を究明すると、\xe4 は 1 バイトを表しますが、有効な UTF-8 シーケンスではありません(UTF-8 エンコーディングでは \xe4 は 3 バイト文字の開始ですが、後の 2 バイトが不足しています)。[]rune に変換すると、Golang は自動的にそれを単一の Unicode 文字を含む []rune{"\uFFFD"} に変換し、反転後も仍是 []rune{"\uFFFD"} で、string に戻すと該 Unicode 文字は再びその UTF-8 エンコーディング \xef\xbf\xbd に置換されます。したがって、1 つの解決策は、入力された文字列が非 utf8 文字列の場合、直接エラーを返すことです。
func Reverse(s string) (string, error) {
if !utf8.ValidString(s) {
return s, errors.New("input is not valid UTF-8")
}
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r), nil
}テストコードも少し修正する必要があります。
func FuzzReverse(f *testing.F) {
testdata := []string{"hello world!", "nice to meet you", "good bye!"}
for _, data := range testdata {
f.Add(data)
}
f.Fuzz(func(t *testing.T, str string) {
first, err := tool.Reverse(str)
if err != nil {
t.Skip()
}
second, err := tool.Reverse(first)
if err != nil {
t.Skip()
}
t.Logf("str:%q,first:%q,second:%q", str, first, second)
if str != second {
t.Errorf("before: %q, after: %q", str, second)
}
if utf8.ValidString(str) && !utf8.ValidString(first) {
t.Errorf("Reverse produced invalid UTF-8 string %q %q", str, first)
}
})
}反転関数が error を返す場合、テストをスキップします。再度ファジングテストを実行します。
$ go test -fuzz . -fuzztime 30s -run Fuzz -v
=== RUN FuzzReverse
fuzz: elapsed: 0s, gathering baseline coverage: 0/219 completed
fuzz: elapsed: 0s, gathering baseline coverage: 219/219 completed, now fuzzing with 16 workers
fuzz: elapsed: 3s, execs: 895571 (297796/sec), new interesting: 32 (total: 251)
fuzz: elapsed: 6s, execs: 1985543 (363120/sec), new interesting: 37 (total: 256)
fuzz: elapsed: 9s, execs: 3087837 (367225/sec), new interesting: 38 (total: 257)
fuzz: elapsed: 12s, execs: 4090817 (335167/sec), new interesting: 40 (total: 259)
fuzz: elapsed: 15s, execs: 5132580 (346408/sec), new interesting: 44 (total: 263)
fuzz: elapsed: 18s, execs: 6248486 (372185/sec), new interesting: 45 (total: 264)
fuzz: elapsed: 21s, execs: 7366827 (373305/sec), new interesting: 46 (total: 265)
fuzz: elapsed: 24s, execs: 8439803 (358059/sec), new interesting: 47 (total: 266)
fuzz: elapsed: 27s, execs: 9527671 (361408/sec), new interesting: 47 (total: 266)
fuzz: elapsed: 30s, execs: 10569473 (348056/sec), new interesting: 48 (total: 267)
fuzz: elapsed: 30s, execs: 10569473 (0/sec), new interesting: 48 (total: 267)
--- PASS: FuzzReverse (30.16s)
=== NAME
PASS
ok golearn/tool_test 30.789sこれで、より完全なファジングテスト出力ログが得られます。その中のいくつかの概念の説明は以下の通りです。
- elapsed: 1 つのラウンド完了後に経過した時間
- execs: 実行された入力の総数。297796/sec は 1 秒間に何個の入力を示します
- new interesting: テスト中に、コーパスに追加された「面白い」入力の総数。(面白い入力とは、コードカバレッジを既存のコーパスがカバーできる範囲を超えて拡大できる入力を指します。カバレッジ範囲の拡大に伴い、その増加傾向は全体的に鈍化します)
TIP
-fuzztime パラメータで時間を制限しない場合、ファジングテストは永遠に実行され続けます。
サポートされるタイプ
Go Fuzz でサポートされるタイプは以下の通りです。
string,[]byteint,int8,int16,int32/rune,int64uint,uint8/byte,uint16,uint32,uint64float32,float64bool
