🤩

`go test` の33個のフラグを全て解説してみた!!! (サンプルコード付き)

2024/11/12に公開

導入

みなさん、Goでテストを実行したことはありますか?
その時には go test コマンドを実行することがあると思います。

実は go test コマンドには30個以上のフラグが用意されているのをご存知でしょうか??
(ちなみに筆者はそのほとんどを知らなかったです)

このブログでは、そのすべてのフラグの使い方を説明しようと思います!!
とても長いので目次から気になるフラグだけピックアップしてみると読みやすいかと思います!
あるいは「❤️」していただいて備忘録的に見返してもらうのも良いかと思います!

Goのバージョンは以下を参考にしてください。

❯ go version
go version go1.23.1 darwin/arm64

またこのブログでは testing パッケージの使い方やpackageの指定の仕方などに関して、詳細な説明はしません。

フラグを全部見てみる

とりあえずすべてのフラグを見てみます!
以下のコマンドを実行すれば、現時点 (2024/10) で go test が受け付けるすべてのフラグが分かります。

❯ go help testflag

結果は長いのでトグルにしてます。

`go help testflag` の実行結果
The 'go test' command takes both flags that apply to 'go test' itself
and flags that apply to the resulting test binary.

Several of the flags control profiling and write an execution profile
suitable for "go tool pprof"; run "go tool pprof -h" for more
information. The --alloc_space, --alloc_objects, and --show_bytes
options of pprof control how the information is presented.

The following flags are recognized by the 'go test' command and
control the execution of any test:

	-bench regexp
	    Run only those benchmarks matching a regular expression.
	    By default, no benchmarks are run.
	    To run all benchmarks, use '-bench .' or '-bench=.'.
	    The regular expression is split by unbracketed slash (/)
	    characters into a sequence of regular expressions, and each
	    part of a benchmark's identifier must match the corresponding
	    element in the sequence, if any. Possible parents of matches
	    are run with b.N=1 to identify sub-benchmarks. For example,
	    given -bench=X/Y, top-level benchmarks matching X are run
	    with b.N=1 to find any sub-benchmarks matching Y, which are
	    then run in full.

	-benchtime t
	    Run enough iterations of each benchmark to take t, specified
	    as a time.Duration (for example, -benchtime 1h30s).
	    The default is 1 second (1s).
	    The special syntax Nx means to run the benchmark N times
	    (for example, -benchtime 100x).

	-count n
	    Run each test, benchmark, and fuzz seed n times (default 1).
	    If -cpu is set, run n times for each GOMAXPROCS value.
	    Examples are always run once. -count does not apply to
	    fuzz tests matched by -fuzz.

	-cover
	    Enable coverage analysis.
	    Note that because coverage works by annotating the source
	    code before compilation, compilation and test failures with
	    coverage enabled may report line numbers that don't correspond
	    to the original sources.

	-covermode set,count,atomic
	    Set the mode for coverage analysis for the package[s]
	    being tested. The default is "set" unless -race is enabled,
	    in which case it is "atomic".
	    The values:
		set: bool: does this statement run?
		count: int: how many times does this statement run?
		atomic: int: count, but correct in multithreaded tests;
			significantly more expensive.
	    Sets -cover.

	-coverpkg pattern1,pattern2,pattern3
	    Apply coverage analysis in each test to packages matching the patterns.
	    The default is for each test to analyze only the package being tested.
	    See 'go help packages' for a description of package patterns.
	    Sets -cover.

	-cpu 1,2,4
	    Specify a list of GOMAXPROCS values for which the tests, benchmarks or
	    fuzz tests should be executed. The default is the current value
	    of GOMAXPROCS. -cpu does not apply to fuzz tests matched by -fuzz.

	-failfast
	    Do not start new tests after the first test failure.

	-fullpath
	    Show full file names in the error messages.

	-fuzz regexp
	    Run the fuzz test matching the regular expression. When specified,
	    the command line argument must match exactly one package within the
	    main module, and regexp must match exactly one fuzz test within
	    that package. Fuzzing will occur after tests, benchmarks, seed corpora
	    of other fuzz tests, and examples have completed. See the Fuzzing
	    section of the testing package documentation for details.

	-fuzztime t
	    Run enough iterations of the fuzz target during fuzzing to take t,
	    specified as a time.Duration (for example, -fuzztime 1h30s).
		The default is to run forever.
	    The special syntax Nx means to run the fuzz target N times
	    (for example, -fuzztime 1000x).

	-fuzzminimizetime t
	    Run enough iterations of the fuzz target during each minimization
	    attempt to take t, as specified as a time.Duration (for example,
	    -fuzzminimizetime 30s).
		The default is 60s.
	    The special syntax Nx means to run the fuzz target N times
	    (for example, -fuzzminimizetime 100x).

	-json
	    Log verbose output and test results in JSON. This presents the
	    same information as the -v flag in a machine-readable format.

	-list regexp
	    List tests, benchmarks, fuzz tests, or examples matching the regular
	    expression. No tests, benchmarks, fuzz tests, or examples will be run.
	    This will only list top-level tests. No subtest or subbenchmarks will be
	    shown.

	-parallel n
	    Allow parallel execution of test functions that call t.Parallel, and
	    fuzz targets that call t.Parallel when running the seed corpus.
	    The value of this flag is the maximum number of tests to run
	    simultaneously.
	    While fuzzing, the value of this flag is the maximum number of
	    subprocesses that may call the fuzz function simultaneously, regardless of
	    whether T.Parallel is called.
	    By default, -parallel is set to the value of GOMAXPROCS.
	    Setting -parallel to values higher than GOMAXPROCS may cause degraded
	    performance due to CPU contention, especially when fuzzing.
	    Note that -parallel only applies within a single test binary.
	    The 'go test' command may run tests for different packages
	    in parallel as well, according to the setting of the -p flag
	    (see 'go help build').

	-run regexp
	    Run only those tests, examples, and fuzz tests matching the regular
	    expression. For tests, the regular expression is split by unbracketed
	    slash (/) characters into a sequence of regular expressions, and each
	    part of a test's identifier must match the corresponding element in
	    the sequence, if any. Note that possible parents of matches are
	    run too, so that -run=X/Y matches and runs and reports the result
	    of all tests matching X, even those without sub-tests matching Y,
	    because it must run them to look for those sub-tests.
	    See also -skip.

	-short
	    Tell long-running tests to shorten their run time.
	    It is off by default but set during all.bash so that installing
	    the Go tree can run a sanity check but not spend time running
	    exhaustive tests.

	-shuffle off,on,N
	    Randomize the execution order of tests and benchmarks.
	    It is off by default. If -shuffle is set to on, then it will seed
	    the randomizer using the system clock. If -shuffle is set to an
	    integer N, then N will be used as the seed value. In both cases,
	    the seed will be reported for reproducibility.

	-skip regexp
	    Run only those tests, examples, fuzz tests, and benchmarks that
	    do not match the regular expression. Like for -run and -bench,
	    for tests and benchmarks, the regular expression is split by unbracketed
	    slash (/) characters into a sequence of regular expressions, and each
	    part of a test's identifier must match the corresponding element in
	    the sequence, if any.

	-timeout d
	    If a test binary runs longer than duration d, panic.
	    If d is 0, the timeout is disabled.
	    The default is 10 minutes (10m).

	-v
	    Verbose output: log all tests as they are run. Also print all
	    text from Log and Logf calls even if the test succeeds.

	-vet list
	    Configure the invocation of "go vet" during "go test"
	    to use the comma-separated list of vet checks.
	    If list is empty, "go test" runs "go vet" with a curated list of
	    checks believed to be always worth addressing.
	    If list is "off", "go test" does not run "go vet" at all.

The following flags are also recognized by 'go test' and can be used to
profile the tests during execution:

	-benchmem
	    Print memory allocation statistics for benchmarks.
	    Allocations made in C or using C.malloc are not counted.

	-blockprofile block.out
	    Write a goroutine blocking profile to the specified file
	    when all tests are complete.
	    Writes test binary as -c would.

	-blockprofilerate n
	    Control the detail provided in goroutine blocking profiles by
	    calling runtime.SetBlockProfileRate with n.
	    See 'go doc runtime.SetBlockProfileRate'.
	    The profiler aims to sample, on average, one blocking event every
	    n nanoseconds the program spends blocked. By default,
	    if -test.blockprofile is set without this flag, all blocking events
	    are recorded, equivalent to -test.blockprofilerate=1.

	-coverprofile cover.out
	    Write a coverage profile to the file after all tests have passed.
	    Sets -cover.

	-cpuprofile cpu.out
	    Write a CPU profile to the specified file before exiting.
	    Writes test binary as -c would.

	-memprofile mem.out
	    Write an allocation profile to the file after all tests have passed.
	    Writes test binary as -c would.

	-memprofilerate n
	    Enable more precise (and expensive) memory allocation profiles by
	    setting runtime.MemProfileRate. See 'go doc runtime.MemProfileRate'.
	    To profile all memory allocations, use -test.memprofilerate=1.

	-mutexprofile mutex.out
	    Write a mutex contention profile to the specified file
	    when all tests are complete.
	    Writes test binary as -c would.

	-mutexprofilefraction n
	    Sample 1 in n stack traces of goroutines holding a
	    contended mutex.

	-outputdir directory
	    Place output files from profiling in the specified directory,
	    by default the directory in which "go test" is running.

	-trace trace.out
	    Write an execution trace to the specified file before exiting.

Each of these flags is also recognized with an optional 'test.' prefix,
as in -test.v. When invoking the generated test binary (the result of
'go test -c') directly, however, the prefix is mandatory.

The 'go test' command rewrites or removes recognized flags,
as appropriate, both before and after the optional package list,
before invoking the test binary.

For instance, the command

	go test -v -myflag testdata -cpuprofile=prof.out -x

will compile the test binary and then run it as

	pkg.test -test.v -myflag testdata -test.cpuprofile=prof.out

(The -x flag is removed because it applies only to the go command's
execution, not to the test itself.)

The test flags that generate profiles (other than for coverage) also
leave the test binary in pkg.test for use when analyzing the profiles.

When 'go test' runs a test binary, it does so from within the
corresponding package's source code directory. Depending on the test,
it may be necessary to do the same when invoking a generated test
binary directly. Because that directory may be located within the
module cache, which may be read-only and is verified by checksums, the
test must not write to it or any other directory within the module
unless explicitly requested by the user (such as with the -fuzz flag,
which writes failures to testdata/fuzz).

The command-line package list, if present, must appear before any
flag not known to the go test command. Continuing the example above,
the package list would have to appear before -myflag, but could appear
on either side of -v.

When 'go test' runs in package list mode, 'go test' caches successful
package test results to avoid unnecessary repeated running of tests. To
disable test caching, use any test flag or argument other than the
cacheable flags. The idiomatic way to disable test caching explicitly
is to use -count=1.

To keep an argument for a test binary from being interpreted as a
known flag or a package name, use -args (see 'go help test') which
passes the remainder of the command line through to the test binary
uninterpreted and unaltered.

For instance, the command

	go test -v -args -x -v

will compile the test binary and then run it as

	pkg.test -test.v -x -v

Similarly,

	go test -args math

will compile the test binary and then run it as

	pkg.test math

In the first example, the -x and the second -v are passed through to the
test binary unchanged and with no effect on the go command itself.
In the second example, the argument math is passed through to the test
binary, instead of being interpreted as the package list.

フラグの一覧をまとめてみました!

  1. -bench
  2. -benchtime
  3. -count
  4. -cover
  5. -covermode
  6. -coverpkg
  7. -cpu
  8. -failfast
  9. -fullpath
  10. -fuzz
  11. -fuzztime
  12. -fuzzminimizetime
  13. -json
  14. -list
  15. -parallel
  16. -run
  17. -short
  18. -shuffle
  19. -skip
  20. -timeout
  21. -v
  22. -vet
  23. -benchmem
  24. -blockprofile
  25. -blockprofilerate
  26. -coverprofile
  27. -cpuprofile
  28. -memprofile
  29. -memprofilerate
  30. -mutexprofile
  31. -mutexprofilefraction
  32. -outputdir
  33. -trace

全部で33個もあります!

フラグは以下の2種類に分類できるようです。

  • テストの実行を制御するフラグ(1〜22まで)
  • テストの実行中にテスト自体をプロファイルするフラグ(23〜33まで)

実際にそれぞれのフラグを見ていきましょう!

まずはテストの実行についての記述を見てみる

フラグの解説の前にテストの実行の詳細を見てみます(興味のない方はスキップしてもらっても構いません)。
具体的には「Each of these flags is also recognized with ...」の部分の内容を詳しくみていきたいと思います。

サマリとしては以下のようになります。

  1. テストのバイナリを go test -c で生成することができる
  2. テストのバイナリによるテスト実行をする場合、各フラグは test. のprefixをつける必要がある(たとえば -v-test.v として指定する必要がある)
  3. プロファイルを生成するテストフラグもテストのバイナリを残す(残されたバイナリはプロファイルを分析するときに使用されます)
  4. go test は成功したテストをキャッシュし、余計なテストが実行されないようになっている

go help test をみてみると、go test コマンド自体が受け付けるフラグの詳細をみることができます。

サンプルコードについての補足

今回、テストの挙動がよりわかりやすくなるようにフラグを付与してテストを実行しています。
とくに記載のない限りは、以下のコードでテスト行っています。

  • main.go
package main

import "slices"

func Sum(nums []int) int {
	var sum int
	for v := range slices.Values(nums) {
		sum += v
	}
	return sum
}
  • main_test.go(ベンチマーク)
package main

import "testing"

func BenchmarkTest(b *testing.B) {
	nums := []int{1, 2, 3, 4, 5}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = Sum(nums)
	}
}
  • main_test.go(ベンチマーク以外)
package main

import "testing"

func TestSum(t *testing.T) {
	nums := []int{1, 2, 3, 4, 5}

	expected := 15
	if acutal := Sum(nums); acutal != expected {
		t.Errorf("Expected %d but got %d", expected, acutal)
	}
}

テストの実行を制御するフラグ

1. -bench フラグ

シグネチャと詳細は以下になります。

	-bench regexp
	    Run only those benchmarks matching a regular expression.
	    By default, no benchmarks are run.
	    To run all benchmarks, use '-bench .' or '-bench=.'.
	    The regular expression is split by unbracketed slash (/)
	    characters into a sequence of regular expressions, and each
	    part of a benchmark's identifier must match the corresponding
	    element in the sequence, if any. Possible parents of matches
	    are run with b.N=1 to identify sub-benchmarks. For example,
	    given -bench=X/Y, top-level benchmarks matching X are run
	    with b.N=1 to find any sub-benchmarks matching Y, which are
	    then run in full.

-bench フラグはベンチマークのテストを実行するためのフラグです。デフォルトではベンチマークのテストは実行されず、-bench .-bench=. のフラグを付与するとベンチマークのテストが実行されます。たとえば、-bench=X/Yを指定すると、Xにマッチするトップレベルのベンチマークがb.N=1で実行され、Yにマッチするサブベンチマークが見つかり、それがフルで実行されます。

実際に確かめてみましょう。
冒頭に記述したベンチマークの main_test.go を使用します。

❯ go test .
ok      github.com/k3forx/testflag      0.504s [no tests to run]

. のみだと「no tests to run」という表示になります。

-bench を指定して実行してみると、ちゃんとベンチマークが実行されました!

❯ go test -bench .
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkTest-11        310160636                3.872 ns/op
PASS
ok      github.com/k3forx/testflag      1.836s

サブテストについては https://go.dev/blog/subtests が参考になります。

2. -benchtime フラグ

シグネチャと詳細は以下になります。

	-benchtime t
	    Run enough iterations of each benchmark to take t, specified
	    as a time.Duration (for example, -benchtime 1h30s).
	    The default is 1 second (1s).
	    The special syntax Nx means to run the benchmark N times
	    (for example, -benchtime 100x).

-benchtimeベンチマークのテストを実行する時間を指定するようです。ただし、このフラグには特別なシンタックスがあり Nx と記述された際には、N 回だけベンチマークテストを実行するようになっているようです。

実際に見てみましょう。
ベンチマークを実行してみます。

❯ go test -benchtime 1s -bench .
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11         314220164                3.876 ns/op
PASS
ok      github.com/k3forx/testflag      1.750s

❯ go test -benchtime 0.5s -bench .
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11         153570301                3.858 ns/op
PASS
ok      github.com/k3forx/testflag      1.136s

0.5s を指定した際には反復回数が 153570301 になっており、1s314220164 のおよそ半分になっています。実行時間が半分になったので、反復回数も半分になったのだと考えられますね!

Nx の方はどうでしょうか?

❯ go test -benchtime 100x -bench .
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11              100                12.50 ns/op
PASS
ok      github.com/k3forx/testflag      0.163s

確かに100回しかテストが実行されていないようです!

3. -count フラグ

シグネチャと詳細は以下になります。

	-count n
	    Run each test, benchmark, and fuzz seed n times (default 1).
	    If -cpu is set, run n times for each GOMAXPROCS value.
	    Examples are always run once. -count does not apply to
	    fuzz tests matched by -fuzz.

-count フラグはテストを実行する回数を指定するフラグになります。-cpu フラグがある場合、それぞれの GOMAXPROCS に対して n 回テストを実行するようです (-cpu フラグと GOMAXPROCS については後述します!)。

実際に見てみましょう!

❯ go test . -count=1
ok      github.com/k3forx/testflag      0.165s

❯ go test . -count=2
ok      github.com/k3forx/testflag      0.166s

簡単なテストなので、1回分多く実行した差がほとんどみられないですね。まだ解説していないのですが、-v のフラグをつけるとテストの実行ログを確認できるので、そのオプションをつけて再度実行してみます。

❯ go test . -count=1 -v
=== RUN   TestSum
--- PASS: TestSum (0.00s)
PASS
ok      github.com/k3forx/testflag      0.197s

❯ go test . -count=2 -v
=== RUN   TestSum
--- PASS: TestSum (0.00s)
=== RUN   TestSum
--- PASS: TestSum (0.00s)
PASS
ok      github.com/k3forx/testflag      0.162s

確かに -count で指定された回数分のテストが実行されていそうでした!


余談ですが、この -count=1 フラグはキャッシュを無効にするフラグとして使われることが多い印象です。go help testflag の記述の中にも以下のような文章があります。

When 'go test' runs in package list mode, 'go test' caches successful
package test results to avoid unnecessary repeated running of tests. To
disable test caching, use any test flag or argument other than the
cacheable flags. The idiomatic way to disable test caching explicitly
is to use -count=1.

4. -cover フラグ

シグネチャと詳細は以下になります。

	-cover
	    Enable coverage analysis.
	    Note that because coverage works by annotating the source
	    code before compilation, compilation and test failures with
	    coverage enabled may report line numbers that don't correspond
	    to the original sources.

-cover はカバレッジ分析を有効にするフラグです。注意事項として「カバレッジはコンパイル前にソースコードに注釈を付けることで機能するため、カバレッジを有効にしてコンパイルやテストに失敗すると、元のソースと一致しない行番号が報告される可能性がある」と補足があります。

実際に試してみます!

❯ go test . -cover
ok      github.com/k3forx/testflag      0.203s  coverage: 100.0% of statements

5. -covermode

シグネチャと詳細は以下になります。

	-covermode set,count,atomic
	    Set the mode for coverage analysis for the package[s]
	    being tested. The default is "set" unless -race is enabled,
	    in which case it is "atomic".
	    The values:
		set: bool: does this statement run?
		count: int: how many times does this statement run?
		atomic: int: count, but correct in multithreaded tests;
			significantly more expensive.
	    Sets -cover.

-covermode フラグはカバレッジ分析に用いられるモードを指定するフラグです。デフォルトは set になっていますが、 -race フラグが指定されていると atomic になるようです。「Sets -cover」と記述してあるように -cover の機能も含むフラグとなっています!

指定可能な値は以下のようになっています。

  • set: 実行されたかどうか(デフォルト)
  • count: 何回実行されたか
  • atomic: マルチスレッドでのテストで正確に何回実行されたか(実行コストは増える)

実際に指定してみるとわかるのですが、 -covermode の値を変えてテストを実行してみてもほとんど差分は分かりません。-coverprofile フラグと使用されることが多いので、あとで差分を詳しくみてみたいと思います!

6. -coverpkg

シグネチャと詳細は以下になります。

        -coverpkg pattern1,pattern2,pattern3
            Apply coverage analysis in each test to packages matching the patterns.
            The default is for each test to analyze only the package being tested.
            See 'go help packages' for a description of package patterns.
            Sets -cover.

-coverpkg フラグは各テストで、パターンにマッチするパッケージに対してカバレッジ解析するためのフラグです。デフォルトでは、各テストはテスト対象のパッケージのみを分析します。
また -cover フラグをカバーしているので、-coverpkg を指定している場合は -cover フラグが不要になります。

と説明しましたが、実際のコードで説明したほうがわかりやすいと思うので、具体例を見ていきたいと思います。


今回は以下のようなディレクトリ構成でテストを実行することを考えてみます。それぞれのテストコードは同じパッケージのテストしか記述していないとします。実際のコードやテストコードはとくに大事でないので、ここでは記述していません。

❯ tree .
.
├── go.mod
├── main.go
├── main_test.go # main.goのテストのみが記述されている
└── pkg
    ├── a
    │   ├── a.go
    │   └── a_test.go # a.goのテストのみが記述されている
    └── b
        ├── b.go
        └── b_test.go # b.goのテストのみが記述されている

4 directories, 7 files

-coverpkg フラグなしの場合と、-coverpkg=. フラグ、-coverpkg=./... フラグでどのように差分が出るかみてみます!

# `-coverpkg` フラグなし
❯ go test ./... -cover
ok      github.com/k3forx/testflag      0.960s  coverage: 100.0% of statements
ok      github.com/k3forx/testflag/pkg/a        1.337s  coverage: 100.0% of statements
ok      github.com/k3forx/testflag/pkg/b        1.146s  coverage: 100.0% of statements

# `-coverpkg=.` フラグ
❯ go test ./... -coverpkg=.
ok      github.com/k3forx/testflag      0.188s  coverage: 100.0% of statements in .
ok      github.com/k3forx/testflag/pkg/a        0.360s  coverage: 0.0% of statements in .
ok      github.com/k3forx/testflag/pkg/b        0.546s  coverage: 0.0% of statements in .

# `-coverpkg=./...` フラグ
❯ go test ./... -coverpkg=./...
ok      github.com/k3forx/testflag      0.232s  coverage: 33.3% of statements in ./...
ok      github.com/k3forx/testflag/pkg/a        0.422s  coverage: 33.3% of statements in ./...
ok      github.com/k3forx/testflag/pkg/b        0.600s  coverage: 33.3% of statements in ./...

-coverpkg で指定されたパッケージに対して、実行されたテストがどれだけのカバレッジを持っているかを示しているようです。

たとえば -coverpkg=. を指定した場合、テストを実行した階層と同じ階層にあるコードに対してどれだけのカバレッジがあるかが計測されます。pkg/apkg/b には main.go のテストコードが記述されていないので、coverage: 0.0% of statements in . と表示されます。

では、-coverpkg=./pkg/a と指定した場合はどうなるでしょうか?
ここまでの説明をもとに考えると pkg/a の部分には coverage: 100% of ... と表示され、それ以外の部分は coverage: 0.0% of ... と表示されることが予想できますね。

実際に試してみます!

❯ go test ./... -coverpkg=./pkg/a
ok      github.com/k3forx/testflag      0.201s  coverage: 0.0% of statements in ./pkg/a
ok      github.com/k3forx/testflag/pkg/a        0.384s  coverage: 100.0% of statements in ./pkg/a
ok      github.com/k3forx/testflag/pkg/b        0.565s  coverage: 0.0% of statements in ./pkg/a

予想通りの結果になりました!

余談ですが、 ./... の記述については go help packages を参照するとより詳細な説明が得られます。

7. -cpu

シグネチャと詳細は以下になります。

        -cpu 1,2,4
            Specify a list of GOMAXPROCS values for which the tests, benchmarks or
            fuzz tests should be executed. The default is the current value
            of GOMAXPROCS. -cpu does not apply to fuzz tests matched by -fuzz.

-cpu フラグは 1,2,4 などの数字のリストを指定でき、実行されるべきテストやベンチマークに対する GOMAXPROCS のリストを指定できます。デフォルトの値は GOMAXPROCS の現在の値になります。

実際に試してみます!
異なるCPU数で実行して、ロックや同期が期待通りに機能するか確認するためのテストを書いてみます。

  • main_test.go
package main

import (
	"sync"
	"testing"
)

func parallelSum(values []int) int {
	var (
		sum int
		mu  sync.Mutex
		wg  sync.WaitGroup
	)

	for _, v := range values {
		wg.Add(1)
		go func(val int) {
			defer wg.Done()
			mu.Lock()
			sum += val
			mu.Unlock()
		}(v)
	}
	wg.Wait()
	return sum
}

func TestParallelSum(t *testing.T) {
	values := []int{1, 2, 3, 4, 5}
	result := parallelSum(values)
	expected := 15

	if result != expected {
		t.Errorf("Expected %d, got %d", expected, result)
	}
}
❯ go test . -cpu 1,12 -v
=== RUN   TestParallelSum
--- PASS: TestParallelSum (0.00s)
=== RUN   TestParallelSum
--- PASS: TestParallelSum (0.00s)
PASS
ok      github.com/k3forx/testflag      0.164s

ちゃんと動作していることが確かめられました!
並列性を考慮したテストが各CPU設定で正常に動作するかを確認するために使えそうです。

8. -failfast

シグネチャと詳細は以下になります。

        -failfast
            Do not start new tests after the first test failure.

-failfast フラグは最初のテストが失敗したら、次のテストを実行させないようにするためのフラグです。

実際に見てみましょう!

package main

import (
	"testing"
)

func TestSum(t *testing.T) {
	cases := map[string]struct {
		nums     []int
		expected int
	}{
		"ok_not_zero": {
			nums:     []int{1, 2, 3, 4, 5},
			expected: 15,
		},
		"ng_not_zero": {
			nums:     []int{1, 2, 3, 4, 5},
			expected: 0,
		},
		"ok_zero": {
			nums:     []int{},
			expected: 0,
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			if acutal := Sum(c.nums); acutal != c.expected {
				t.Errorf("Expected %d but got %d", c.expected, acutal)
			}
		})
	}
}

とくに -failfast フラグを指定さずに実行してみます。

❯ go test . -v
=== RUN   TestSum
=== RUN   TestSum/ok_not_zero
=== RUN   TestSum/ng_not_zero
    main_test.go:29: Expected 0 but got 15
=== RUN   TestSum/ok_zero
--- FAIL: TestSum (0.00s)
    --- PASS: TestSum/ok_not_zero (0.00s)
    --- FAIL: TestSum/ng_not_zero (0.00s)
    --- PASS: TestSum/ok_zero (0.00s)
FAIL
FAIL    github.com/k3forx/testflag      0.161s
FAIL

すべてのテストケースが実行されていますね!

次は -failfast フラグを指定してみます。

❯ go test . -v -failfast
=== RUN   TestSum
=== RUN   TestSum/ok_not_zero
=== RUN   TestSum/ng_not_zero
    main_test.go:29: Expected 0 but got 15
--- FAIL: TestSum (0.00s)
    --- PASS: TestSum/ok_not_zero (0.00s)
    --- FAIL: TestSum/ng_not_zero (0.00s)
FAIL
FAIL    github.com/k3forx/testflag      0.165s
FAIL

先ほどまで実行されていた ok_zero のテストケースが実行されてません。-failfast フラグを指定すると失敗したテスト以降のテストは実行されないことが確かめられました!

9. -fullpath

シグネチャと詳細は以下になります。

        -fullpath
            Show full file names in the error messages.

-fullpath フラグを指定すればエラーメッセージの中のファイル名をフルパスで表示できます。

実際に確かめてみます!

フラグをつけて実行すると、以下のように失敗したテストの部分がフルパスで表示されていました!

❯ go test . -fullpath
--- FAIL: TestSum (0.00s)
    /Users/XXX/repos/scrap/testflag/main_test.go:12: Expected 0 but got 15
FAIL
FAIL    github.com/k3forx/testflag      0.163s
FAIL

10. -fuzz

シグネチャと詳細は以下になります。

        -fuzz regexp
            Run the fuzz test matching the regular expression. When specified,
            the command line argument must match exactly one package within the
            main module, and regexp must match exactly one fuzz test within
            that package. Fuzzing will occur after tests, benchmarks, seed corpora
            of other fuzz tests, and examples have completed. See the Fuzzing
            section of the testing package documentation for details.

-fuzz フラグは正規表現にマッチしたfuzzテストを実行するためのフラグです。このフラグが指定されたとき、コマンドラインの引数はmainモジュール内の1つのパッケージにマッチしなければならず、かつ、正規表現はそのパッケージ内にある1つのfuzzテストとマッチしなければなりません。また、fuzzテストはテスト、ベンチマーク、seed corpora、他のfuzzテストとexampleテストが終了したのちに実行されます。

fuzzテストに関しては以下の公式ドキュメントやチュートリアルを読んでいただくと良いと思います。

https://pkg.go.dev/testing#hdr-Fuzzing

https://go.dev/doc/tutorial/fuzz

実際に試してみます!

文字列を逆転させる Reverse 関数を実装してみました。日本語でも問題ないように []rune を使っているので、実装自体はできているように見えます!

  • main.go
package main

import "slices"

func Reverse(str string) string {
	r := []rune(str)
	slices.Reverse(r)
	return string(r)
}
  • main_test.go
package main

import "testing"

func TestReverse(t *testing.T) {
        cases := map[string]struct {
                str string
        }{
                "empty": {
                        str: "",
                },
                "success_abc": {
                        str: "abc",
                },
                "success_123": {
                        str: "123",
                },
                "success_あいう": {
                        str: "あいう",
                },
        }

        for name, c := range cases {
                t.Run(name, func(t *testing.T) {
                        original := Reverse(Reverse(c.str))
                        if original != c.str {
                                t.Errorf("'%s' should be '%s'", original, c.str)
                        }
                })
        }
}

まずは通常のテストを実行して、実装が問題ないかみてみます。

❯ go test .
ok      github.com/k3forx/fuzztest      0.324s

問題なさそうですね!

続いて、以下のようなfuzzテストを main_test.go に追加して、fuzzテストを実行してみます。

func FuzzReverse(f *testing.F) {
	for _, seed := range []string{"", "abc", "123"} {
		f.Add(seed)
	}
	f.Fuzz(func(t *testing.T, str string) {
		original := Reverse(Reverse(str))
		if original != str {
			t.Errorf("'%s' should be '%s'", original, str)
		}
	})
}

実行結果は以下のようになりました。今の実装に考慮漏れがありそうですね!

また、フラグの説明通り、通常のテストが実行されたのちにfuzzテストが実行されていることも確かめられました!

❯ go test -fuzz . -v
=== RUN   TestReverse
=== RUN   TestReverse/empty
=== RUN   TestReverse/success_abc
=== RUN   TestReverse/success_123
=== RUN   TestReverse/success_あいう
--- PASS: TestReverse (0.00s)
    --- PASS: TestReverse/empty (0.00s)
    --- PASS: TestReverse/success_abc (0.00s)
    --- PASS: TestReverse/success_123 (0.00s)
    --- PASS: TestReverse/success_あいう (0.00s)
=== RUN   FuzzReverse
fuzz: elapsed: 0s, gathering baseline coverage: 0/6 completed
failure while testing seed corpus entry: FuzzReverse/15bd0ea6a71e1138
fuzz: elapsed: 0s, gathering baseline coverage: 3/6 completed
--- FAIL: FuzzReverse (0.04s)
    --- FAIL: FuzzReverse (0.00s)
        main_test.go:12: '�' should be '�'

=== NAME
FAIL
exit status 1
FAIL    github.com/k3forx/fuzztest      0.419s

ちなみにfuzzテストに失敗した場合は、その時のログが testdata/fuzz/${テスト名} ディレクトリ配下に追加されます。

11. -fuzztime

シグネチャと詳細は以下になります。

        -fuzztime t
            Run enough iterations of the fuzz target during fuzzing to take t,
            specified as a time.Duration (for example, -fuzztime 1h30s).
                The default is to run forever.
            The special syntax Nx means to run the fuzz target N times
            (for example, -fuzztime 1000x).

-fuzztime フラグはfuzzテストを実行する時間を指定するためのフラグです。time.Duration の形式で指定します。デフォルトだと無限に実行されます。Nx という特別なsyntaxが用意されており、fuzzテストを N 回実行するフラグになっています。

実際に試してみます!
fuzzテストが無限に続くような簡単な関数を実装してみました。

  • main.go
package main

func DoNothing(str string) string {
	return str
}
  • main_test.go
package main

import "testing"

func FuzzDoNothing(f *testing.F) {
	for _, seed := range []string{"", "abc", "123"} {
		f.Add(seed)
	}
	f.Fuzz(func(t *testing.T, str string) {
		original := DoNothing(str)
		if original != str {
			t.Errorf("'%s' should be '%s'", original, str)
		}
	})
}

-fuzztime フラグを指定しないと永遠にfuzzテストが実行されます(以下のログはCtrl + Cで中断したときのログになります)。

❯ go test -fuzz .
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 16 workers
fuzz: elapsed: 3s, execs: 740969 (246985/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 6s, execs: 1592199 (283653/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 9s, execs: 2467434 (291831/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 12s, execs: 3239115 (257238/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 15s, execs: 3992998 (251284/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 18s, execs: 4801640 (269543/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 21s, execs: 5587572 (261988/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 24s, execs: 6394724 (269043/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 27s, execs: 7180623 (261970/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 30s, execs: 7967698 (262315/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 33s, execs: 8736557 (256317/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 36s, execs: 9526655 (263375/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 39s, execs: 10309036 (260783/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 42s, execs: 11076178 (255721/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 45s, execs: 11855857 (259882/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 48s, execs: 12626839 (257008/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 51s, execs: 13369611 (247573/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 54s, execs: 14134283 (254833/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 57s, execs: 14905019 (256990/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 1m0s, execs: 15660974 (251980/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 1m3s, execs: 16397227 (245348/sec), new interesting: 0 (total: 3)
^Cfuzz: elapsed: 1m5s, execs: 16802788 (233700/sec), new interesting: 0 (total: 3)
PASS
ok      github.com/k3forx/fuzztest      65.084s

次は -fuzztime フラグを指定して実行してみます。

❯ go test -fuzz . -fuzztime 3s
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 16 workers
fuzz: elapsed: 3s, execs: 868136 (289372/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 3s, execs: 868136 (0/sec), new interesting: 0 (total: 3)
PASS
ok      github.com/k3forx/fuzztest      3.461s

確かに3秒でfuzzテストが終了していますね!

12. -fuzzminimizetime

シグネチャと詳細は以下になります。

        -fuzzminimizetime t
            Run enough iterations of the fuzz target during each minimization
            attempt to take t, as specified as a time.Duration (for example,
            -fuzzminimizetime 30s).
                The default is 60s.
            The special syntax Nx means to run the fuzz target N times
            (for example, -fuzzminimizetime 100x).

-fuzzminimizetime フラグは各最小化試行中にfuzzターゲットのイテレーションを time.Duration として指定される t の時間だけ実行するためのフラグです。特別なsyntaxがあり、Nx と指定されている場合は N 回実行するようになります。

「最小化」というのはFuzzテストの一般的な考えのことのようです。
テストに失敗するデータを見つけた際にそのデータ量を限りなく小さくすることを「最小化」と言っているようです。この最小化によってデバッグを容易にする目的のようです。詳細はGo Fuzzingを参照してください。

つまり、-fuzzminimizetime フラグはその最小化を行うための時間を指定するためのフラグだと言えます。

実際に試してみます!

  • main.go
package main

import "slices"

func Reverse(str string) string {
	r := []rune(str)
	slices.Reverse(r)
	return string(r)
}
  • main_test.go
package main

import (
	"testing"
)

func FuzzReverse(f *testing.F) {
	for _, seed := range []string{"", "abc", "123"} {
		f.Add(seed)
	}
	f.Fuzz(func(t *testing.T, str string) {
		original := Reverse(Reverse(str))
		if original != str {
			t.Errorf("'%s' should be '%s'", original, str)
		}
	})
}

-fuzzminimizetime フラグとして 0 を指定してみて、失敗したケースを見てみます。

❯ go test -fuzz . -fuzzminimizetime=0s
fuzz: elapsed: 0s, gathering baseline coverage: 0/4 completed
fuzz: elapsed: 0s, gathering baseline coverage: 4/4 completed, now fuzzing with 11 workers
fuzz: elapsed: 0s, execs: 6 (263/sec), new interesting: 0 (total: 4)
--- FAIL: FuzzReverse (0.03s)
    --- FAIL: FuzzReverse (0.00s)
        main_test.go:14: '
                          b�' should be '
                                         b�'

    Failing input written to testdata/fuzz/FuzzReverse/e5eba58a36d29375
    To re-run:
    go test -run=FuzzReverse/e5eba58a36d29375
FAIL
exit status 1
FAIL    github.com/k3forx/testflag      0.200s

❯ cat testdata/fuzz/FuzzReverse/e5eba58a36d29375
go test fuzz v1
string("\fb\x90")

\fb\x90 が失敗した入力として記録されています。

続いて、-fuzzminimizetime フラグとして 100s を指定し、失敗した入力がどう変化するかみてみます。

# 再度Fuzzテストを行おうとすると失敗したデータから始まってしまうので一旦削除rm testdata/fuzz/FuzzReverse/e5eba58a36d29375

❯ go test -fuzz . -fuzzminimizetime=100s
fuzz: elapsed: 0s, gathering baseline coverage: 0/4 completed
fuzz: elapsed: 0s, gathering baseline coverage: 4/4 completed, now fuzzing with 11 workers
fuzz: minimizing 45-byte failing input file
fuzz: elapsed: 0s, minimizing
--- FAIL: FuzzReverse (0.02s)
    --- FAIL: FuzzReverse (0.00s)
        main_test.go:14: '�' should be '�'

    Failing input written to testdata/fuzz/FuzzReverse/e504fa449c34beb2
    To re-run:
    go test -run=FuzzReverse/e504fa449c34beb2
FAIL
exit status 1
FAIL    github.com/k3forx/testflag      0.192s

❯ cat testdata/fuzz/FuzzReverse/e504fa449c34beb2
go test fuzz v1
string("\xc6")

\xc6 が失敗データとして記録されており、確かに先ほどの \fb\x90 よりデータ量として小さくなっていることが確かめられました!

13. -json

シグネチャと詳細は以下になります。

        -json
            Log verbose output and test results in JSON. This presents the
            same information as the -v flag in a machine-readable format.

-json フラグを指定すると、テストの詳細なログと結果をJSON形式で表示できます。これは、テストのログを見やすく表示する -v フラグと同じ情報になります。

実際に見てみましょう!

package main

import (
	"testing"
)

func TestSum(t *testing.T) {
	nums := []int{1, 2, 3, 4, 5}

	expected := 15
	if acutal := Sum(nums); acutal != expected {
		t.Errorf("Expected %d but got %d", expected, acutal)
	}
}

-json フラグを付与してテストを実行してみます。

❯ go test . -json | jq
{
  "Time": "2024-10-22T21:51:09.327997+09:00",
  "Action": "start",
  "Package": "github.com/k3forx/testflag"
}
{
  "Time": "2024-10-22T21:51:09.328047+09:00",
  "Action": "run",
  "Package": "github.com/k3forx/testflag",
  "Test": "TestSum"
}
{
  "Time": "2024-10-22T21:51:09.328049+09:00",
  "Action": "output",
  "Package": "github.com/k3forx/testflag",
  "Test": "TestSum",
  "Output": "=== RUN   TestSum\n"
}
{
  "Time": "2024-10-22T21:51:09.328054+09:00",
  "Action": "output",
  "Package": "github.com/k3forx/testflag",
  "Test": "TestSum",
  "Output": "--- PASS: TestSum (0.00s)\n"
}
{
  "Time": "2024-10-22T21:51:09.328055+09:00",
  "Action": "pass",
  "Package": "github.com/k3forx/testflag",
  "Test": "TestSum",
  "Elapsed": 0
}
{
  "Time": "2024-10-22T21:51:09.328058+09:00",
  "Action": "output",
  "Package": "github.com/k3forx/testflag",
  "Output": "PASS\n"
}
{
  "Time": "2024-10-22T21:51:09.328059+09:00",
  "Action": "output",
  "Package": "github.com/k3forx/testflag",
  "Output": "ok  \tgithub.com/k3forx/testflag\t(cached)\n"
}
{
  "Time": "2024-10-22T21:51:09.328063+09:00",
  "Action": "pass",
  "Package": "github.com/k3forx/testflag",
  "Elapsed": 0
}

確かにJSON形式でテストのログと結果が表示されていました!

14. -list

シグネチャと詳細は以下になります。

        -list regexp
            List tests, benchmarks, fuzz tests, or examples matching the regular
            expression. No tests, benchmarks, fuzz tests, or examples will be run.
            This will only list top-level tests. No subtest or subbenchmarks will be
            shown.

-list フラグを指定すると、指定された正規表現にマッチしたテストやベンチマーク、fuzzテスト、exampleテストを表示できます。どのテストも実行されません。このフラグはトップレベルのテストのみに適用され、サブテストやサブベンチマークテストは表示されないようになっています。

実際に確かめてみます!

TestSum というテスト関数を用意して挙動を確かめてみます。正規表現なので Sum, ^TestSum を指定すると TestSum が表示され、^SuTest$ だと表示されないことが予想されます。

❯ go test . -list Sum
TestSum
ok      github.com/k3forx/testflag      0.246s

❯ go test . -list ^TestSum
TestSum
ok      github.com/k3forx/testflag      0.159s

❯ go test . -list ^Su
ok      github.com/k3forx/testflag      0.162s

❯ go test . -list Test$
ok      github.com/k3forx/testflag      0.163s

確かに正規表現にマッチしたテスト名を取得できました!

15. -parallel

シグネチャと詳細は以下になります。

        -parallel n
            Allow parallel execution of test functions that call t.Parallel, and
            fuzz targets that call t.Parallel when running the seed corpus.
            The value of this flag is the maximum number of tests to run
            simultaneously.
            While fuzzing, the value of this flag is the maximum number of
            subprocesses that may call the fuzz function simultaneously, regardless of
            whether T.Parallel is called.
            By default, -parallel is set to the value of GOMAXPROCS.
            Setting -parallel to values higher than GOMAXPROCS may cause degraded
            performance due to CPU contention, especially when fuzzing.
            Note that -parallel only applies within a single test binary.
            The 'go test' command may run tests for different packages
            in parallel as well, according to the setting of the -p flag
            (see 'go help build').

-parallel フラグは t.Parallel を呼んでいるテスト関数の並列実行を許可するためのフラグです。このフラグの値は同時に実行するテストの最大数を指定するものです。注意点として、GOMAXPROCS より大きい値を指定するとパフォーマンスに影響が出ることと、このフラグは1つのテストバイナリの中でしか有効にならないことの2つが挙げられています。

ここで気になるのは

  1. テストのバイナリはどの単位で生成されるのか?
  2. テストのバイナリ同士を並列に実行するためにはどうすれば良いのか?

ということだと思います。

2つ目の質問に対する回答は go help testflag の中にヒントが隠されており、-p フラグが並列性を制御するためのフラグになります。実際に go help build を実行すると、以下のような説明が得られます。

        -p n
                the number of programs, such as build commands or
                test binaries, that can be run in parallel.
                The default is GOMAXPROCS, normally the number of CPUs available.

「the number of programs, such as build commands or test binaries, that can be run in parallel.」とあるように、テストのバイナリを並列に実行するためには -p フラグを使用すれば良いことがわかります。


最初の問いについて考えてみます。

わかりやすいように以下のようなディレクトリ構成で、テストバイナリを生成してみます。

❯ tree .
.
├── go.mod
├── main.go
├── main_test.go // package main
└── pkg
    ├── a
    │   └── a_test.go // package a_test
    └── b
        └── b_test.go // package b_test

4 directories, 5 files

テストバイナリは -c フラグで生成できます。

❯ go test -c ./...

❯ ls
a.test  b.test  go.mod  main.go  main_test.go  pkg  testflag.test

a.test, b.test, testflag.test の3つのテストバイナリが生成されており、package単位で生成されていそうということがわかると思います。


ある程度挙動がわかってきたので実際に試してみます!
先ほどのディレクトリ構成のテストを以下のように実装してみます。
とく何をしているわけでもなく、どのテストケースでも time.Sleep(3 * time.Second) を呼び出すようにしているだけです。またサブテストは t.Parallel を許可しています。

  • main_test.go
package b_test

import (
	"testing"
	"time"
)

func TestB(t *testing.T) {
	cases := map[string]struct {
		num int
	}{
		"case1": {num: 1},
		"case2": {num: 2},
		"case3": {num: 3},
	}

	for name := range cases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			time.Sleep(3 * time.Second)
			t.Logf("%s done at %v", t.Name(), time.Now())
		})
	}
}
  • a_test.go
package a_test

import (
	"testing"
	"time"
)

func TestA(t *testing.T) {
	cases := map[string]struct {
		num int
	}{
		"case1": {num: 1},
		"case2": {num: 2},
		"case3": {num: 3},
	}

	for name := range cases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			time.Sleep(3 * time.Second)
			t.Logf("%s done at %v", t.Name(), time.Now())
		})
	}
}
  • b_test.go
package b_test

import (
	"testing"
	"time"
)

func TestB(t *testing.T) {
	cases := map[string]struct {
		num int
	}{
		"case1": {num: 1},
		"case2": {num: 2},
		"case3": {num: 3},
	}

	for name := range cases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			time.Sleep(3 * time.Second)
			t.Logf("%s done at %v", t.Name(), time.Now())
		})
	}
}

-parallel フラグも -p フラグもどちらもデフォルトの数が GOMAXPROCS になっています。わかりやすいようにまずは 「パッケージごとに実行し、同一パッケージ内は並列処理を行う」 という条件のもとでテストを実行してみます。

-p フラグがパッケージ単位の並列を指定し、-parallel フラグが同一パッケージ内の並列度を指定するので、-p 1 -parallel 2 としてみます。

❯ go test ./... -v -parallel 2 -p 1 | grep _test.go
    main_test.go:22: TestMain/case3 done at 2024-10-30 22:10:15.949223 +0900 JST m=+3.001105918
    main_test.go:22: TestMain/case2 done at 2024-10-30 22:10:15.949207 +0900 JST m=+3.001090168
    main_test.go:22: TestMain/case1 done at 2024-10-30 22:10:18.950911 +0900 JST m=+6.002810251
    a_test.go:22: TestA/case2 done at 2024-10-30 22:10:22.21671 +0900 JST m=+3.001957043
    a_test.go:22: TestA/case1 done at 2024-10-30 22:10:22.216719 +0900 JST m=+3.001966335
    a_test.go:22: TestA/case3 done at 2024-10-30 22:10:25.218075 +0900 JST m=+6.003337376
    b_test.go:22: TestB/case1 done at 2024-10-30 22:10:28.481415 +0900 JST m=+3.001872084
    b_test.go:22: TestB/case3 done at 2024-10-30 22:10:28.481405 +0900 JST m=+3.001861793
    b_test.go:22: TestB/case2 done at 2024-10-30 22:10:31.48306 +0900 JST m=+6.003532501

確かに意図した挙動になっていそうですね!
どのパッケージも同時刻に終了したサブテストが2つあり、最後の1つのサブテストが終わってから、次のパッケージのテストが実行されています!

次に -p 2 -parallel 2 として実行してみます!

❯ go test ./... -count=1 -v -parallel 2 -p 2 | grep _test.go
    main_test.go:22: TestMain/case3 done at 2024-10-30 22:13:39.015679 +0900 JST m=+3.001437751
    main_test.go:22: TestMain/case1 done at 2024-10-30 22:13:39.015699 +0900 JST m=+3.001457709
    main_test.go:22: TestMain/case2 done at 2024-10-30 22:13:42.017176 +0900 JST m=+6.002949793
    a_test.go:22: TestA/case1 done at 2024-10-30 22:13:39.134682 +0900 JST m=+3.002205460
    a_test.go:22: TestA/case2 done at 2024-10-30 22:13:39.134649 +0900 JST m=+3.002172126
    a_test.go:22: TestA/case3 done at 2024-10-30 22:13:42.137257 +0900 JST m=+6.004795543
    b_test.go:22: TestB/case1 done at 2024-10-30 22:13:45.292284 +0900 JST m=+3.001975751
    b_test.go:22: TestB/case3 done at 2024-10-30 22:13:45.292252 +0900 JST m=+3.001943626
    b_test.go:22: TestB/case2 done at 2024-10-30 22:13:48.294393 +0900 JST m=+6.004099792

少しわかりにくいと思うので、時系列としてまとめてみます。

  1. TestMain/case3, TestMain/case1, TestA/case1, TestA/case2 が2024-10-30 22:13:39に終了
    • 並列できるパッケージは2つで、かつ、そのパッケージ内の並列度も2なので期待通りですね
  2. TestMain/case2, TestA/case3 が2024-10-30 22:13:42に終了
    • 並列できるパッケージは2つのままで、残ったテストが実行されています
  3. TestB/case1, TestB/case3 が2024-10-30 22:13:45に終了
    • 並列できるパッケージは2つだが、TestMainTestA のテストはすべて終了しているので、TestB のサブテストが並列実行される
  4. TestB/case2 が2024-10-30 22:13:48に終了
    • 残ったテストが終了する

確かに意図した挙動になっていそうでした!

16. -run

シグネチャと詳細は以下になります。

        -run regexp
            Run only those tests, examples, and fuzz tests matching the regular
            expression. For tests, the regular expression is split by unbracketed
            slash (/) characters into a sequence of regular expressions, and each
            part of a test's identifier must match the corresponding element in
            the sequence, if any. Note that possible parents of matches are
            run too, so that -run=X/Y matches and runs and reports the result
            of all tests matching X, even those without sub-tests matching Y,
            because it must run them to look for those sub-tests.
            See also -skip.

-run フラグは、指定された正規表現にマッチしたテストを実行します。テストに関して、正規表現は / によって一連の正規表現に分割され、テストの識別子はその一連の正規表現の要素に一致しなければなりません。-run=X/Y が指定された場合、Y に合致するサブテストがなかったとしても、X にマッチするテストはすべて実行されます。

実際に試してみます!

  • main.go
package main

func SumInt(a []int) int {
	sum := 0
	for _, v := range a {
		sum += v
	}
	return sum
}

func SumInt64(a []int64) int64 {
	sum := int64(0)
	for _, v := range a {
		sum += v
	}
	return sum
}
  • main_test.go
package main

import (
	"testing"
)

func TestSumInt(t *testing.T) {
	cases := map[string]struct {
		input    []int
		expected int
	}{
		"empty": {
			input:    []int{},
			expected: 0,
		},
		"result_15": {
			input:    []int{1, 2, 3, 4, 5},
			expected: 15,
		},
		"result_115": {
			input:    []int{10, 20, 30, 40, 15},
			expected: 115,
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			actual := SumInt(c.input)
			if actual != c.expected {
				t.Errorf("expected %d, got %d", c.expected, actual)
			}
		})
	}
}

func TestSumInt64(t *testing.T) {
	cases := map[string]struct {
		input    []int64
		expected int64
	}{
		"empty": {
			input:    []int64{},
			expected: 0,
		},
		"result_15": {
			input:    []int64{1, 2, 3, 4, 5},
			expected: 15,
		},
		"result_115": {
			input:    []int64{10, 20, 30, 40, 15},
			expected: 115,
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			actual := SumInt64(c.input)
			if actual != c.expected {
				t.Errorf("expected %d, got %d", c.expected, actual)
			}
		})
	}
}

TestSumInt のみを実行してみたいと思います

❯ go test . -v -run "TestSumInt$"
=== RUN   TestSumInt
=== RUN   TestSumInt/result_115
=== RUN   TestSumInt/empty
=== RUN   TestSumInt/result_15
--- PASS: TestSumInt (0.00s)
    --- PASS: TestSumInt/result_115 (0.00s)
    --- PASS: TestSumInt/empty (0.00s)
    --- PASS: TestSumInt/result_15 (0.00s)
PASS
ok      github.com/k3forx/testflag      0.164s

ちゃんと TestSumInt のみが実行されてますね!
ちなみに -run "SumInt" として指定すると、TestSumIntTestSumInt64 のテストが実行されます。


サブテストの方も見てみます!

/ で分けて正規表現が適用されるので、TestSumInt のテストの result_15 のサブテストのみを実行したい場合、-run "Int$/result_15$" と指定すれば良さそうですね。

❯ go test . -v -run "Int$/result_15$"
=== RUN   TestSumInt
=== RUN   TestSumInt/result_15
--- PASS: TestSumInt (0.00s)
    --- PASS: TestSumInt/result_15 (0.00s)
PASS
ok      github.com/k3forx/testflag      0.162s

確かに指定通りのテストのみが実行されていそうでした!

17. -short

シグネチャと詳細は以下になります。

        -short
            Tell long-running tests to shorten their run time.
            It is off by default but set during all.bash so that installing
            the Go tree can run a sanity check but not spend time running
            exhaustive tests.

-short フラグは長時間かかるようなテストをスキップするためのフラグです。
正直 go help testflag のドキュメントを見てもよく分からなかったのですが、testingパッケージを見ると詳細がわかりました。

❯ go doc testing.Short
package testing // import "testing"

func Short() bool
    Short reports whether the -test.short flag is set.

つまり、-short フラグを指定しているとき、testing.Short() がtrueになる挙動のようです。実際のユースケースとしては testing.Short() がtrueの場合に、testing.Skip()testing.SkipNow() を呼ぶことが多いようです。

実際に試してみます!

  • main_test.go
package main

import (
	"testing"
)

func TestSumInt(t *testing.T) {
	cases := map[string]struct {
		input    []int
		expected int
	}{
		"empty": {
			input:    []int{},
			expected: 0,
		},
		"result_15": {
			input:    []int{1, 2, 3, 4, 5},
			expected: 15,
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			if len(c.input) == 0 && testing.Short() {
				t.Skip("SKIP")
			}

			actual := SumInt(c.input)
			if actual != c.expected {
				t.Errorf("expected %d, got %d", c.expected, actual)
			}
		})
	}
}

まずは -short フラグなしで実行してみます。

❯ go test . -v
=== RUN   TestSumInt
=== RUN   TestSumInt/empty
=== RUN   TestSumInt/result_15
--- PASS: TestSumInt (0.00s)
    --- PASS: TestSumInt/empty (0.00s)
    --- PASS: TestSumInt/result_15 (0.00s)
PASS
ok      github.com/k3forx/testflag      0.266s

すべてのサブテストが実行されてますね!

次は -short フラグを指定してみます。

❯ go test . -v -short
=== RUN   TestSumInt
=== RUN   TestSumInt/empty
    main_test.go:25: SKIP
=== RUN   TestSumInt/result_15
--- PASS: TestSumInt (0.00s)
    --- SKIP: TestSumInt/empty (0.00s)
    --- PASS: TestSumInt/result_15 (0.00s)
PASS
ok      github.com/k3forx/testflag      0.172s

「SKIP」というログと共に TestSumInt/empty がスキップされることが確かめられました!

18. -shuffle

シグネチャと詳細は以下になります。

        -shuffle off,on,N
            Randomize the execution order of tests and benchmarks.
            It is off by default. If -shuffle is set to on, then it will seed
            the randomizer using the system clock. If -shuffle is set to an
            integer N, then N will be used as the seed value. In both cases,
            the seed will be reported for reproducibility.

-shuffle フラグを指定するとテストとベンチマークの実行順序をランダムにできます。 デフォルトではオフになっています。オンになっている場合、system clockを用いて乱数の初期化が行われるようです。もし整数 N に対して -shuffle フラグが指定されている場合には、N がseed値として使用されるようです。

実際に確かめてみます!

  • main_test.go
package main

import (
	"testing"
)

func TestSum1(t *testing.T) {
	nums := []int{1, 2, 3, 4, 5}

	expected := 15
	if acutal := Sum(nums); acutal != expected {
		t.Errorf("Expected %d but got %d", expected, acutal)
	}
}

func TestSum2(t *testing.T) {
	nums := []int{}

	expected := 0
	if acutal := Sum(nums); acutal != expected {
		t.Errorf("Expected %d but got %d", expected, acutal)
	}
}

-shuffle フラグなしだと TestSum1 の後に TestSum2 が実行されます。

❯ go test . -v
=== RUN   TestSum1
--- PASS: TestSum1 (0.00s)
=== RUN   TestSum2
--- PASS: TestSum2 (0.00s)
PASS
ok      github.com/k3forx/testflag      0.158s

-shuffle フラグを指定して実行してみます!

❯ go test . -shuffle on -v
-test.shuffle 1729606565255227000
=== RUN   TestSum2
--- PASS: TestSum2 (0.00s)
=== RUN   TestSum1
--- PASS: TestSum1 (0.00s)
PASS
ok      github.com/k3forx/testflag      0.163s

TestSum2 のあとに TestSum1 が実行されてますね!

19. -skip

シグネチャと詳細は以下になります。

        -skip regexp
            Run only those tests, examples, fuzz tests, and benchmarks that
            do not match the regular expression. Like for -run and -bench,
            for tests and benchmarks, the regular expression is split by unbracketed
            slash (/) characters into a sequence of regular expressions, and each
            part of a test's identifier must match the corresponding element in
            the sequence, if any.

-skip フラグは正規表現にマッチしないテストを実行する(マッチするテストをスキップする) ためのフラグです。-run-bench と同じように正規表現は / で区切られて適用されます。

実際に試してみます!

  • main_test.go
package main

import (
	"testing"
)

func TestSumInt(t *testing.T) {
	cases := map[string]struct {
		input    []int
		expected int
	}{
		"empty": {
			input:    []int{},
			expected: 0,
		},
		"result_15": {
			input:    []int{1, 2, 3, 4, 5},
			expected: 15,
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			actual := SumInt(c.input)
			if actual != c.expected {
				t.Errorf("expected %d, got %d", c.expected, actual)
			}
		})
	}
}

empty のサブテストをスキップするために -skip "/empty" を指定してみます。

❯ go test . -v -skip "/empty"
=== RUN   TestSumInt
=== RUN   TestSumInt/result_15
--- PASS: TestSumInt (0.00s)
    --- PASS: TestSumInt/result_15 (0.00s)
PASS
ok      github.com/k3forx/testflag      0.259s

ちゃんとスキップされていそうです!

20. -timeout

シグネチャと詳細は以下になります。

        -timeout d
            If a test binary runs longer than duration d, panic.
            If d is 0, the timeout is disabled.
            The default is 10 minutes (10m).

-timeout フラグを指定すると、指定された時間 d よりテストの実行時間が長い場合、パニックするようになっています。d0 の場合には、タイムアウトはしないようになっており、デフォルトでは 10 (10m) が指定されています。

実際に確かめてみます!

package main

import (
	"testing"
	"time"
)

func TestSum(t *testing.T) {
	nums := []int{1, 2, 3, 4, 5}

	time.Sleep(10 * time.Second)

	expected := 15
	if acutal := Sum(nums); acutal != expected {
		t.Errorf("Expected %d but got %d", expected, acutal)
	}
}

time.Sleep で10秒間待つようにしているので、-timeout フラグとして 1s を指定します。

❯ go test . -timeout 1s
panic: test timed out after 1s
        running tests:
                TestSum (1s)

goroutine 21 [running]:
testing.(*M).startAlarm.func1()
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/testing/testing.go:2373 +0x30c
created by time.goFunc
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/time/sleep.go:215 +0x38

goroutine 1 [chan receive]:
testing.(*T).Run(0x140000b64e0, {0x102938930?, 0x140000a0b48?}, 0x1029a6ea0)
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/testing/testing.go:1751 +0x328
testing.runTests.func1(0x140000b64e0)
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/testing/testing.go:2168 +0x40
testing.tRunner(0x140000b64e0, 0x140000a0c68)
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/testing/testing.go:1690 +0xe4
testing.runTests(0x140000c0030, {0x102a77ff0, 0x1, 0x1}, {0x8400000000000000?, 0x845f8d6b25824c05?, 0x102a81580?})
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/testing/testing.go:2166 +0x3ac
testing.(*M).Run(0x140000a60a0)
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/testing/testing.go:2034 +0x588
main.main()
        _testmain.go:45 +0x90

goroutine 20 [sleep]:
time.Sleep(0x2540be400)
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/runtime/time.go:315 +0xe0
github.com/k3forx/testflag.TestSum(0x140000b6680)
        /Users/XXX/repos/scrap/testflag/main_test.go:11 +0x58
testing.tRunner(0x140000b6680, 0x1029a6ea0)
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/testing/testing.go:1690 +0xe4
created by testing.(*T).Run in goroutine 1
        /Users/XXX/.asdf/installs/golang/1.23.2/go/src/testing/testing.go:1743 +0x314
FAIL    github.com/k3forx/testflag      1.198s
FAIL

確かにパニックしていそうです!test timed out after 1s というログも表示されていますね!

21. -v

シグネチャと詳細は以下になります。

        -v
            Verbose output: log all tests as they are run. Also print all
            text from Log and Logf calls even if the test succeeds.

-v フラグを付与すると冗長な(verboseな)outputが表示されます。テストが成功していても LogLogf で指定されたテキストが表示されます。

実際に確かめてみます!

package main

import (
	"testing"
)

func TestSum(t *testing.T) {
	nums := []int{1, 2, 3, 4, 5}
	t.Log("Given the array of numbers: ", nums)

	expected := 15
	t.Logf("Expected: %d", expected)
	if acutal := Sum(nums); acutal != expected {
		t.Errorf("Expected %d but got %d", expected, acutal)
	}
}

-v フラグありとなしでテストを実行してみます。

❯ go test .
ok      github.com/k3forx/testflag      0.237s

❯ go test . -v
=== RUN   TestSum
    main_test.go:9: Given the array of numbers:  [1 2 3 4 5]
    main_test.go:12: Expected: 15
--- PASS: TestSum (0.00s)
PASS
ok      github.com/k3forx/testflag      0.140s

-v フラグを付与すると t.Logt.Logf で指定されたメッセージも合わせて表示されました!

22. -vet

シグネチャと詳細は以下になります。

        -vet list
            Configure the invocation of "go vet" during "go test"
            to use the comma-separated list of vet checks.
            If list is empty, "go test" runs "go vet" with a curated list of
            checks believed to be always worth addressing.
            If list is "off", "go test" does not run "go vet" at all.

-vet フラグは go test 中に実行する go vet の呼び出しを設定するためのフラグです。カンマで分割したリストのvetチェックが実行されます。リストが空の場合、go testgo vet を実行し、常に取り組む価値があると思われるチェックを厳選して実行します。もしリストは off で指定されている場合、go testgo vet を実行しないようになっています。

実際に確かめてみます!

appends のvetチェックに引っ掛かるように main.go を記述してみます。
appends については go tool vet help appends を実行すると詳細がわかります。

  • main.go
package main

func AppendInt(nums []int, n int) []int {
	s := []string{"a", "b", "c"}
	_ = append(s)
	return append(nums, n)
}
  • main_test.go
package main

import (
	"slices"
	"testing"
)

func TestAppendInt(t *testing.T) {
	nums := []int{1, 2, 3}
	n := 4

	expected := []int{1, 2, 3, 4}
	actual := AppendInt(nums, n)
	if !slices.Equal(actual, expected) {
		t.Errorf("expected %v but got %v", expected, actual)
	}
}

-vet フラグなしで実行してみると通常通りテストが通ります。

❯ go test .
ok      github.com/k3forx/testflag      0.229s

-vet フラグありだとテストが落ちるようになっていますね!

❯ go test . -vet appends
# github.com/k3forx/testflag
# [github.com/k3forx/testflag]
./main.go:17:6: append with no values
FAIL    github.com/k3forx/testflag [build failed]
FAIL

-vet フラグが受け付けるリストは go tool vet help で確かめられます!

テストの実行中にテスト自体をプロファイルするフラグ

23. -benchmem

シグネチャと詳細は以下になります。

        -benchmem
            Print memory allocation statistics for benchmarks.
            Allocations made in C or using C.malloc are not counted.

-benchmem フラグはベンチマークテストに対するメモリのアロケーションの統計を表示してくれるフラグです。CやC.mallocを通じてのアロケーションはカウントされません。

実際に試してみます!

package main

import "testing"

func BenchmarkTest(b *testing.B) {
	nums := []int{1, 2, 3, 4, 5}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = Sum(nums)
	}
}

-benchmem フラグをつけて実行してみます!

❯ go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11         315525358                3.691 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/k3forx/testflag      1.778s

最後の allocs/op の部分がメモリアロケーションに関する統計を示してます。

24. -blockprofile

シグネチャと詳細は以下になります。

        -blockprofile block.out
            Write a goroutine blocking profile to the specified file
            when all tests are complete.
            Writes test binary as -c would.

-blockprofile フラグはすべてのテストが完了した後、goroutineのブロッキングプロファイルを指定されたファイルに書き込むフラグです。-c フラグが行うようにテストバイナリを書き込みます。

実際に試してみたいと思います!

❯ go test -bench . -blockprofile block.out
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11         335249805                3.609 ns/op
PASS
ok      github.com/k3forx/testflag      1.577s

実行すると以下の2つのファイルが生成されていると思います。

  • testflag.test
  • block.out
❯ ls
block.out  go.mod  main.go  main_test.go  testflag.test

testflag.test がテストのバイナリファイルで、block.out がgoroutineのブロッキングプロファイルになります。得られたプロファイルのバイナリファイルは go tool pprof コマンドを使用すると可視化できます。詳細は go tool pprof -h を参照してください。

25. -blockprofilerate

シグネチャと詳細は以下になります。

        -blockprofilerate n
            Control the detail provided in goroutine blocking profiles by
            calling runtime.SetBlockProfileRate with n.
            See 'go doc runtime.SetBlockProfileRate'.
            The profiler aims to sample, on average, one blocking event every
            n nanoseconds the program spends blocked. By default,
            if -test.blockprofile is set without this flag, all blocking events
            are recorded, equivalent to -test.blockprofilerate=1.

-blockprofilerate フラグはgoroutineのブロッキングプロファイルによって提供される詳細を、 runtime.SetBlockProfileRate(n) によってコントロールするためのフラグです。プロファイラーは、平均して、プログラムがブロックされている時間 n ナノ秒ごとに1つのブロッキング・イベントをサンプリングすることを目標としているようです。

より詳細は go doc runtime.SetBlockProfileRate でみることができます。

❯ go doc runtime.SetBlockProfileRate
package runtime // import "runtime"

func SetBlockProfileRate(rate int)
    SetBlockProfileRate controls the fraction of goroutine blocking events that
    are reported in the blocking profile. The profiler aims to sample an average
    of one blocking event per rate nanoseconds spent blocked.

    To include every blocking event in the profile, pass rate = 1. To turn off
    profiling entirely, pass rate <= 0.

実際に試してみたいと思います!
まずは -blockprofilerate 1 としてみます。

❯ go test -bench . -blockprofile block.out -blockprofilerate 1
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11         482151358                2.244 ns/op
PASS
ok      github.com/k3forx/testflag      1.541s

❯ go tool pprof block.out
File: testflag.test
Type: delay
Time: Oct 30, 2024 at 5:51pm (JST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top3
Showing nodes accounting for 1.34s, 100% of 1.34s total
Dropped 4 nodes (cum <= 0.01s)
Showing top 3 nodes out of 11
      flat  flat%   sum%        cum   cum%
     1.34s   100%   100%      1.34s   100%  runtime.chanrecv1
         0     0%   100%      1.34s   100%  main.main
         0     0%   100%      1.34s   100%  runtime.main
(pprof)

確かにプロファイルが取得できていますね!

続いて -blockprofilerate 0 のケースを試してみます。

❯ go test -bench . -blockprofile block.out -blockprofilerate 0
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11         523895547                2.244 ns/op
PASS
ok      github.com/k3forx/testflag      1.491s

❯ go tool pprof block.out
File: testflag.test
Type: delay
Time: Oct 30, 2024 at 5:55pm (JST)
No samples were found with the default sample value type.
Try "sample_index" command to analyze different sample values.
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) 

「No samples were found with the default sample value type.」と表示されているので、サンプリンが行われていないことがわかります!

26. -coverprofile

シグネチャと詳細は以下になります。

        -coverprofile cover.out
            Write a coverage profile to the file after all tests have passed.
            Sets -cover.

-coverprofile フラグはすべてのテストが通った後で、カバレッジのプロファイルをファイルに書き込むフラグです。-coverprofile フラグを指定している場合は、-cover フラグは不要になります。

実際に確かめてみます!
Sum 関数に対する以下のテストコードのカバレッジを見てみます。

❯ go test . -covermode set -coverprofile cover.out
ok      github.com/k3forx/testflag      0.234s  coverage: 100.0% of statements

テストが通った場合、cover.out に以下のようなカバレッジのプロファイルが書き込まれていることがわかると思います。可視化したい場合は go tool cover を使います。

cat cover.out
mode: set
github.com/k3forx/testflag/main.go:5.26,7.37 2 1
github.com/k3forx/testflag/main.go:7.37,9.3 1 1
github.com/k3forx/testflag/main.go:10.2,10.12 1 1

-covermode フラグの引数の違いについても以下で詳細にみていきたいと思います。
各モードで違いが出るように以下のコードで試してみたいと思います。

  • main.go
package main

import (
	"strconv"
)

func ToString(v any) string {
	switch v.(type) {
	case int:
		return strconv.Itoa(v.(int))
	case string:
		return v.(string)
	case bool:
		return strconv.FormatBool(v.(bool))
	default:
		return ""
	}
}
  • main_test.go
package main

import "testing"

func TestToString(t *testing.T) {
	cases := map[string]struct {
		v        any
		expected any
	}{
		"string_hello": {
			v:        "hello",
			expected: "hello",
		},
		"string_empty": {
			v:        "",
			expected: "",
		},
		"true": {
			v:        true,
			expected: "true",
		},
	}

	for name, c := range cases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			if actual := ToString(c.v); actual != c.expected {
				t.Errorf("Expected %v but got %v", c.expected, actual)
			}
		})
	}
}

26.1 -covermode set で実行してみる

❯ go test . -covermode set -coverprofile set.out
ok      github.com/k3forx/testflag      0.198s  coverage: 60.0% of statements

go tool cover -html=set.out で可視化してみると以下のような表示が得られます。

return strconv.Itoa(v.(int))return "" の部分はテストされていないことがわかりますね!

26.2 -covermode count で実行してみる

❯ go test . -covermode count -coverprofile count.out
ok      github.com/k3forx/testflag      0.207s  coverage: 60.0% of statements

go tool cover -html=count.out で可視化してみると以下のような表示が得られます。

-covermode set でみたのと同じように return strconv.Itoa(v.(int))return "" の部分はテストされていないことがわかります。

さらに return v.(string)return strconv.FormatBool(v.(bool)) は微妙に色が違っていることがわかると思います。これはテストで実行された回数に依存していて、色が濃くなるほどテストで実行された回数が多いことを示します。

26.3 -covermode atomic で実行してみる

❯ go test . -covermode atomic -coverprofile atomic.out
ok      github.com/k3forx/testflag      0.268s  coverage: 60.0% of statements

go tool cover -html=atomic.out で可視化してみると以下のような表示が得られます。

こちらの図は -covermode count を指定して得られた図とまったく同じになってしまいました。
今回のテストケースでは -covermode count を指定した場合と -covermode atomic を指定した場合で差異はないということになりますね!

27. -cpuprofile

シグネチャと詳細は以下になります。

        -cpuprofile cpu.out
            Write a CPU profile to the specified file before exiting.
            Writes test binary as -c would.

-cpuprofile フラグはCPUのプロファイルを指定されたファイルに書き込むためのフラグです。

実際に試してみます!(実行されたテストがわかりやすいように -v フラグもつけています)

❯ go test . -cpuprofile cpu.out -v
=== RUN   TestSum
--- PASS: TestSum (0.00s)
PASS
ok      github.com/k3forx/testflag      0.283s

テストを実行すると cpu.out というファイルが生成されます。

ls | grep cpu.out
cpu.out

詳細を見ようとすると以下のように「No samples were found with the default sample value type.」という表示が出てきます。これはプログラムの実行が早すぎてプロファイルを取れなかったことを意味しています。CPUプロファイルを取る場合はベンチマークのテストを実行して、プロファイルを取ることも多いようです。

❯ go tool pprof cpu.out
File: testflag.test
Type: cpu
Time: Oct 29, 2024 at 9:39pm (JST)
Duration: 202.24ms, Total samples = 0
No samples were found with the default sample value type.
Try "sample_index" command to analyze different sample values.
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

28. -memprofile

シグネチャと詳細は以下になります。

        -memprofile mem.out
            Write an allocation profile to the file after all tests have passed.
            Writes test binary as -c would.

-memprofile フラグはすべてのテストが成功した後に、メモリアロケーションのプロファイルを指定されたファイルに書き込むためのフラグです。

実際に試してみます!(実行されたテストがわかりやすいように -v フラグもつけています)

❯ go test . -memprofile mem.out -v
=== RUN   TestSum
--- PASS: TestSum (0.00s)
PASS
ok      github.com/k3forx/testflag      0.082s

テストを実行すると mem.out というファイルが生成されます。

ls | grep mem.out
mem.out

詳細を見ようとすると以下のように No samples were found with the default sample value type. という表示が出てきます。これはプログラムの実行が早すぎてプロファイルを取れなかったことを意味しています。メモリのプロファイルを取る場合はベンチマークのテストを実行して、プロファイルを取ることも多いようです。

❯ go tool pprof mem.out
File: testflag.test
Type: alloc_space
Time: Oct 29, 2024 at 9:55pm (JST)
No samples were found with the default sample value type.
Try "sample_index" command to analyze different sample values.
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) 

29. -memprofilerate

シグネチャと詳細は以下になります。

        -memprofilerate n
            Enable more precise (and expensive) memory allocation profiles by
            setting runtime.MemProfileRate. See 'go doc runtime.MemProfileRate'.
            To profile all memory allocations, use -test.memprofilerate=1.

-memprofile フラグは、runtime.MemProfileRate で指定される値によって、より正確で (かつコストのかかる) メモリアロケーションのプロファイルを取得するためのフラグです。すべてのメモリアロケーションをサンプリングするには 1 を指定します。

ちなみに go doc runtime.MemProfileRate を実行すると以下のように詳細を得ることができます。

❯ go doc runtime.MemProfileRate
package runtime // import "runtime"

var MemProfileRate int = 512 * 1024
    MemProfileRate controls the fraction of memory allocations that are recorded
    and reported in the memory profile. The profiler aims to sample an average
    of one allocation per MemProfileRate bytes allocated.

    To include every allocated block in the profile, set MemProfileRate to 1.
    To turn off profiling entirely, set MemProfileRate to 0.

    The tools that process the memory profiles assume that the profile rate is
    constant across the lifetime of the program and equal to the current value.
    Programs that change the memory profiling rate should do so just once,
    as early as possible in the execution of the program (for example, at the
    beginning of main).

実際に試してみます!

-memprofilerate=1 として実行してみます。

❯ go test -bench . -memprofile mem.out -v -memprofilerate=1
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum
BenchmarkSum-11         315554538                3.690 ns/op
PASS
ok      github.com/k3forx/testflag      1.809s

go tool pprof を使ってみると、確かにプロファイルが取得できていることがわかると思います。

❯ go tool pprof mem.out
File: testflag.test
Type: alloc_space
Time: Oct 29, 2024 at 10:06pm (JST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top10
Showing nodes accounting for 101.70kB, 95.84% of 106.12kB total
Dropped 70 nodes (cum <= 0.53kB)
Showing top 10 nodes out of 57
      flat  flat%   sum%        cum   cum%
      72kB 67.85% 67.85%       72kB 67.85%  regexp.(*bitState).reset
    9.06kB  8.54% 76.39%     9.06kB  8.54%  sync.(*Pool).pinSlow
       7kB  6.60% 82.99%        7kB  6.60%  runtime.malg
    4.50kB  4.24% 87.23%     4.50kB  4.24%  runtime.makeProfStackFP (inline)
       4kB  3.77% 91.00%    10.25kB  9.66%  runtime.allocm
    1.84kB  1.74% 92.73%     1.84kB  1.74%  testing.(*B).ResetTimer
    1.16kB  1.09% 93.82%    44.57kB 42.00%  testing.(*B).Run
    0.81kB  0.77% 94.59%     0.81kB  0.77%  fmt.init.func1
    0.70kB  0.66% 95.25%    86.93kB 81.92%  testing.runBenchmarks
    0.62kB  0.59% 95.84%     1.25kB  1.18%  testing.runTests
(pprof)

続いて、-memprofilerate=0 として実行したのちに、go tool pprof を使ってプロファイルを見てみます。

❯ go test -bench . -memprofile mem.out -v -memprofilerate=0
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum
BenchmarkSum-11         317048524                3.728 ns/op
PASS
ok      github.com/k3forx/testflag      1.645s

❯ go tool pprof mem.out
File: testflag.test
Type: alloc_space
Time: Oct 29, 2024 at 10:08pm (JST)
No samples were found with the default sample value type.
Try "sample_index" command to analyze different sample values.
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

「No samples were found with the default sample value type.」と記述があるようにプロファイルは取得できていないことがわかります。

30. -mutexprofile

シグネチャと詳細は以下になります。

        -mutexprofile mutex.out
            Write a mutex contention profile to the specified file
            when all tests are complete.
            Writes test binary as -c would.

-mutexprofile フラグはすべてのテストが完了したとき、ミューテックスの競合に関するプロファイルを指定されたファイルに書き込むフラグです。

実際に試してみます!

❯ go test . -mutexprofile mutex.out -bench .
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11         479839014                2.237 ns/op
PASS
ok      github.com/k3forx/testflag      1.558s

mutex.out というファイルが作成されていることが確かめられると思います。
実際に可視化してみたい場合は go tool pprof コマンドを使用します。

31. -mutexprofilefraction

シグネチャと詳細は以下になります。

        -mutexprofilefraction n
            Sample 1 in n stack traces of goroutines holding a
            contended mutex.

-mutexprofilefraction フラグは、競合するミューテックスを保持するゴルーチンのスタックトレースn個に1個のサンプリングを行うフラグです。

-memprofilerate フラグと同様に go doc runtime.SetMutexProfileFraction をみるとより詳細を知ることができます。

❯ go doc runtime.SetMutexProfileFraction
package runtime // import "runtime"

func SetMutexProfileFraction(rate int) int
    SetMutexProfileFraction controls the fraction of mutex contention events
    that are reported in the mutex profile. On average 1/rate events are
    reported. The previous rate is returned.

    To turn off profiling entirely, pass rate 0. To just read the current rate,
    pass rate < 0. (For n>1 the details of sampling may change.)

ミューテックスのプロファイルで報告されたミューテックスの競合のうちの 1/n がサンプリングされるようです。0 を渡せばプロファイルをオフにでき、1 を渡せばすべてサンプリングするようです。

実際に試してみます!

-mutexprofilefraction フラグの値として 1 を指定してみます。

❯ go test . -mutexprofile mutex.out -bench . -mutexprofilefraction 1
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11         517613786                2.237 ns/op
PASS
ok      github.com/k3forx/testflag      1.477s

❯ go tool pprof mutex.out
File: testflag.test
Type: delay
Time: Oct 29, 2024 at 10:50pm (JST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top3
Showing nodes accounting for 346.34us, 100% of 346.34us total
      flat  flat%   sum%        cum   cum%
  346.34us   100%   100%   346.34us   100%  runtime._LostContendedRuntimeLock
(pprof)

続いて -mutexprofilefraction フラグの値として 0 を指定してみます

❯ go test . -mutexprofile mutex.out -bench . -mutexprofilefraction 0
goos: darwin
goarch: arm64
pkg: github.com/k3forx/testflag
cpu: Apple M3 Pro
BenchmarkSum-11         519822098                2.243 ns/op
PASS
ok      github.com/k3forx/testflag      1.483s

❯ go tool pprof mutex.out
File: testflag.test
Type: delay
Time: Oct 29, 2024 at 10:51pm (JST)
No samples were found with the default sample value type.
Try "sample_index" command to analyze different sample values.
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

「No samples were found with the default sample value type.」という表示があるので、確かにサンプリングされていないことが分かりますね。

32. -outputdir

シグネチャと詳細は以下になります。

        -outputdir directory
            Place output files from profiling in the specified directory,
            by default the directory in which "go test" is running.

-outputdir フラグはプロファイリングの結果を指定されたディレクトリに置くためのコマンドです。デフォルトでは go test が実行されたディレクトリにファイルが置かれます。

実際に試してみます!

❯ go test . -memprofile mem.out -outputdir ./profile
ok      github.com/k3forx/testflag      0.003s

❯ tree ./profile
./profile
└── mem.out

1 directory, 1 file

ちなみに -outputdir フラグで指定するディレクトリは先に作成しておく必要があります。

33. -trace

シグネチャと詳細は以下になります。

        -trace trace.out
            Write an execution trace to the specified file before exiting.

-trace フラグは実行トレースを指定されたファイルに書き込むためのフラグです。

実際に試してみます!

❯ go test . -trace trace.out
ok      github.com/k3forx/testflag      0.246s

trace.out というファイルが作成されていると思います!

ls | grep trace.out
trace.out

可視化をしたい場合は go tool trace コマンドを使用します。可視化されたtraceの見方については、テストのフラグの本筋とは離れてしまうので機会があれば解説記事を書いてみたいと思います!

まとめ

go test に指定できる全33個のフラグをすべて解説してみました。知らないフラグもたくさんあったのではないでしょうか?

Goでテストを書く際の参考になれば幸いです!

Canary Tech Blog

Discussion