E2D(End to end - 1)の考察

# はじめに
レイヤードアーキテクチャ、クリーンアーキテクチャ、DIなどのアーキテクチャを使うリポジトリを前提とした話です。

gitOps,CIで単体パッケージだけで完結する単体テストを行う際に依存するモジュールをモックすると思います。
その場合はそのソースコードだけでテストが完結するはずです。

```golang:example_unit
func TestFoo(t *testing.T) {
  ...
  mock := gomock.NewBar(ctrl)
  ...
  target := &Foo{bar: mock}
  target.Do()
  ...
}
```

E2Eテストの場合は実際にmain関数相当のものを立ち上げ、Webサーバーなら実際にhttpリクエストを送信してレスポンスの検証等をするために、に依存するDBなど外部ミドルウェアのmockサーバーが起動されlistenするようにクライアントをバインドする構成が多いと思います。

```golang:example_e2e
func TestE2E(t *testing.T) {
    db := newDB()
    target := &Target{DB: db}
    mux := http.NewServeMux()
    mux.HandleFunc("/", target.Func)
    s := &http.Server{
        Addr: ":8080",
        Handler: mux,
    }
    go func() {
      s.ListenAndServe()
    }()
    res, err := http.Get("http://localhost:8080")
    ...
}
```

この場合は外部サーバへのオーバーヘッドや外部サーバの立ち上げ時間など単体テストよりもリソースコストやCI時間が長くなりがちです。

DBへのアクセス時間にオーバヘッドの大部分が存在するのでいっそのことその末端部分だけmockにすることで全体コストの削減が図れないかなという考察をこの記事では記載します。

`E2E(End to end)`の末端(2個目のE)の手前(`D->E`)という意味で`E2D`と名付けました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12060/3e27754b-78a4-3451-e83f-638add6960dd.png" width=100% />

# 各パターンでの説明
説明に使うソースコードはここに置いてます。

https://github.com/junpayment/e2dlabo

## シンプルパターンでの説明
[説明するソースコードのディレクトリ](https://github.com/junpayment/e2dlabo/tree/main/simple)

ごく単純化して説明するために構造だけ示したソースコードで説明します。
このディレクトリには`lib1〜3`それぞれ下記のような構造のパッケージが記述されています。

```golang:lib1/lib1.go
package lib1

import "log"

//go:generate mockgen -source=lib1.go -destination=mocks/lib1.go -package=mocks

type Lib2 interface {
	Do() error
}

type Lib struct {
	Lib Lib2
}

func (l *Lib) Do() error {
	_ = l.Lib.Do()
	log.Println(`this is lib1`)
	return nil
}
```

そしてmain関数でDIしています。
`lib1->lib2->lib3`の順番に依存させるコードです。

```golang:main.go
package main

import (
	"e2dlabo/simple/lib1"
	"e2dlabo/simple/lib2"
	"e2dlabo/simple/lib3"
	"log"
)

func main() {
	lib := lib1.Lib{
		Lib: &lib2.Lib{
			Lib: &lib3.Lib{},
		},
	}
	err := lib.Do()
	if err != nil {
		log.Fatal(err)
	}
	log.Println("yeah")
}
```

これはE2Dテストコードです。
`lib1->lib2->lib3のモック`という依存のさせ方で末端パッケージをモックさせるテストを実現しました。
要は**依存するパッケージのうちの末端パッケージだけmock化**するということです。
かなり単純化していますが、やりたいことはこういうことです。

```golang:e2d/e2d_test.go
package e2d

import (
	"e2dlabo/simple/e2d/mocks"
	"e2dlabo/simple/lib1"
	"e2dlabo/simple/lib2"
	"log"
	"testing"

	"github.com/golang/mock/gomock"
)

func Test(t *testing.T) {
	ctrl := gomock.NewController(t)
	lib3Mock := mocks.NewMockLib3(ctrl)
	lib3Mock.EXPECT().Do().DoAndReturn(func() error {
		log.Println("this is mock")
		return nil
	})
	lib := lib1.Lib{
		Lib: &lib2.Lib{
			Lib: lib3Mock,
		},
	}
	err := lib.Do()
	if err != nil {
		log.Fatal(err)
	}
	log.Println("yeah")
}
```

## 現実世界での説明
前述したものはかなり単純化した例だったので今度は実際にwebサーバを実装した例で検証してみます。
[説明に使うディレクトリはこちら](https://github.com/junpayment/e2dlabo/tree/main/real_world)

ディレクトリは下記のような構成になっており前セクションと同じく各パッケージはinterfaceで通信することを前提としていて、`cmd/web.go`でDIをされます。

```
real_world/
  |- cmd
  |   `- web.go
  |- e2d
  |   `- e2d_test.go
  |- handlers       # webサーバのハンドラ層
  |   `- yeah.go
  |- services       # ビジネスロジック的な層(usecaseに近い)
  |   `- user.go
  |- infrastructures # DBのラッパー層(adapterに近い)
  |   `- db.go
  `- models         # domain層的なところ
      `- user.go
```

下記のようにmain.goでDIされます。
前述と違うのは、`infrastructures.NewDB`によって具体的なDBクライアントが作られてしまうので、以降の処理はDBへの接続オーバーヘッドが発生してしまう点です。

```golang:cmd/web.go
package main

import (
	"e2dlabo/real_world/handlers"
	"e2dlabo/real_world/infrastructures"
	"e2dlabo/real_world/services"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "ok"})
	})
	db, err := infrastructures.NewDB(`user:password@tcp(0.0.0.0:3306)/real_world`)
	if err != nil {
		log.Fatalln(err)
	}
	h := &handlers.Yeah{
		UserService: &services.UserService{
			DB: db,
		},
	}
	r.GET("/me", h.Me)
	err = r.Run(":3000")
	if err != nil {
		log.Fatalln(err)
	}
}

```

こちらはE2Dテストです。
`database/sql`の`*sql.DB`をmockすることで外部DBへのオーバーヘッド予防を実現しています。

```golang:e2d/e2d_test.go
package e2d

import (
	"e2dlabo/real_world/handlers"
	"e2dlabo/real_world/infrastructures"
	"e2dlabo/real_world/services"
	"net/http"
	"net/http/httptest"
	"regexp"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func Test(t *testing.T) {
	db, mock, _ := sqlmock.New(sqlmock.MonitorPingsOption(true))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users WHERE id = ? LIMIT 1`)).WillReturnRows(
		sqlmock.NewRows([]string{"id", "name", "phone_number"}).AddRow("hoge", "hoge", "09012345678"))
	d := &infrastructures.DB{
		DB: db,
	}
	target := &handlers.Yeah{
		UserService: &services.UserService{
			DB: d,
		},
	}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	target.Me(c)
	assert.Equal(t, http.StatusOK, c.Writer.Status())
}
```

## google/wireを使った発展系
[wire](https://github.com/google/wire)を知っていますか？
実際のプロダクションコードは規模が大きいので、前述したmain関数のDIくらいの記述量で済むことはありません。
とても長いmain関数のコードが並んでしまいます。
それを解決するのがwireです。

このように依存するオブジェクトを生み出す`Provider`関数を列挙することで冗長コードを省略しつつDIを実現します。
```golang
wire.Build(
  handlers.New,
  services.New,
  infrastructures.New,
)
```

詳しくは[チュートリアル](https://github.com/google/wire/blob/main/_tutorial/README.md)を参照してみてください。

今回の`real_world`のコードも下記のようにwireを使ってDIすることができます。
[説明しているソースコードはこちらです](https://github.com/junpayment/e2dlabo/tree/main/real_world/wire)
前述の例と同じ感じで`handlers -> services -> infrastructures -> dbクライアント`の順に依存しています。

```golang
func Initialize(dsn string) (*handlers.Yeah, error) {
    wire.Build(
    	handlers.NewYeah,
    	wire.Bind(new(handlers.UserService), new(*services.UserService)),
    	services.NewUserService,
    	wire.Bind(new(services.DB), new(*infrastructures.DB)),
    	infrastructures.NewDB,
   )
　　　　　　return nil, nil
}
```

では、**webサーバーで使うDIとE2Dテストで使うDIの共通部分を切り出して再利用**してみましょう。
こちらはDI共通部分です。
wireではこのように共通化させたいDIを`wire.NewSet`メソッドで定義することができます。

```golang:wire/injector.go
package wire

import (
	"e2dlabo/real_world/handlers"
	"e2dlabo/real_world/services"

	"github.com/google/wire"
)

var EndSet = wire.NewSet(
	handlers.NewYeah,
	wire.Bind(new(handlers.UserService), new(*services.UserService)),
	services.NewUserService,
)
```

ここで言う共通部分とは、`handlers -> services -> infrastructures`までです。
コード上は`services.NewUserService`までしか書かれていませんが、実際の引数は`interfaceの実装=infrastructures層のDB`をセットする予定なので分かりにくいですが暗黙的に含まれます。

```
func NewUserServices(db services.DB)
```

次はwebサーバーとしてのDIです。
`dsn=Data source name`を取得すると内部でDBクライアントを生成してくれる`infrastructures.NewDB`を末端として定義しています。

```golang:web/wire/injector.go
//go:build wireinject
// +build wireinject

package wire

import (
	"e2dlabo/real_world/handlers"
	"e2dlabo/real_world/infrastructures"
	"e2dlabo/real_world/services"
	wire2 "e2dlabo/real_world/wire/wire"

	"github.com/google/wire"
)

func Initialize(dsn string) (*handlers.Yeah, error) {
	wire.Build(
		wire2.EndSet,
		wire.Bind(new(services.DB), new(*infrastructures.DB)),
		infrastructures.NewDB,
	)
	return nil, nil
}
```

こちらがE2Dテストで使うDIです。
`dabase/sql`の`*sql.DB(DBクライアント)`を末端として取り扱うようにしています。

```golang:e2d/e2d_test.go
//go:build wireinject
// +build wireinject

package wire

import (
	"database/sql"
	"e2dlabo/real_world/handlers"
	"e2dlabo/real_world/infrastructures"
	"e2dlabo/real_world/services"
	wire2 "e2dlabo/real_world/wire/wire"

	"github.com/google/wire"
)

func Initialize(db *sql.DB) (*handlers.Yeah, error) {
	wire.Build(
		wire2.EndSet,
		wire.Bind(new(services.DB), new(*infrastructures.DB)),
		newDB,
		wire.Bind(new(infrastructures.SqlDB), new(*sql.DB)),
	)
	return nil, nil
}

func newDB(db infrastructures.SqlDB) *infrastructures.DB {
	return &infrastructures.DB{
		DB: db,
	}
}
```

そしてテストコードでは[go-sqlmock](https://github.com/DATA-DOG/go-sqlmock)で作った`*sql.DB`をセットすることで末端をmockにしています。

```golang:e2d/e2d_test.go
package e2d

import (
	"e2dlabo/real_world/wire/e2d/wire"
	"net/http"
	"net/http/httptest"
	"regexp"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func Test(t *testing.T) {
	db, mock, _ := sqlmock.New(sqlmock.MonitorPingsOption(true))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM users WHERE id = ? LIMIT 1`)).WillReturnRows(
		sqlmock.NewRows([]string{"id", "name", "phone_number"}).AddRow("hoge", "hoge", "09012345678"))
	target, _ := wire.Initialize(db)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	target.Me(c)
	assert.Equal(t, http.StatusOK, c.Writer.Status())
}
```

# 最後に
E2Dテストという考え方はいかがでしたか？
E2Dテストによって下記のような効能が出てくると思います。
- ソースコードだけでテストが完結するけど、E2Eテストに近いことができる。
- 外部サーバに依存しないので同時実行するテスト同士が干渉しない。
- テスト時間、CIコストを節約できる。
- DIを再利用できるwireと親和性高いんではないか？

Enjoy！