DBアクセスをスタブしてGolang Echoハンドラの自動テストを書く
丹内です。過去数回の合わせ技です。
環境
- プロジェクト名: echotest
- プロジェクトディレクトリ: ~/dev/echotest
- Golang Version: 1.6.2
- パッケージ管理: godep
$ cat .envrc layout go $ echo $GOPATH /Users/tannai.yuki/dev/echotest:/Users/tannai.yuki/.go
DBから値を読んで返すAPIを作る
$GOPATH/src/echotest/main.go
package main import ( "database/sql" "echotest/handler" "echotest/user" _ "github.com/go-sql-driver/mysql" "github.com/labstack/echo" "github.com/labstack/echo/engine/standard" "github.com/labstack/echo/middleware" ) func main() { db, err := sql.Open("mysql", "root@/gosample") if err != nil { panic(err.Error()) } defer db.Close() e := echo.New() u := userModel.NewUserModel(db) h := handler.NewHandler(u) e.Use(middleware.Logger()) e.Use(middleware.Recover()) e.GET("/users/:id", h.GetUser) e.Run(standard.New(":1323")) }
$GOPATH/src/echotest/handler/handler.go
package handler import ( "echotest/user" "net/http" "strconv" "github.com/labstack/echo" ) type ( handler struct { userModel userModel.UserModel } resultJSON struct { Id string `json:"id"` } ) func NewHandler(u userModel.UserModel) *handler { return &handler{u} } func (h handler) GetUser(c echo.Context) error { id, _ := strconv.Atoi(c.Param("id")) h.userModel.Fetch(id) name := h.userModel.GetName() if name == "" { return echo.NewHTTPError(http.StatusNotFound, "user not found") } return c.JSON(http.StatusOK, resultJSON{Id: name}) }
$GOPATH/src/echotest/user/userModel.go
package userModel import ( "database/sql" ) type ( UserModel interface { Fetch(id int) GetName() string } userModel struct { db *sql.DB name string } ) func NewUserModel(db *sql.DB) *userModel { return &userModel{db: db, name: ""} } func (u *userModel) GetName() string { return u.name } func (u *userModel) Fetch(id int) { if id < 1 { return } err := u.db.QueryRow("SELECT name FROM users WHERE id=? LIMIT 1", id).Scan(&u.name) switch { case err == sql.ErrNoRows: u.name = "" case err != nil: panic(err.Error()) } }
実行してみます。
$ godep go run main.go& [1] 24348 $ curl localhost:1323/users/1 {"time":"2016-06-17T22:38:11+09:00","remote_ip":"::1","method":"GET","uri":"/users/1","status":200, "latency":1946,"latency_human":"1.946374ms","rx_bytes":0,"tx_bytes":15} {"id":"tannai"}%
これでテスト対象のAPIが作れました。
ハンドラに対するテストを書く
$GOPATH/src/echotest/main_test.go
package main import ( "echotest/handler" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo" "github.com/labstack/echo/engine/standard" ) type userModelStub struct { name string } // UserModelインタフェースを満たす実装 func (u *userModelStub) Fetch(id int) {} func (u *userModelStub) GetName() string { return u.name } func TestHandler(t *testing.T) { e := echo.New() req := new(http.Request) rec := httptest.NewRecorder() c := e.NewContext(standard.NewRequest(req, e.Logger()), standard.NewResponse(rec, e.Logger())) c.SetPath("/users/1") u := &userModelStub{name: "dummy tannai"} h := handler.NewHandler(u) h.GetUser(c) if rec.Body.String() != "{\"id\":\"dummy tannai\"}" { t.Errorf("expected response id: dummy tannai, got %s", rec.Body.String()) } }
テストしてみます。
$ godep go test -v === RUN TestUnitGet --- PASS: TestUnitGet (0.00s) PASS ok echotest 0.011s
上記テストのu := &userModelStub{name: "dummy tannai"}
で違う名前を指定すると、テストがfailします。
godep go test -v === RUN TestHandler --- FAIL: TestHandler (0.00s) main_test.go:35: expected response id: dummy tannai, got {"id":"dummy yuki"} FAIL exit status 1 FAIL echotest 0.011s godep: go exit status 1
まとめ
interfaceを活用することで、DBへのアクセスをスタブしてハンドラを自動テストすることができました。
ついに発売されたプログラミング言語Goを買ったので、読んで書いてGo力(りょく)を高めていきたいです。