この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
丹内です。過去数回の合わせ技です。
環境
- プロジェクト名: 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力(りょく)を高めていきたいです。