Edited at

go test で出来ること

More than 3 years have passed since last update.

goで標準に用意されているtestパッケージ。

その使い方や便利な関数について


実行の仕方

go testコマンドで実行できる。この場合はカレントにあるテスト。

go test <package名> でパッケージ指定できる。

% go test strconv

ok strconv 3.395s


テストの書き方

テストは _test.goというファイルに、TestXxxxという関数名をつける。


$GOROOT/src/net/http/serve_test.go

func TestSetsRemoteAddr(t *testing.T) {

defer afterTest(t)
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
fmt.Fprintf(w, "%s", r.RemoteAddr)
}))
defer ts.Close()

res, err := Get(ts.URL)
if err != nil {
t.Fatalf("Get error: %v", err)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("ReadAll error: %v", err)
}
ip := string(body)
if !strings.HasPrefix(ip, "127.0.0.1:") && !strings.HasPrefix(ip, "[::1]:") {
t.Fatalf("Expected local addr; got %q", ip)
}
}


testing.T は、主に以下のような機能が用意されている。


func (*T) Fail

実行後にそのテストは失敗となるが、そのテストの処理は続く


  • format構造や変数を渡してメッセージを表示させたい場合


    • func (*T) Error

    • func (*T) Errorf




func (*T) FatalNow

実行後にそのテストは失敗となり、そのテストの処理は止まる


  • format構造や変数を渡してメッセージを表示させたい場合


    • func (*T) Fatal

    • func (*T) Fatalf




t.Skip()

そのテストをスキップする。

func (*T) Skipfunc (*T) SkipNow のエイリアス。

両方とも、その後の処理は実行されない。


t.Log()

Printlnなどと同じように、値を実行時に表示してくれる。

直接fmt.Println()を使う場合との違いは、行数をきちんと出してくれる。

ちなみに出力は、go testは、テキストは失敗するか

-vオプションをつけたときしか表示されない。


コマンド


一部のテストだけを実行したい場合

-run regexpで実行したいテストを指定できる

go test -run TestSetsRemoteAddr


ベンチマーク

go test -bench .とするとベンチマーク用の関数も実行出来る。

例えば適当に下のようなものがあった場合


concat.go

package concat

func Concat(ss ...string) string {
var r string
for _, s := range ss {
r += s
}
return r
}



concat_test.go

package concat

import "testing"

func BenchmarkConcat(b *testing.B) {
var ss []string
for n := 0; n < 100; n++ {
ss = append(ss, "foo")
}

for i := 0; i < b.N; i++ {
Concat(ss...)
}
}


ベンチマークの結果が出てくる

% go test -bench . -benchmem

testing: warning: no tests to run
PASS
BenchmarkConcat 20000 77496 ns/op 16016 B/op 99 allocs/op

-benchmemを指定すると、1回あたりアロケーションが何回行ったかも表示する。


ベンチマークの結果から修正をする

上の場合、文字列を+で連結しているのでアロケーションが呼ばれているのでそれを減らす。

例えば bytes.Bufferを使う場合


concat.go

func Concat(ss ...string) string {

b := bytes.NewBufferString("")
for _, s := range ss {
r := strings.NewReader(s)
r.WriteTo(b)
}
return b.String()
}

% go test  -bench . -benchmem

testing: warning: no tests to run
PASS
BenchmarkConcat 100000 16668 ns/op 1144 B/op 8 allocs/op

アロケーションが99から8に減り、処理速度も上がっているのがわかる。


よく使うツール


benchcmp

ベンチマークの結果を比較したものを出力できる

% go get -u golang.org/x/tools/cmd/benchcmp

% go test -bench . -benchmem > before.out
# 修正後
% go test -bench . -benchmem > after.out
% benchcmp before.out after.out
benchmark old ns/op new ns/op delta
BenchmarkConcat 81059 15953 -80.32%

benchmark old allocs new allocs delta
BenchmarkConcat 99 8 -91.92%

benchmark old bytes new bytes delta
BenchmarkConcat 16016 1144 -92.86%


prettybench

https://github.com/cespare/prettybench

ベンチマークが複数あった場合に、整形して表示してくれる。

% go get -u github.com/cespare/prettybench 

% go test -bench . -benchmem | prettybench
PASS
benchmark iter time/iter bytes alloc allocs
--------- ---- --------- ----------- ------
BenchmarkConcat 20000 77.88 μs/op 16016 B/op 99 allocs/op


参考

Goでアロケーションに気をつけたコードを書く方法

Goの文字列結合のパフォーマンス


Examples

出力の結果をテストしたい場合は、Examplesを使う。

func ExampleHello() {

fmt.Println("hello")
// Output: hello
}

func ExampleSalutations() {
fmt.Println("hello, and")
fmt.Println("goodbye")
// Output:
// hello, and
// goodbye
}

またgodocにExampleを表示させたい場合に、利用する。


foo.go

package foo

func Foo() string {
return "Hello"
}



foo_test.go

package foo

import "fmt"

func ExampleFoo() {
fmt.Println(Foo())
// Output: Hello
}


% godoc -ex=true .

PACKAGE DOCUMENTATION

package foo
import "."

FUNCTIONS

func Foo() string

Example:
fmt.Println(Foo())
// Output: Hello

ただし、ドキュメントにきちんと表示されるようにしたい場合は

名前のつけ方をきちんと行わないと正しく認識されない。

func Example() { ... }

func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

詳しくは http://golang.org/pkg/testing/#pkg-overview で。


カバレッジ

go testはカバレッジの取得も出来る

go test -cover -coverprofile cover.out # カバレッジ結果をcover.outに出力

go tool cover -func=cover.out # カバレッジ結果を出力
github.com/hiroosak/gotest/foo.go:3: Foo 100.0%
github.com/hiroosak/gotest/foo.go:9: String 100.0%
total: (statements) 100.0%
go tool cover -html=cover.out # カバレッジ結果をhtmlで出力


参考

Go 1.2で追加されたコードカバレッジ解析ツールを使う


Tips


TableDrivenTests

いろいろなパターンで、関数への入力と出力を試したい場合は

あらかじめ入力と結果をセットにしたスライスを用意して、

それをループさせてひとつずつテストを行って行数を減らす。


$GOROOT/src/strconv/atoi_test.go(一部)

type atoi64Test struct {

in string
out int64
err error
}

var atoi64tests = []atoi64Test{
{"", 0, ErrSyntax},
{"0", 0, nil},
{"-0", 0, nil},
{"1", 1, nil},
{"-1", -1, nil},
{"12345", 12345, nil},
{"-12345", -12345, nil},
{"012345", 12345, nil},
{"-012345", -12345, nil},
{"98765432100", 98765432100, nil},
{"-98765432100", -98765432100, nil},
{"9223372036854775807", 1<<63 - 1, nil},
{"-9223372036854775807", -(1<<63 - 1), nil},
{"9223372036854775808", 1<<63 - 1, ErrRange},
{"-9223372036854775808", -1 << 63, nil},
{"9223372036854775809", 1<<63 - 1, ErrRange},
{"-9223372036854775809", -1 << 63, ErrRange},
}

// ...

func TestParseInt64(t *testing.T) {
for i := range atoi64tests {
test := &atoi64tests[i]
out, err := ParseInt(test.in, 10, 64)
if test.out != out || !reflect.DeepEqual(test.err, err) {
t.Errorf("Atoi64(%q) = %v, %v want %v, %v",
test.in, out, err, test.out, test.err)
}
}
}


https://github.com/golang/go/wiki/TableDrivenTests


外部APIを扱った処理のテスト

(あとで書く)


時刻を扱ったテスト

(あとで書く)