genericsの練習用にgoのテストでerrorやcleanup処理を含むfactory関数を手軽に使うためのライブラリを書いてました
genericsの練習用にお遊びで作ったのですが、意外と便利そうなので紹介してみます。
goのテストコードを書くときに、主にファクトリー関数などで発生するerror値やcleanup処理のハンドリングが面倒なことがあります。それをいい感じに扱えるライブラリです[1]
使い方
使い方はor.Fatal()
を使うのが基本です。この関数は func(...) (T, error)
のような関数の利用を func(...) T
の関数であったかのように扱ってくれます[2]。
type DB struct {}
func NewDB() (*DB, error) { return &DB{}, nil}
func TestIt(t *testing.T){
db := or.Fatal(NewDB())(t)
}
少し呼び出し方が特殊で or.Fatal(t, NewDB())
などではないのは、goの多値の仕様とgenericsの仕様による制限です。個人的にはパッケージ名にorとつけたことで「もしくは失敗」というようなニュアンスで読むことができて気に入っています。
何が嬉しいか?
個人的に感じる嬉しさは以下2つです
- setup部分のコードが減ることで、実際のテストコード部分に集中ができる
- 多段に重なったerror対応などから開放される
読むときに楽になりそうなのが前者で、書くときに楽になるのが後者というイメージです。
多段に重なったerrorの対応と言うのは例えば以下のようなものです。getFoo()でエラーになってもgetBar()でエラーになっても大丈夫です。
func TestFatal(t *testing.T) {
foo := or.Fatal(getFoo())(t)
bar := or.Fatal(getBar(foo))(t)
_ = bar // doSomething
}
ここで、getFoo()は func() (*Foo, error)
という関数で getBarはFooを受け取る func(*Foo) (*Bar, error)
のような関数です。
このような依存部分の値の生成の連鎖の途中部分でエラーになった場合にはそこでテストが止まって失敗してくれます(実際にはtesting.T.Fatal()が呼ばれる)。
例えば以下のような形で失敗します。
$ go test
--- FAIL: TestFatal (0.00s)
example_test.go:24: accessing fail (*main.Bar): hmm
--- FAIL: TestFatalWithCleanup (0.00s)
example_test.go:31: accessing fail (*main.Boo): hmm
FAIL
exit status 1
FAIL github.com/podhmo/or/testdata 0.003s
Fatal()の親戚
or.Fatal()
の他にcleanup関数に対応した or.FatalWithCleanup()
という関数も用意しています[3]。こちらも内部で testing.T.Cleanup()
を呼んであげているので同様の感覚で使えます。
func TestFatalWithCleanup(t *testing.T) {
foo := or.Fatal(getFoo())(t)
boo := or.FatalWithCleanup(getBoo(foo))(t)
_ = boo // doSomething
}
その他細々と関数が用意されていますが、基本的にはこの2つを把握しておけば大丈夫だと思います。
本当にこのパッケージは必要?
と、ここまで作ったパッケージの宣伝をしてきたわけですが、本当にこのパッケージが必要なんだろうか?と疑問に湧く人もいると思います。実際には丁寧にテスト用のパッケージを書いてあげて広義の意味でのfixtureを担うfactory関数だったり、テストヘルパーだったりを書いてあげると不要になります。
わかり易い例で言えば、net/http
に対する net/http/httptest
でしょうか? httptest.NewRequest()
はエラー値を返さないですよね。そんな感じで、今回の例に関しても別途テスト用のfactory関数を書いてあげれば不要になります。
[1]: いわゆるrustで言えば !
のような、あるいはErrorモナドのような何か
[2]: たまにパッケージに存在する Must()
のような関数ですね
[3]: 名付けに自信がないので、もっと良い名前を募集していたりします
Discussion