go test で出来ること

goで標準に用意されているtestパッケージ。
その使い方や便利な関数について

# 実行の仕方

`go test`コマンドで実行できる。この場合はカレントにあるテスト。
`go test <package名>` でパッケージ指定できる。

```zsh
% go test strconv
ok  	strconv	3.395s
```

# テストの書き方

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

```go:$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) Skip` は `func (*T) SkipNow` のエイリアス。

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

## `t.Log()`

`Println`などと同じように、値を実行時に表示してくれる。
直接`fmt.Println()`を使う場合との違いは、行数をきちんと出してくれる。

ちなみに出力は、`go test`は、テキストは失敗するか
`-v`オプションをつけたときしか表示されない。

# コマンド

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

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

```
go test -run TestSetsRemoteAddr
```

## ベンチマーク

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

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

```go:concat.go
package concat

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

```go: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...)
	}
}
```

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

```zsh
% 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`を使う場合

```go:concat.go
func Concat(ss ...string) string {
  b := bytes.NewBufferString("")
  for _, s := range ss {
    r := strings.NewReader(s)
    r.WriteTo(b)
  }
  return b.String()
}
```

```zsh
% 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

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

```zsh
% 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
ベンチマークが複数あった場合に、整形して表示してくれる。

```zsh
% 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でアロケーションに気をつけたコードを書く方法](http://dsas.blog.klab.org/archives/cat_50043510.html)
[Goの文字列結合のパフォーマンス](http://qiita.com/ono_matope/items/d5e70d8a9ff2b54d5c37)

## Examples

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

```go
func ExampleHello() {
        fmt.Println("hello")
        // Output: hello
}

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

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

```go:foo.go
package foo

func Foo() string {
  return "Hello"
}

```

```go:foo_test.go
package foo

import "fmt"

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

```zsh
% godoc -ex=true .
PACKAGE DOCUMENTATION

package foo
    import "."


FUNCTIONS

func Foo() string

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

ただし、ドキュメントにきちんと表示されるようにしたい場合は
名前のつけ方をきちんと行わないと正しく認識されない。

```go
func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }
```

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

## カバレッジ

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

```zsh
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で追加されたコードカバレッジ解析ツールを使う](http://qiita.com/kaz8/items/06c102369acb784ca689)



# Tips

## TableDrivenTests

いろいろなパターンで、関数への入力と出力を試したい場合は
あらかじめ入力と結果をセットにしたスライスを用意して、
それをループさせてひとつずつテストを行って行数を減らす。

```go:$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を扱った処理のテスト

(あとで書く)

## 時刻を扱ったテスト

(あとで書く)