DBアクセスをスタブしてGolang Echoハンドラの自動テストを書く

この記事は公開されてから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力(りょく)を高めていきたいです。

関連URL