いい記事に感化されて僕も何か書きたくなった。
Golangにおけるinterfaceをつかったテスト技法 | SOTA
Golangにおけるinterfaceをつかったテスト技法 最近何度か聞かれたので自分がGolangでCLIツールやAPIサーバーを書くときに実践してるinterfaceを使ったテスト技法について簡単...
http://deeeet.com/writing/2016/10/25/go-interface-testing/
僕も1つ golang のテストの tips を。golang を書いていて良くあるのが「コマンドがステータス 0 で終了する事」のテスト。
package main
import "os"
func doSomething() {
os.Exit(0)
}
func main() {
doSomething()
}
でも os.Exit
しちゃうとテスト自体が死んでしまうので「テスト出来ない」と思われがち。しかしラッパーを書いてあげる事でテストは出来る。まず os.Exit
を関数リファレンスにしてしまう。
package main
import "os"
var exit = os.Exit
func doSomething() {
exit(0)
}
func main() {
doSomething()
}
次に以下の様なテストツールを書く。ファイル名は main_test.go
package main
import (
"errors"
"fmt"
"testing"
)
type ExitError int
func (e ExitError) Error() string {
return fmt.Sprintf("exited with code %d", int(e))
}
func init() {
exit = func(n int) {
panic(ExitError(n))
}
}
func testExit(code int, f func()) (err error) {
defer func() {
e := recover()
switch t := e.(type) {
case ExitError:
if int(t) == code {
err = nil
} else {
err = fmt.Errorf("expected exit with %v but %v", code, e)
}
default:
err = fmt.Errorf("expected exit with %v but %v", code, e)
}
}()
f()
return errors.New("expected exited but not")
}
第一引数は期待する終了コード。第二引数は関数を呼び出すためのクロージャ。後は以下の様なテストを書くだけ。
func TestExit(t *testing.T) {
err := testExit(0, func() {
doSomething()
})
if err != nil {
t.Fatal(err)
}
}
テストを実行すると
$ go test
PASS
ok _/C_/dev/go-sandbox/exittest 0.080s
今度はコードを修正して
package main
import "os"
var exit = os.Exit
func doSomething() {
exit(1)
}
func main() {
doSomething()
}
$ go test
--- FAIL: TestExit (0.00s)
main_test.go:46: expected exit with 0 but exited with code 1
FAIL
FAIL _/C_/dev/go-sandbox/exittest 0.080s
また log.Fatal
で panic を起こさせても大丈夫。
package main
import (
"log"
"os"
)
var exit = os.Exit
func doSomething() {
log.Fatal("unknown error")
}
func main() {
doSomething()
}
$ go test
2016/10/25 11:34:03 unknown error
FAIL _/C_/dev/go-sandbox/exittest 0.254s
出来ないと思ったらそこで勝負は終わりよ!
注意点としては、os.Exit の実行と同じスコープで recover する処理があると、そちらに拾われてしまうのでその辺が良く分かっている人だけお使い下さい。
あと、もちろんテストしやすい様に、以下の様に実装するのが正しいのです。(上記はそれが出来ない場合の得策です)
package main
import (
"log"
"os"
)
func doSomething() int {
log.Print("unknown error")
return 1
}
func main() {
os.Exit(doSomething())
}