search
LoginSignup
27

More than 5 years have passed since last update.

posted at

Organization

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 を使い方を体験してみる。

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

じゃんけんのルール仕様

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

自分(i)\相手(you) rock paper scessors
rock draw lose win
paper win draw lose
scessors lose win draw

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

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

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

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() {
            })
        })
    })
}

振る舞いを埋めていく

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

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

実装する

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

touch janken.go

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

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

janken.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

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

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

    case mine == yours:
        return DRAW

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

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
27