goで標準に用意されているtestパッケージ。
その使い方や便利な関数について
実行の仕方
go test
コマンドで実行できる。この場合はカレントにあるテスト。
go test <package名>
でパッケージ指定できる。
% go test strconv
ok strconv 3.395s
テストの書き方
テストは _test.go
というファイルに、TestXxxx
という関数名をつける。
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) Skip
は func (*T) SkipNow
のエイリアス。
両方とも、その後の処理は実行されない。
t.Log()
Println
などと同じように、値を実行時に表示してくれる。
直接fmt.Println()
を使う場合との違いは、行数をきちんと出してくれる。
ちなみに出力は、go test
は、テキストは失敗するか
-v
オプションをつけたときしか表示されない。
コマンド
一部のテストだけを実行したい場合
-run regexp
で実行したいテストを指定できる
go test -run TestSetsRemoteAddr
ベンチマーク
go test -bench .
とするとベンチマーク用の関数も実行出来る。
例えば適当に下のようなものがあった場合
package concat
func Concat(ss ...string) string {
var r string
for _, s := range ss {
r += s
}
return r
}
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
を使う場合
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を表示させたい場合に、利用する。
package foo
func Foo() string {
return "Hello"
}
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で出力
参考
Tips
TableDrivenTests
いろいろなパターンで、関数への入力と出力を試したい場合は
あらかじめ入力と結果をセットにしたスライスを用意して、
それをループさせてひとつずつテストを行って行数を減らす。
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)
}
}
}
外部APIを扱った処理のテスト
(あとで書く)
時刻を扱ったテスト
(あとで書く)