Goで標準パッケージのみでtest coverage 100%を維持し続けるための開発ガイド


# 前提
この記事は、Goでテスト駆動で開発をしていく上で、`標準パッケージのみで`、ユニットテストのカバレッジを常に100%を維持しながら開発していくための基本ガイドとなります。
「関数をMockしてテストしていく方法」を主に紹介し、最後に「どうすればtestableなコードを書けるか」という話をしております。

### なぜ標準パッケージだけでやるのか
[Goの思想](https://golang.org/doc/faq#testing_framework) に則ると、標準の`Testing`パッケージだけでテストすることが理想だからです。
Goの`Testing`パッケージは、他言語のメジャーなテストライブラリに比べるとかなり機能が少ないものの、それゆえに`Goの原則さえ理解していれば誰でもすぐに読み書きでき`、`Testに合わせてコードを書いていかないといけないからこそ逆説的に適切なコードを書ける`というメリットがあります。

### テストカバレッジの基準とは
Goのテストカバレッジの分母は、基本的に `C0`または`命令網羅率(statement coverage)`と呼ばれる`命令をどれくらい網羅しているか`となります。
ここでいう、`命令`とは、`条件分岐を除いた、なんらかの処理を実行している部分`となります。

詳細は、こちら。
Goのテストカバレッジの考え方と計測方法はこちら。
[Try Golang! Go標準のカバレッジとはなにものか](https://medium.com/veltra-engineering/what-is-the-coverage-of-golang-standard-b2bffdd0596)

また、テストのカバレッジを測定するコマンドはこちら。
[テストカバレッジの測定 はじめてのGo言語](http://cuto.unirita.co.jp/gostudy/post/coverage/)


# 関数をMockする方法
Goの標準のTestingパッケージには、Mockingなどの他のライブラリによくある機能がないので、コードを書く時点からどうやってテストするかを考えながら書いていく必要があります。
つまり、**どうやってMockしていくか**を常に考えていく必要があります。

例えば、下記のような処理を見てみましょう。
下記の`GetData`という関数をテストする場合、中で使用されている`GetItem`という関数をMockする必要があります。
なぜなら、コメントアウトにある通り、`GetItem`は、DBに実際にSQLを走らせる関数なので、ユニットテストでそのまま走らせるわけにはいかないからです。
また、この`GetItem`の直後にエラーハンドリングがあるので、エラーハンドリングの処理もテストで網羅するために、エラーが返却されるケースのMockも必要になります。

```go
func (s *Server) GetData(query string) ([]string, error){
	d, err := s.db.GetItem(query) // 内部で実際にDBに接続してSQLを走らせる関数
	if err != nil {
		return nil, err
	}
	return d, nil
}
```

それでは、どうすれば良いか実際に見ていきましょう。

## 1. 関数をinterfaceに定義する
まず。GoにおけるテストのMockingの一番基本的なやり方である`interface`を使った方法を紹介していきます。

Goにおける`interface`は、「実装したオブジェクトが使用できる関数(振る舞い)と入出力の型のみをまとめて定めたもの」といえます。また、`関数の型だけを取り決めており、その関数の中身の挙動には一切関与しない`ということもいえます
そして、あるinterfaceを実装したオブジェクトは、interfaceに定義された関数を`具体的な挙動`とともに実装しています。
テストでは、このinterfaceの特性を活かし、本番用のtypeとは別に、**テスト用のオブジェクトにinterfaceを実装させて、関数のダミーの挙動を定義する**ことでMockしていきます。
そして、そのMockの関数を呼び出す側は、具体的なtypeではなく、**interface(を実装したオブジェクト)をレシーバに関数を実行する**ようにすることで、そのinterfaceを実装したオブジェクトならなんでも受け付けるようにしておきます。

文章だけで説明してもぴんと来ないと思うので、以下で具体的な例を見ていきましょう。

## a. interfaceをStructのフィールドとして持つ
先程のコードに戻ってみます。
関連するstructやinterfaceも追加しました。

```go

type Server struct{
  db datagetter
}

func (s *Server) GetData(query string) ([]string, error){
	d, err := s.db.GetItem(query) // 内部で実際にDBに接続してSQLを走らせる関数
	if err != nil {
		return nil, err
	}
	return d, nil
}

type DB struct{}

type datagetter interface{
  GetItem(string) ([]string, error)
}

func GetItem(q string) ([]string, error){
 	// DBからDataを取得して返却 下記のようなイメージ
 	// 1.queryを整形
 	// 2.整形したqueryとでレシーバのstructが持っているconnectionを利用して、DBからデータを取得
	// 3.取得したデータを整形して返却
}

```

ここで注目すべきは、interfaceである`datagetter`を、structである`Server`が中のフィールドの型として使用している点です。
つまり、ここの`Server`の中の`db`というフィールドには、`datagetter`を実装したオブジェクトなら`なんでも入る`ということになります。
こうすることで、テストの際には、`db` というフィールドに `datagetter`を実装したダミーのオブジェクトを入れることで、`datagetter`が宣言している`GetItem` という関数の挙動をテスト用のものに変更することができます。

そして、次にこの`GetData`のMock用のstructと関数を作ってみましょう。

```go
// DBに接続して実行するメソッドをmockするためのstruct
type MockDB struct{}

// テスト用にMockした関数
func (*MockDB) GetItem(string) ([]string, error) {
	// returns dummy response
	return []string{"test"}, nil
}

```


```go

func TestGetData(t *testing.T) {
	cases := []struct {
		input    string
		expected []string
	}{
		{"test", []string{"test"}},
	}

	// mock
	// MockDBをServerの項目として持たせる
	s := &Server{
		db: &MockDB{},
	}

	for _, tc := range cases {
		// execute
		d, err := s.GetData(tc.input)

		// assert
		if !reflect.DeepEqual(d, tc.expected) {
			t.Errorf("failed handling valid cases, expected: '%#v', actual: '%#v'", tc.expected, d)
		}
		if err != nil {
			t.Errorf("failed handling valid cases, '%d'", err)
		}
	}
}

```

[The Go Playground](https://play.golang.org/p/6JcdfzoUbhN)

上記と同様にこのinterfaceを活用することで、エラーケースのテストも実施することができます。

```go
// DBに接続して実行するメソッドをmockするためのstruct
type MockDBError struct{}

// テスト用にMockした関数
func (*MockDBError) GetItem(string) ([]string, error) {
	// returns dummy response
	return nil, fmt.Errorf("error in GetItem")
}

```

```go

func TestGetDataError(t *testing.T) {
    // set up
	cases := []struct {
		input    string
		expected []string
	}{
		{"test", nil},
	}

	// mock
	// MockDBをServerの項目として持たせる
	s := &Server{
		db: &MockDB{},
	}

	for _, tc := range cases {
		// execute
		d, err := s.GetData(tc.input)

		// assert
		if !reflect.DeepEqual(d, tc.expected) {
			t.Errorf("failed handling valid cases, expected: '%#v', actual: '%#v'", tc.expected, d)
		}
		if err == nil {
			t.Errorf("failed handling invalid cases, error was not found when expected")
		}
	}
}
```

[The Go Playground](https://play.golang.org/p/gSeP3DTOtZ8)

このように、**Mockしたい関数をinterfaceに定義して、そのinterfaceをstructのフィールドとして埋め込む**ことで、関数のテストができるようになります。

### b. Mockしたinterfaceを関数の引数に取る
上で紹介したのは、`interfaceをStructのfieldの一つとして持たせることで、そのStructを引数かレシーバに取る関数が内部で使用している関数をMockする`というやり方でした。
ただし、このやり方では、レシーバを取らない関数をMockできません。
例えば、structの初期化の関数などはレシーバは取ろうにも取れないことが多いと思います。

そこで、そのような場合は、`interfaceを関数の引数に持つ` ことで関数を必要に応じてMockできます。

```go

type Server struct {
	db DB
}

type ConnectionMaker interface {
    MakeDBConnection() (DB, error)
}

// 引数にInterfaceを取る
func InitializeServer(cm ConnectionMaker) (*Server, error) {
    db, err := cm.MakeDBConnection() // Mockしたい関数
	if err != nil {
		return nil, err
	}

	s := &Server{
		db: db,
	}
	return s, nil
}

```

上記の`InitializeServer`は、引数に`ConnectionMaker`というinterfaceを取っているため、`このinterfaceを実装したオブジェクトならなんでも引数として受け付ける` ということになります。
つまり、前項と同様に、今度は引数にMock用のオブジェクトを入れることでテストができるということになります。

## 2. type funcを利用する
ここまででinterfaceを使ってMockする方法を紹介しました。基本的に、interfaceでMockできるところはそうするべきですが、interfaceを使うと冗長になって、可読性が下がる場合があります。
その際の方法として、`type func`を利用する方法もあります。
つまり、Goの、**関数をオブジェクトとして扱える性質**を利用して、**テスト対象の関数の引数またはレシーバに関数を持たせる**ことで、外からmockした関数を投げてテストすることができるようにするということです。

実際の例を見てみましょう。

```go

// 関数をオブジェクトとして扱うためにtypeとして定義する
type ConnectionFunc func() (DB, error)

func InitializeServer(connect ConnectionFunc) (*Server, error) {
	// 引数として受け取った関数を実行する
	db, err := connect()
	if err != nil {
		return nil, err
	}

	s := &Server{
		db: db,
	}
	return s, nil
}

// 本番用の関数
func connect() (DB, error) {
//   	// DBとの接続を確立して返却 下記のようなイメージ
//	// 1.環境変数などからポートなどのデータベース接続用の情報を読み込み
//	// 2.読み込みんだ情報を利用して接続を試行（エラーなら返却）
//	// 3.成功したらコネクションをオブジェクトとして返却
}


```

上記の`InitializeServer`という関数は、関数(をtypeとして定義したもの)を引数として取って中で実行しているので、引数として投げる関数次第でその挙動を変えることができるということになります。
そして、その引数の、`ConnectionFunc` というtypeは、`func() DB, error`という形の関数ならなんでも良いので、Mockする関数もこの形を取れば良いということになります。

例えば、本番ではこのように使うとして、

```go
func main() {
    s := InitializeServer(connect)
}


```

テストでは、このようにmock用の関数を用意して、テストすることができます。

```go
// 正常系のMock
func MockConnect() (DB, error) {
	return "mocked db", nil

}

// 異常系のMock
func MockFailConnect() (DB, error) {
	return "", fmt.Errorf("mocked failure")
}

```

```go

func TestInitializeServerSuccess(t *testing.T) {
	// execute(引数にMock関数を渡している)
	s, err := InitializeServer(MockConnect)

	// assert
	if s == nil { // checking if Server is initialized(= not nil)
		t.Errorf("failed handling with a valid case, return value was nil when expected")
	}
	if err != nil {
		t.Errorf("failed handling valid cases, '%d'", err)
	}
}

func TestInitializeServerFail(t *testing.T) {
	// execute(引数にMock関数を渡している)
	s, err := InitializeServer(MockFailConnect)

	// assert
	if s != nil { // checking if Server is not initialized
		t.Errorf("failed handling with a invalid case, unexpected return value:'%#v'", s)
	}
	if err == nil {
		t.Errorf("failed handling a invalid case, error was not found when expected")
	}
}

```

[The Go Playground](https://play.golang.org/p/HUkzgCtHMwC)

# Mockを使ったテストのやり方
前項のまとめとして、あらためて実際にどのようにMockを作ってテストをするかということを考えていきましょう。

## Step1. Mock作成用の関数をテストパッケージ用に公開する
前項の例では、便宜的にすべて同じパッケージ内の想定で説明していました。
それでも問題はないのですが、Goでは、**テスト対象のパッケージとは異なるテスト用のパッケージからテストする**のがベストプラクティスという考え方もあります。それは、**パッケージのユーザの視点からテストする**ためです。
しかし、外部のパッケージからテストするとなると、`パッケージ内のprivateな関数やstructのfieldはテストケースから直接参照することができない`ということになります。
例えば、最初に例にあげたコードだと、下記の`db`は、`Server`のprivate fieldなので外からは参照できません。

```go
package datagetter

func (s *Server) GetData(query string) ([]string, error){
	d, err := s.db.GetItem(query) 
	if err != nil {
		return nil, err
	}
	return d, nil
}
```

なので、この場合は、テスト用にMockした関数を実装した`db`を、あらかじめ作って外部向けに公開することになります。
具体的には、以下のようになります。

```go
package datagetter

// MakeServerWithConnectionError initializes and return server with MockDBConnection
func MakeServerWithMockDBConnection() *Server {
	return &Server{
		db: &MockDB{},
	}
}

// MakeServerWithConnectionError initializes and return server with MockDBConnection
func MakeServerWithConnectionError() *Server {
	return &Server{
		db: &MockDBError{},
	}
}
```

## Step2. Mockを呼び出してテストをする
そして、テストする側は、publicに提供されたツールを利用することで外部パッケージからでもMockされた関数が利用できることになります。

```go
package datagetter_test

import (
    "datagetter"
)

func TestGetData(t *testing.T) {
	cases := []struct {
		input    string
		expected []string
	}{
		{"test", []string{"test"}},
	}

	// mock
	// 提供されたメソッドを利用してテスト用のオブジェクトを取得する
	s := datagetter.MakeServerWithMockDBConnection()

	for _, tc := range cases {
		// execute
		d, err := s.GetData(tc.input)

		// assert
		if !reflect.DeepEqual(d, tc.expected) {
			t.Errorf("failed handling valid cases, expected: '%#v', actual: '%#v'", tc.expected, d)
		}
		if err != nil {
			t.Errorf("failed handling valid cases, '%d'", err)
		}
	}
}
```


# テストできる(=Mockできる)コードを書く方法
最後に、これまでのテストのMockの方法を踏まえて、どうやってテストできるコードを書いていくべきかという話をします。
Goでtest coverage 100%を維持し続けるためには、そもそもテストを書きながら(少なくともテストすることを考えながら)コードを書いていくテスト駆動の考え方が必要になります。

そして、そのテストできるコード(関数)とは、下記のいずれかを満たしているということになります。

**1. そもそもMockしないでそのまま走らせて良い関数**
**2. interfaceを実装していてMockできる関数**
**3. 引数にinterfaceを実装したオブジェクトを渡すことでMockできる関数**
**4. レシーバか引数でtype funcを渡すことでMockできる関数**

例えば、最初に例として出した`GetData`の関数も、下記のような作りだったら、上の1-4のいずれも満たしておらず、テストが難しくなります。

```go
import (
	"db" // DB操作パッケージ
)
func (s *Server) GetData(query string) ([]string, error){
	d, err := db.GetItem(query) // dbは外部からimportしたパッケージのレシーバを取らない関数
	if err != nil {
		return nil, err
	}
	return d, nil
}
```

まず、`GetData`の中の`GetItem`は、DBを操作する処理なのでテストでそのまま走らせていい関数ではありません。また、後続にエラーハンドリングがあるので、テストによってはエラーが返ってくるようにする必要もあります。
そして`GetItem`という関数は、この`db`という外部パッケージのレシーバを持たない関数として持っています。
その上で、引数にはふつうのstringを取っているため、外部から関数の挙動を制御する方法がありません。(この引数のstringに特定の値("test"とか)の場合だけ特殊な処理をするみたいなこともできますが、それは可読性や安全性などの観点から好ましくないのは明らかでしょう。)
このような場合、**このコードの書き方がTestableじゃないコードということになるので、`GetItem`を定義したinterfaceを用意するか、引数を変更するなどの対応が必要になります**。

# 補足
ここまで説明したのが、「標準パッケージのみでテストのカバレッジを満たしながら開発していく」ための方法となります。この記事で書いた方法で、基本的に開発は進められると思います。
ただし、この記事では、テストのカバレッジを満たしていくことに重きを置いているため、可読性などの視点でより良いコードを書いていく方法は最低限しか触れていません。
実際には、「原則に則ってカバレッジを100%満たしたコードを書いているけど、コードの行数が膨大かつ冗長で読みづらい」みたいな状況にはしばしば陥りがちだと思います。
それらに対処する方法は大小いくつかあると思いますが、例えば[sub test](https://blog.golang.org/subtests)がその一つだと思います。
なので、実際にそれなりの規模のものを作る上では、そのあたりの方法も別途頭に入れる必要があるとは思います（そのうちそういう記事も書こうかなと思います）。

# まとめ
### Goの標準パッケージのみでテストする際に、関数をMockする方法

**1. 関数をinterfaceに定義する**
**2. type funcを関数の引数かレシーバに持つ**

### test coverage100%を達成する上で、関数が満たしていないといけないこと(いずれか一つ)

**1. そもそもMockしないでそのまま走らせて良い関数**
**2. interfaceを実装していてMockできる関数**
**3. 引数にinterfaceを実装したオブジェクトを渡すことでMockできる関数**
**4. レシーバか引数でtype funcを渡すことでMockできる関数**


# 参考
- [Advanced Testing with Go](https://speakerdeck.com/mitchellh/advanced-testing-with-go)
- [Golangにおけるinterfaceをつかったテスト技法](https://deeeet.com/writing/2016/10/25/go-interface-testing/)
- [Try Golang! Go標準のカバレッジとはなにものか](https://medium.com/veltra-engineering/what-is-the-coverage-of-golang-standard-b2bffdd0596)
- [テストカバレッジの測定 はじめてのGo言語](http://cuto.unirita.co.jp/gostudy/post/coverage/)
- [Why does Go not have assertions? ](https://golang.org/doc/faq#assertions)
- [sub test](https://blog.golang.org/subtests)