Go言語: 単体テストの始め方！Gospelを使ってBDDをやってみよう


# この記事の目的

* Go言語で単体テストを書けるようになる

# テーマ

「じゃんけんプログラムをBDDで作る」をテーマに順を追って説明する。

# 準備

適当に作業用のディレクトリを作る:

```
mkdir ~/Desktop/go-bdd
cd ~/Desktop/go-bdd/
```

テストを書いていくGoのファイルを作ってエディタで開く:

```
touch janken_test.go
```


github.com/r7kamura/gospel をインストールする:

```
go get github.com/r7kamura/gospel
```


# ひとまず Gospel を体験してみる

じゃんけんと関係ないが、100円入っている財布に100円を追加したら200円になるテストを試しに書いて、gospel を使い方を体験してみる。

```go:janken_test.go
package janken

import (
	. "github.com/r7kamura/gospel"
	"testing"
)

func TestDescribe(t *testing.T) {
	Describe(t, "I have 100 yen", func() {
		wallet := 100
		Context("and I got 100 yen", func() {
			wallet += 100
			It("should be 200 yen", func() {
				Expect(wallet).To(Equal, 200)
			})
		})
	})
}

```

テストを実行する:

```
go test -v
```

テスト結果が表示された:

![Terminal_—_bash_—_68×23.png](https://qiita-image-store.s3.amazonaws.com/0/889/7f061fd4-6f12-f34a-2bd8-d33d5db2af1c.png "Terminal_—_bash_—_68×23.png")

# じゃんけんのルール仕様

じゃんけんの勝敗は自分の手と相手の手の関係で決まる:

| 自分(i)＼相手(you)| rock | paper | scessors |
| ----             | ---- | ---- | ---- |
| rock | *draw* | **lose** | win |
| paper | win | *draw* | **lose** | 
| scessors | **lose** | win| *draw* |

draw: ひきわけ
lose: 負け
win: 勝ち

# 仕様をシナリオに落としこむ

財布の例では処理とシナリオを同時に書いたが、今回はいきなり処理を書かずに、まずはシナリオ(テストケース)だけ先に決めてしまう。

```go:janken_test.go
package janken

import (
	. "github.com/r7kamura/gospel"
	"testing"

)

func TestDescribe(t *testing.T) {
	Describe(t, "I pick rock", func() {
		Context("you pick rock", func() {
			It("should be draw", func() {
			})
		})
		Context("you pick paper", func() {
			It("should be lose", func() {
			})
		})
		Context("you pick scessors", func() {
			It("should be win", func() {
			})
		})
	})
	Describe(t, "I pick paper", func() {
		Context("you pick rock", func() {
			It("should be win", func() {
			})
		})
		Context("you pick paper", func() {
			It("should be draw", func() {
			})
		})
		Context("you pick scessors", func() {
			It("should be lose", func() {
			})
		})
	})
	Describe(t, "I pick scessors", func() {
		Context("you pick rock", func() {
			It("should be lose", func() {
			})
		})
		Context("you pick paper", func() {
			It("should be win", func() {
			})
		})
		Context("you pick scessors", func() {
			It("should be draw", func() {
			})
		})
	})
}

```


# 振る舞いを埋めていく

次に、振る舞い(処理)の部分を埋めていく。

```go:janken_test.go
package janken

import (
	. "github.com/r7kamura/gospel"
	"testing"
)

func TestDescribe(t *testing.T) {
	Describe(t, "I pick rock", func() {
		i := Rock()
		Context("you pick rock", func() {
			you := Rock()
			It("should be draw", func() {
				Expect(i.Beats(you)).To(Equal, DRAW)
			})
		})
		Context("you pick paper", func() {
			you := Paper()
			It("should be lose", func() {
				Expect(i.Beats(you)).To(Equal, LOSE)
			})
		})
		Context("you pick scessors", func() {
			you := Scessors()
			It("should be win", func() {
				Expect(i.Beats(you)).To(Equal, WIN)
			})
		})
	})
	Describe(t, "I pick paper", func() {
		i := Paper()
		Context("you pick rock", func() {
			you := Rock()
			It("should be win", func() {
				Expect(i.Beats(you)).To(Equal, WIN)
			})
		})
		Context("you pick paper", func() {
			you := Paper()
			It("should be draw", func() {
				Expect(i.Beats(you)).To(Equal, DRAW)
			})
		})
		Context("you pick scessors", func() {
			you := Scessors()
			It("should be lose", func() {
				Expect(i.Beats(you)).To(Equal, LOSE)
			})
		})
	})
	Describe(t, "I pick scessors", func() {
		i := Scessors()
		Context("you pick rock", func() {
			you := Rock()
			It("should be lose", func() {
				Expect(i.Beats(you)).To(Equal, LOSE)
			})
		})
		Context("you pick paper", func() {
			you := Paper()
			It("should be win", func() {
				Expect(i.Beats(you)).To(Equal, WIN)
			})
		})
		Context("you pick scessors", func() {
			you := Scessors()
			It("should be draw", func() {
				Expect(i.Beats(you)).To(Equal, DRAW)
			})
		})
	})
}

```

この状態でテストを実行すると、まだテスト対象の `janken` モジュールが定義されていないのでコンパイルエラーになる:


![Terminal_—_bash_—_68×23-3.png](https://qiita-image-store.s3.amazonaws.com/0/889/85356fe9-3fba-263a-2145-136ebe8636f5.png "Terminal_—_bash_—_68×23-3.png")


# 実装する

実装を書くGoファイルを作り、エディタで開く:

```
touch janken.go
```

ファイルを置く場所は  `janken_test.go` のあるディレクトリにする。全体のファイルツリーとしては下記のようになる:

```
~/Desktop/go-bdd
├── janken.go
└── janken_test.go
```

`janken.go` を実装する:

```go:janken.go
package janken

const (
	ROCK     = 1
	PAPER    = 2
	SCESSORS = 3
)

const (
	WIN  = 1
	LOSE = -1
	DRAW = 0
)

type Hand struct {
	Hand int
}

func Rock() *Hand {
	return &Hand{ROCK}
}

func Paper() *Hand {
	return &Hand{PAPER}
}

func Scessors() *Hand {
	return &Hand{SCESSORS}
}

func (my *Hand) Beats(your *Hand) int {
	mine := my.Hand
	yours := your.Hand

	switch {
	case mine == ROCK && yours == SCESSORS:
		return WIN
	case mine == PAPER && yours == ROCK:
		return WIN
	case mine == SCESSORS && yours == PAPER:
		return WIN
	default:
		return LOSE
	}
}

```

# テストする

テストを実行する:

```
go test -v
```

![Terminal_—_bash_—_68×43.png](https://qiita-image-store.s3.amazonaws.com/0/889/c1e5da33-ed3b-4421-2f9a-d5d4e7c7b3e1.png "Terminal_—_bash_—_68×43.png")

見ての通り、3つのシナリオでテストが失敗している。プログラムを確認すると、引き分けのロジックが抜けていた。いわゆるバグだ。

下記のコードを `janken.go` に追加する:

```go
	case mine == yours:
		return DRAW
```

全体のコードはこのようになる:

```go:janken.go
package janken

const (
	ROCK     = 1
	PAPER    = 2
	SCESSORS = 3
)

const (
	WIN  = 1
	LOSE = -1
	DRAW = 0
)

type Hand struct {
	Hand int
}

func Rock() *Hand {
	return &Hand{ROCK}
}

func Paper() *Hand {
	return &Hand{PAPER}
}

func Scessors() *Hand {
	return &Hand{SCESSORS}
}

func (my *Hand) Beats(your *Hand) int {
	mine := my.Hand
	yours := your.Hand

	switch {
	case mine == ROCK && yours == SCESSORS:
		return WIN
	case mine == PAPER && yours == ROCK:
		return WIN
	case mine == SCESSORS && yours == PAPER:
		return WIN
	case mine == yours:
		return DRAW
	default:
		return LOSE
	}
}
```

ふたたびテストを実行する:

```
go test -v
```

![Terminal_—_bash_—_68×27.png](https://qiita-image-store.s3.amazonaws.com/0/889/4274319b-f8ff-0335-41bc-b34831b69b75.png "Terminal_—_bash_—_68×27.png")


今度は全てのテストが通った。