【Go】Golandでテストから書く開発

# 前書き
- 私のPC & OS

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/ef842cf7-3975-73bf-bfad-cd7df7dcf9a4.png)

- 今回のソースコードはこちら
https://github.com/wataboru/golang-test-driven-dev

# 前準備

## プロジェクト作る
- Golandにて、`File -> New -> Project...`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/a168c740-6b8b-0f15-bab2-76818a18bcbb.png)

## ディレクトリ作成
今回はサンプルとして型変換処理を作成します。
以下の通りディレクトリを作成

```
- goalng-test-driven-dev(root)
  ∟ pkg
    ∟ convert
```

## 処理の定義を行う
後ほどテストを記述する際に、Golandが補完してくれて便利なので、先に定義してしまいましょう。

1. `/pkg/convert`にGoファイルを作成。
2. 今回はstring型へ型変換処理を行う関数を作成
3. 引数の型は自由とし、戻り値の型はstringとする。
4. とりあえずコンパイルが通る様に無理やりstringを返却する。

```convert.go
package convert

func ToString(v interface{}) (string) {
	return ""
}

```

# テスト駆動で開発してみる

## テスト書く

![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/d11d3a27-dcd4-5c7c-e5a4-88fea2d8f485.gif)

**Golandはテストファイルの自動生成が便利です。**
以下の通り利用しましょう

1. テストしたいfuncやファイル内にフォーカスした状態で、`Cmd + Shift + T`
2. `Test for function` や `Test for file`等、自動生成したい範囲を選択
3. 以下の通り自動生成されたら、`// TODO:`にケースを追記していく
4. 今回はとりあえず、期待値としてstring,bool,intを受け取る様にした。

```convert_test.go
package convert

import "testing"

func TestToString(t *testing.T) {
	type args struct {
		v interface{}
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{name: "String", args: args{v: "hoge"}, want: "hoge"},
		{name: "Int", args: args{v: 1},want: "1"},
		{name: "Bool", args: args{v: true}, want: "true"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := ToString(tt.args.v); got != tt.want {
				t.Errorf("ToString() = %v, want %v", got, tt.want)
			}
		})
	}
}
```

## テストを実行する
テストを書いたらすぐに実行しましょう。
もちろん、まだ処理を書いていないのでエラーが発生するはずですね。

- `convert_test.go`を右クリックし、`Run 'convert_test.go'`を選択
![貼り付けた画像_2020_05_28_19_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/fee7f708-9764-79a2-ac85-7a85c0fc7b6e.png)

- 結果を確認、ちゃんとエラーで落ちてますね！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/3b5cbb92-18fd-2f51-3a5e-367e92ec4749.png)

## 処理を実装する
それでは、テストが通る様に実装をしていきましょう
テストの期待値を満たす様に、型別にstringへ変換してreturnする様にします。

```convert.go
package convert

import (
	"strconv"
)

func ToString(v interface{}) string {
	switch vt := v.(type) {
	case string:
		return vt
	case int:
		return strconv.Itoa(vt)
	case bool:
		return strconv.FormatBool(vt)
	default:
		return ""
	}

}
```

## 再実行する
全てのテストをPASSしました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/8b41c867-3ac4-4539-a864-3dd2926bd8c1.png)


# 追加実装する場合
基本的にはやることは変わりません。
1. テストに追記
2. テストFailedを確認
3. 処理を実装
4. テストの再実行
5. テストが通る様になるまで2~4を繰り返す

## 例） エラーハンドリングを追加する
先ほどの関数に対して、以下の通り期待動作を足します。
`string,int,bool以外の型がきた場合はエラーを返却する`


### テストに追記

```convert_test.go
package convert

import "testing"

func TestToString(t *testing.T) {
	type args struct {
		v interface{}
	}
	tests := []struct {
		name string
		args args
		want string
		wantError bool // エラーを期待するか
	}{
		{name: "String", args: args{v: "hoge"}, want: "hoge", wantError: false},
		{name: "Int", args: args{v: 1},want: "1", wantError: false},
		{name: "Bool", args: args{v: true}, want: "true", wantError: false},
		{name: "Other", args: args{v: 123.0}, want: "", wantError: true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ToString(tt.args.v)
			// エラーを期待しているにもかかわらず発生しない
			if err == nil && tt.wantError {
				t.Error("No error occurred.")
				return
			}
			// エラーを期待していないにもかかわらず発生した
			if err != nil && !tt.wantError {
				t.Error("An error has occurred.")
				t.Error(err.Error())
				return
			}
			if got != tt.want {
				t.Errorf("ToString() = %v, want %v", got, tt.want)
			}
		})
	}
}

```

#### コンパイルを通すために最低限実装
関数の戻り値の数が変更されてしまっているため、コンパイルが落ちてしまいます。
テストを実行するため、最低限実装してあげましょう。

```convert.go
package convert

import (
	"strconv"
)

func ToString(v interface{}) (string, error) { // errorを追加
	switch vt := v.(type) {
	case string:
		return vt, nil // とりあえずnilを返却
	case int:
		return strconv.Itoa(vt), nil
	case bool:
		return strconv.FormatBool(vt), nil
	default:
		return "", nil
	}


}
```

### テストFailedを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/0586a445-ca7f-0808-1985-c42ee74828b4.png)

エラーを発生させる処理のみテストが落ちています。
それ以外のケースについてはnilで合っているので通ってますね。

### 処理を実装
```
package convert

import (
	"errors"
	"strconv"
)

func ToString(v interface{}) (string, error) {
	switch vt := v.(type) {
	case string:
		return vt, nil
	case int:
		return strconv.Itoa(vt), nil
	case bool:
		return strconv.FormatBool(vt), nil
	default:
		return "", errors.New("Error")
	}

}

```

### テストの再実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/1ec8b807-b7fa-fa6c-388b-468efe60e3e5.png)

無事に通りましたね！

# カバレッジをとってみる
[カバレッジ  【 coverage 】  カバー率](http://e-words.jp/w/%E3%82%AB%E3%83%90%E3%83%AC%E3%83%83%E3%82%B8.html)

> ソフトウェア開発において、出来上がったプログラムのテストをする際に、どの程度をテスト対象とする（ことができる）かをカバレッジ（テストカバレッジ）という。

コード量が増えてくると、ちゃんとソース上の各ステップを網羅できてるか確認したくなりますよね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/e5f7ac42-e99f-6009-c554-3df07f5e85cf.png)

こちらで実行をすると、以下の通り、カバー率がでます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/3674b83c-0d23-cbe7-ceef-a9ce7cea3630.png)

もし網羅できていない部分がある場合、ソースの該当箇所が赤くマーキングされて、テストを行っていない箇所が明確になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317515/f9053ccd-f07d-aa29-93e8-6f9852d6b005.png)

# 後書き
テストファイルの自動生成が便利ですね。
細かい単位でテストを行うことで、自然とテストが容易なコードを書ける様になり、疎結合なコードを書いていきたくなりますね。