Goのモックオブジェクトと自動生成

## はじめに
今回はモックオブジェクトについての説明と、Goではどのように実装できるかご紹介したいと思います。
その実装をサポートするための[simplemock](https://github.com/theoden9014/simplemock)というモック自動生成ツールを開発したので、ついでにその紹介もさせていただきます。


## モックオブジェクトとは？

ユニットテストを実行時、制御できないような外部コンポーネントに依存している場合、その箇所を代用品に置き換えてテストをすることが多いと思います。
そのようなテストにおける代替品のことを総じてテストダブル（Test Double）といいます。
例えば、クラウドのエミュレータもフェイクオブジェクトと呼ばれるテストダブルのうちの一つです。
テストダブルについては[xUnit](http://xunitpatterns.com/Test Double.html)に詳しくまとまっているので、気になる方は参照してください。

複数あるテストダブルの中でモックオブジェクトとは、テスト対象の間接出力を検証できるオブジェクトのことを指します。このモックオブジェクトは通常、テストダブルの中の一つであるテストスタブの機能も含まれています。テストスタブとはテスト対象に間接入力を行うものです。



## Goにおけるモックオブジェクト

例えば以下のような簡単な実装があるとして`Service#GetUser`を検証対象としましょう。
`Service#GetUser`はユーザIDを入力として、他のサービスに問い合わせをして`User`を返す仕様です。
他サービスへの問い合わせは`HTTPClient`というインターフェースを使っていて中の実装を隠蔽しています。

今回は`Service`が依存している`HTTPClient`をモックオブジェクトに置き換えて検証できるようにしてみましょう。

```Go
import (
	"context"
	"encoding/json"
	"net/http"
	"net/url"
	"strconv"
)

type HTTPClient interface {
	Do(req *http.Request) (*http.Response, error)
}

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

type Service struct {
	HTTPClient HTTPClient
}

func (s *Service) GetUser(ctx context.Context, id int) (*User, error) {
	u, err := url.Parse("http://userservice/")
	if err != nil {
		return nil, err
	}
	q := u.Query()
	q.Set("id", strconv.Itoa(id))
	u.RawQuery = q.Encode()
	req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
	if err != nil {
		return nil, err
	}
	resp, err := s.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody := struct {
		User *User `json:"user"`
	}{}
	json.NewDecoder(resp.Body).Decode(&respBody)

	return respBody.User, nil
}

```

まず、モックオブジェクトを利用する際、検証ロジックをモックオブジェクトに対して実装する必要があります。

例えば、以下のように`HTTPClient`インターフェースを満たすようなモックオブジェクトの構造体を定義すると、フィールドの関数を差し替えることによってテスト毎に検証ロジックを注入することが可能です。

```go
type httpClientMock struct {
	DoFunc func(req *http.Request) (*http.Response, error)
}

func (m *httpClientMock) Do(req *http.Request) (*http.Response, error) {
	if m.DoFunc != nil {
		return m.DoFunc(req)
	}
	return nil, errors.New("implement Do")
}
```

以下のテストコードでは先ほど定義したモックオブジェクトの構造体を利用して、引数の`*http.Request`を検証しています。

```go
func TestService_GetUser_Request(t *testing.T) {
	id := 10000
	client := &httpClientMock{
		DoFunc: func(req *http.Request) (*http.Response, error) {
			if got, want := req.URL.String(), "http://userservice/?id=10000"; got != want {
				t.Errorf("url: %s, want: %s", got, want)
			}
			return httptest.NewRecorder().Result(), nil
		},
	}
	s := Service{client}
	s.GetUser(context.Background(), id)
}
```

モックオブジェクトで戻り値を定義することでスタブとして利用することもできます。
以下のコードでは[net/http/httptest.ResponseRecorder](https://golang.org/pkg/net/http/httptest/#ResponseRecorder)を使ってHTTPレスポンスを生成しています。

```go
func TestService_GetUser_Response(t *testing.T) {
	client := &httpClientMock{
		DoFunc: func(req *http.Request) (*http.Response, error) {
			w := httptest.NewRecorder()
			respBody := struct {
				User *User `json:"user"`
			}{
				User: &User{
					ID:   1,
					Name: "testuser",
				},
			}
			json.NewEncoder(w).Encode(&respBody)
			return w.Result(), nil
		},
	}
	s := Service{client}
	got, err := s.GetUser(context.Background(), 1)
	if err != nil {
		t.Fatal(err)
	}
	want := &User{ID:1, Name: "testuser"}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("GetUser() got = %v, want %v", got, want)
	}
}
```



## モックオブジェクトのコード自動生成

[github.com/golang/mock](https://github.com/golang/mock)というツールはあるですが、メソッドチェーンを使ってDSL形式のようにスタブの定義をできるようにしている為、実装がやや複雑で利用するにもハードルが少し高い気がします。

今回のように、シンプルな実装を生成するジェネレーターが欲しかったので[simplemock](https://github.com/theoden9014/simplemock)というツールを開発しました。
インターフェースをインプットとしてコードを自動生成するツールです。もしよかったらご利用ください！



## 最後に

このように外部依存をインターフェースを使って実装を隠蔽することによって、中の実装を差し替えることでテスト容易性の高いアプリケーションを開発することができます。
今回はインターフェースを使って差し替えられるようなご紹介をしましたが、状態を持たず外から実装を差し込めるようなクロージャーを使っても良いと思います。

それでは、よいGoライフを！