goのtesting.Mを使った関数がpackage単位のfixtureとして使えるか調べてみる

goのtesting.Mを使った関数がpackage単位のfixtureとして使えるか調べてみる。例えばdbのリソースの取得・破棄などの処理を少なくともpackage単位で行うようにしたい(単にtesting.Tをとるテスト関数のdeferでやってしまった場合には関数毎にリソースの取得・破棄が行われてしまう)。

testing.M ?

testing.Mが使えそう。

https://golang.org/pkg/testing/#hdr-Main

It is sometimes necessary for a test program to do extra setup or teardown before or after testing. It is also sometimes necessary for a test to control which code runs on the main thread. To support these and other cases, if a test file contains a function

func TestMain(m *testing.M)

そして記憶が確かなら、gotestはpackage単位でbuildして実行を繰り返す。みたいな形になっていたはず。なのでMainと言いつつpackage単位でのリソースの破棄などに使えそうな感じ。

実験

実際に実験してみる。以下の様な雑な構造のパッケージを用意する。foo,foo/bar,foo/booのパッケージのそれぞれでtesting.Mを利用した関数の出力を見てみる。

$ tree
.
├── bar
│   ├── bar0_test.go
│   ├── bar1_test.go
│   └── main_test.go
├── boo
│   ├── boo0_test.go
│   ├── boo1_test.go
│   └── main_test.go
├── foo0_test.go
├── foo1_test.go
├── main_test.go
└── readme.md

2 directories, 10 files

それぞれ以下の様な関数を定義してみた。(下の例はfooのもののみ)

foo/foo0_test.go

package foo

import "testing"

func TestFoo0(t *testing.T) {
    t.Log("0")
}

foo/main_test.go

package foo

import (
    "fmt"
    "reflect"
    "testing"
)

func TestMain(m *testing.M) {
    m.Run()
    fmt.Printf("end tests=%#+v\n", reflect.ValueOf(m).Elem().FieldByName("tests"))
}

以下の様な形でログが出力されるようなら大丈夫そう。

setup main
  test 0
  test 1
teardown main

実験結果

実際に試した結果。良い感じ。

$ go test -v ./...
=== RUN   TestFoo0
--- PASS: TestFoo0 (0.00s)
    foo0_test.go:6: 0
=== RUN   TestFoo1
--- PASS: TestFoo1 (0.00s)
    foo1_test.go:6: 1
PASS
end tests=[]testing.InternalTest{testing.InternalTest{Name:"TestFoo0", F:(func(*testing.T))(0x4e71c0)}, testing.InternalTest{Name:"TestFoo1", F:(func(*testing.T))(0x4e7240)}}
ok      ./foo   0.002s
=== RUN   TestBar0
--- PASS: TestBar0 (0.00s)
    bar0_test.go:6: 0
=== RUN   TestBar1
--- PASS: TestBar1 (0.00s)
    bar1_test.go:6: 1
PASS
end tests=[]testing.InternalTest{testing.InternalTest{Name:"TestBar0", F:(func(*testing.T))(0x4e71c0)}, testing.InternalTest{Name:"TestBar1", F:(func(*testing.T))(0x4e7240)}}
ok      ./foo/bar   0.001s
=== RUN   TestBoo0
--- PASS: TestBoo0 (0.00s)
    boo0_test.go:6: 0
=== RUN   TestBoo1
--- PASS: TestBoo1 (0.00s)
    boo1_test.go:6: 1
PASS
end tests=[]testing.InternalTest{testing.InternalTest{Name:"TestBoo0", F:(func(*testing.T))(0x4e71c0)}, testing.InternalTest{Name:"TestBoo1", F:(func(*testing.T))(0x4e7240)}}
ok      ./foo/boo   0.001s