golangの日記

Go言語を中心にプログラミングについてのブログ

Go言語(golang)のtestingパッケージを使ったテスト

golang.png


Go言語のテストには、testingパッケージが用意されていて、
goのサブコマンド $ go test でテストを実行します。
また、サーバーが必要なテストには httptestパッケージを使います。





目次



Go言語のassert

Go言語には assert はありません。if 文を使ってテストします。

サードパーティのパッケージは testifygoconvey がスターが多い。


基本的なテストの書き方


  • テスト対象の .go ファイル

hello.go

package hello

func hello(name string) string {
    return "hello, " + name
}


  • テストの記述方法

テスト対象のファイル名に _test をつけるのがテストファイルの命名規則になっている。
テスト関数の関数名は、Test で始めて、testing.T を引数で受け取り、
t.Error, t.Fatal などで、エラーの場合にメッセージを出力する

hello_test.go

package hello

import "testing"

func TestHello(t *testing.T) {
    expect := "hello, Tanaka"
    actual := hello("Tanaka")

    if actual != expect {
        t.Errorf("actual: %v, expected: %v", actual, expect)
    }
}


  • テストの実行
    $ ls
      hello.go  hello_test.go

    // エラーがあれば FAIL でそのエラーメッセージが表示される
    $ go test
      PASS
      ok      _/hello 0.001s



テスト実行のコマンドオプション


  • 一部のファイルのみテストを実行する
    $ ls
      bar.go  bar_test.go  baz.go  baz_test.go  foo.go  foo_test.go

    $ go test foo.go foo_test.go


  • 詳細表示(パスしたテスト名も表示)
    $ go test -v


  • カバレッジを表示
    $ go test -cover


  • 関数名を指定して一部のテストのみ実行する(正規表現が使える)
    $ go test -run TestHello


  • 現在のディレクトリ以下のテストを再帰的に実行する
    $ go test ./...


  • src以下のすべてのテストを実行する
    $ go test ...



オプション

  • -bench regexp
  • -benchmem
  • -benchtime t
  • -blockprofile block.out
  • -blockprofilerate n
  • -cover
  • -covermode set,count,atomic
  • -coverpkg pkg1,pkg2,pkg3
  • -coverprofile cover.out
  • -cpu 1,2,4
  • -cpuprofile cpu.out
  • -memprofile mem.out
  • -memprofilerate n
  • -outputdir directory
  • -parallel n
  • -run regexp
  • -short
  • -timeout t
  • -v



ドキュメントにExampleを表示する

GoDoc は、GitHubなどで公開したGoパッケージのドキュメントを掲載できるサービスです。
掲載する方法は、検索ボックスでリポジトリの URL を検索するだけです。自動的にドキュメントが生成されます。

その公開された、ドキュメントの対応する箇所に コードの書き方などの例 を表示するために書くのが example_test です。

example_test.go

package hello_test

// ここのコメントも意味があって、コード例の内容の説明に使う
func Example() {
    // 全体のコード使用例や説明でドキュメントの先頭に表示される
}

// this example shows how to use Hello
func ExampleHello() {
    // Hello 関数に関する使用例を書く
}

// this example shows how to use Hello
func ExampleString_Hello() {
    // 関数の引数の型など Type_ で書く
}



一つの関数に複数のテストを書く

package hello

import (
    "testing"
)

func TestHello(t *testing.T) {
    t.Run("hello-1", func(t *testing.T) {
        t.Fatal("error")
    })
    t.Run("hello-2", func(t *testing.T) {
        t.Fatal("error")
    })
    t.Run("hello-3", func(t *testing.T) {
        t.Fatal("error")
    })
}


特定のテストのみ実行するのと同じように、TestHelloの hello-2 のみテストすることもできる

    $ go test -run TestHello/hello-2



テストをスキップする

何らかの理由(時間が掛かるなど)でテストを飛ばす場合は、testing.Short を使う

package hello

import (
    "testing"
)

func TestHello(t *testing.T) {
    if testing.Short() {
        // スキップ時のメッセージ
        t.Skip("skipping this test")
    }

    if 0 != 1 {
        t.Fatal("error")
    }
}

テストの実行(-shortでスキップする)

    $ go test -short -v
      === RUN   TestHello
      --- SKIP: TestHello (0.00s)
          hello_test.go:9: skipping this test
      PASS
      ok    _/hello 0.001s



サーバーが必要なテスト

サーバーを使ってテストを行いたい場合は、httptest を使います。
以下の例では、テストの前処理と後処理を行えるように、TestMain を使っています。
TLSサーバーでテストしたい場合は httptest.NewTLSServer を使います。

package hello

import (
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "os"
    "testing"
)

var (
    server *httptest.Server
)

func TestMain(m *testing.M) {
    server = httptest.NewServer(nil)

    // ↑ 前処理 ↑
    exitcode := m.Run() // m.Run でテストを実行し、終了コードを受け取る
    // ↓ 後処理 ↓

    server.Close() // サーバーを閉じる
    os.Exit(exitcode)
}

func handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("hello"))
}

func TestHello(t *testing.T) {
    server.Config.Handler = http.HandlerFunc(handler)

    resp, _ := http.Get(server.URL) // server.URL でURLを取れる

    body, _ := ioutil.ReadAll(resp.Body)
    if string(body) != "hello" {
        t.Errorf("body: %s", string(body))
    }
}