上京エンジニアの葛藤

都会に染まる日々

初めての Go 言語でテストについて学んだ

概要

初めての Go 言語の テスト の章が勉強になったのでそのまとめです。

まとめ

  • テストは製品版と同じディレクトリ、パッケージに置く
    エクスポートされていない関数や変数にもアクセスしてテストが可能
  • ファイル名は xxx_test.go とする
  • テスト関数は Test という prefix を付けて *testing.T 型の引数を1つだけ取り、名前は t にするのが慣習
    関数のユニットテストは TestMethod と Test のあとに関数名にする
    エクスポートされていない関数の場合 Test_method と _ を間に入れる
package sample

import "testing"

func Test_privateFunc(t *testing.T) {
    res := privateFunc()
    if res != "sample" {
        t.Error("failed", res)
    }
}
  • 検証結果が正しくなければ t.Error を使う
    t.Fatal を使うとテスト関数を終了させられる
  • go test ./... でカレント、サブディレクトリ全て実行
    -v で詳細な出力
$ go test -v
=== RUN   Test_privateFunc
--- PASS: Test_privateFunc (0.00s)
PASS
ok      testing-go/sample       0.110s
  • TestMain という関数を使えば before, after のようなことが実現できる
    ただしテストごとに呼ばれない
package sample_test

import (
    "fmt"
    "testing"
    "testing-go/sample"

    "github.com/google/go-cmp/cmp"
)

func TestMain(m *testing.M) {
    fmt.Println("before")

    m.Run()

    fmt.Println("after")
}

func TestPublicFunc(t *testing.T) {
    expected := sample.Sample{
        FirstName: "first",
        LastName:  "last",
    }
    res := sample.PublicFunc()
    if diff := cmp.Diff(expected, res); diff != "" {
        t.Error(res)
    }
}
go test -v sample2_test.go
before
=== RUN   TestPublicFunc
--- PASS: TestPublicFunc (0.00s)
=== RUN   TestPublicFunc2
--- PASS: TestPublicFunc2 (0.00s)
PASS
after
ok      command-line-arguments  0.120s
package sample

import (
    "testing"

    "github.com/google/go-cmp/cmp"
)

func Test_privateFunc(t *testing.T) {
    expected := Sample{
        Name: "name",
    }
    res := privateFunc()
    if diff := cmp.Diff(expected, res); diff != "" {
        t.Error(res)
    }
}
  • 比較したくないフィールドがある場合はローカル関数として比較関数を定義する
package sample

import (
    "testing"

    "github.com/google/go-cmp/cmp"
)

func Test_privateFunc(t *testing.T) {
    expected := Sample{
        FirstName: "first",
        LastName:  "first",
    }
    comparer := cmp.Comparer(func(x, y Sample) bool {
        return x.FirstName == y.FirstName
    })
    res := privateFunc()
    if diff := cmp.Diff(expected, res, comparer); diff != "" {
        t.Error(res)
    }
}
  • 無名構造体を定義してテーブルテストを行う
  • -cover オプションでコードカバレッジの出力
    -coverprofile=c.out でファイルへの保存
    go tool cover -html=c.out で HTML を生成
$ go test -cover
PASS
coverage: 100.0% of statements
ok      testing-go/sample       0.228s
var blackhole sample.Sample

func BenchmarkPublicFunc(b *testing.B) {
    res := sample.PublicFunc()
    blackhole = res
}
go test ./sample2_test.go -bench=. -benchmem
before
goos: darwin
goarch: arm64
BenchmarkPublicFunc-8           1000000000               0.0000002 ns/op               0 B/op          0 allocs/op
PASS
after
ok      command-line-arguments  0.323s
  • httptest を使えば http request が行われる関数のテストが容易に可能
  • httptest.NewServer は未使用なポートをランダムに使用して HTTP サーバーを serve してくれる
  • go test -short で時間のかかるテストをスキップできる
  • -race ではレースチェッカーを有効にできる

Go での開発経験がなくテスト周りの基礎から学びたかったので内容がちょうど良かったです。
モック、スタブなどもう少し実践的なテクニックの理解が必要な気がしているので他の書籍も合わせて学びたいと思います。