この記事の目的
- 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 を使い方を体験してみる。
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
テスト結果が表示された:
じゃんけんのルール仕様
じゃんけんの勝敗は自分の手と相手の手の関係で決まる:
自分(i)\相手(you) | rock | paper | scessors |
---|---|---|---|
rock | draw | lose | win |
paper | win | draw | lose |
scessors | lose | win | draw |
draw: ひきわけ
lose: 負け
win: 勝ち
仕様をシナリオに落としこむ
財布の例では処理とシナリオを同時に書いたが、今回はいきなり処理を書かずに、まずはシナリオ(テストケース)だけ先に決めてしまう。
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() {
})
})
})
}
振る舞いを埋めていく
次に、振る舞い(処理)の部分を埋めていく。
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
モジュールが定義されていないのでコンパイルエラーになる:
実装する
実装を書くGoファイルを作り、エディタで開く:
touch janken.go
ファイルを置く場所は janken_test.go
のあるディレクトリにする。全体のファイルツリーとしては下記のようになる:
~/Desktop/go-bdd
├── janken.go
└── janken_test.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
見ての通り、3つのシナリオでテストが失敗している。プログラムを確認すると、引き分けのロジックが抜けていた。いわゆるバグだ。
下記のコードを janken.go
に追加する:
case mine == yours:
return DRAW
全体のコードはこのようになる:
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
今度は全てのテストが通った。