概要
gomock を使ってモックテストを書いてみました
基本的な使い方やポイントを紹介します
環境
- macOS 10.14.4
- go 1.11.5
gomock のインストール
go get github.com/golang/mock/gomock
go install github.com/golang/mock/mockgen
サンプルアプリ
簡単な Web アプリを用意しました
アプリは別に Web アプリでなくても OK です
この中のハンドラからコールされている Hello
インタフェースの関数 Echo()
を gomock を使って mock してみたいと思います
vim $GOPATH/src/github.com/hawksnowlog/a/main.go
package main
import (
"net/http"
)
type Hello interface {
Echo() string
}
type HelloST struct {
Msg string
}
func (st HelloST) Echo() string {
return st.Msg
}
func HelloHandler(w http.ResponseWriter, r *http.Request) {
st := HelloST{"HELLO"}
w.Write([]byte(st.Echo()))
}
func main() {
http.HandleFunc("/", HelloHandler)
http.ListenAndServe(":8080", nil)
}
mock を作成する
mkdir mock
mockgen -source main.go
で標準出力に mock 用のコードが出力されます
問題なければファイルに記載します
mockgen -source main.go -destination mock/mock.go
自動で生成されるコードなので mock.go の内容は省略します
mock を使ったテストを書く
生成した mock を使ってテストを書いてみます
vim $GOPATH/src/github.com/hawksnowlog/a/main_test.go
package main
import (
"fmt"
"github.com/golang/mock/gomock"
"github.com/hawksnowlog/a/mock"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestHello(t *testing.T) {
// mock
ctrl := gomock.NewController(t)
defer ctrl.Finish()
hello := mock_main.NewMockHello(ctrl)
hello.EXPECT().Echo().Return("HELLO!!")
// call api
testserver := httptest.NewServer(http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
w.Write([]byte(hello.Echo()))
}))
defer testserver.Close()
res, err := http.Get(testserver.URL)
if err != nil {
t.Error(err)
}
b, err := ioutil.ReadAll(res.Body)
defer res.Body.Close()
if err != nil {
t.Error(err)
}
fmt.Println(string(b))
if string(b) != "HELLO!!" {
t.Error("error")
}
}
go test github.com/hawksnowlog/a
解説
まず Echo()
関数を mock します
Controller
を作成しそれを元にインタフェースの mock を作成します
mock を作成したらインタフェースにある関数を更に mock します
ctrl := gomock.NewController(t)
defer ctrl.Finish()
hello := mock_main.NewMockHello(ctrl)
hello.EXPECT().Echo().Return("HELLO!!")
関数を mock する際は EXPECT().関数名().Return()
という感じで定義します
今回であれば本来返るべき「HELLO」の代わりに「HELLO!!」を返すように定義します
あとは今回は net/http
のテストなので httptest を使ってハンドラを定義します
ハンドラは実際のインタフェースの関数ではなく mock したインタフェースの関数を使うように定義します
これで実際にサーバにリクエストが行っても mock された関数がコールされるようになります
本当は既存のハンドラをコールしてそこで呼び出している関数を mock したい
本当は以下のようにテスト側でハンドラを呼び出して
testserver := httptest.NewServer(http.HandlerFunc(HelloHandler))
main.go のハンドラで呼び出されている st.Echo()
の代わりに mock の hello.Echo()
を呼び出してほしかったのですができませんでした
func HelloHandler(w http.ResponseWriter, r *http.Request) {
st := HelloST{"HELLO"}
w.Write([]byte(st.Echo()))
}
他の言語の話の出すのはどうかと思いますがイメージとしては ruby の rspec-mock のようにアプリ側の呼び出しをそっくりそのまま mock が返すようにしたかった感じです
今回の場合だとハンドラを結局再定義しているので、そこが微妙な点かなと思います
最後に
gomock を使ってモックテストを書いてみました
とりあえず使い方は理解できたかなと思います
net/http
の場合は結局ハンドラを再定義する必要があったのでそこが何とも微妙な感じでした
また interface を元に mock となる構造体を作成するので interface がない場合は今回の方法を使うのは厳しいかなと思います
0 件のコメント:
コメントを投稿