Goでテストを書く
久しぶりにちゃんとgolangを勉強していこうという事で、ログを残します。
今日のテーマ
testing
パッケージを使ってテストを書いてみるgom
でgospel
をインストールgospel
でテスト書いてみる- 独自matcherを書いてみる
環境
構成
$ tree . . ├── Gomfile ├── src │ └── model │ ├── user.go │ ├── user_gospel_test.go │ └── user_test.go └── vendor
testing
パッケージを使ってテストを書いてみる
まずは、標準のお作法にしたがってテストを書いてみます。
テスト対象はこんな感じです。
user.go
package model import ( "strings" ) type User struct { FirstName string LastName string Divisions []string } func NewUser(name string) *User { names := strings.Split(name, " ") if len(names) >= 2 { return &User{FirstName: names[0], LastName: names[1]} } else { return &User{FirstName: names[0]} } } func (user *User) FullName() string{ return user.FirstName + " " + user.LastName } func (user *User) AddDivision(division string) *User{ user.Divisions = append(user.Divisions, division) return user }
さて、テストを書いてみます。
user_test.go
package model import ( "testing" ) func TestConstructorWithFullname(t *testing.T) { user := NewUser("hoge aaa") if user.FirstName != "hoge" { t.Error("user's first name should be hoge") } if user.LastName != "aaa" { t.Error("user's last name should be aaa") } } func TestConstructorWithFirstName(t *testing.T) { user := NewUser("hoge") if user.FirstName != "hoge" { t.Error("user's first name should be hoge") } if user.LastName != "" { t.Error("user's last name should be empty") } } func TestConstructorWithEmptyString(t *testing.T) { user := NewUser("") if user.FirstName != "" { t.Error("user's first name should be empty") } if user.LastName != "" { t.Error("user's last name should be empty") } } func TestDevision(t *testing.T) { user := NewUser("hoge aaa") if len(user.Divisions) != 0 { t.Error("default divisions is empty slice") } } func TestFullName(t *testing.T) { fullname := "hoge aaa" user := NewUser(fullname) if user.FullName() != fullname { t.Errorf("fullname should be %s, but %s", fullname, user.FullName()) } } func TestAddDevision(t *testing.T) { user := NewUser("hoge aaa") division := "test" user.AddDivision(division) if user.Divisions[0] != division { t.Log(user.Divisions) t.Errorf("%s division was not added", division) } }
xxx_test.go
というファイル名にする- テスト関数は
Test
というprefixをつける - アサーションは自分で
if
などでチェックしてt.Error
でfailさせる
testing
にはアサートしてくれる関数がありません。
その辺の考え方的には
あたりを見るとよく分かります。 ふむ。
では、テスト実行してみます。
まずは、GOPATH
をカレントディレクトリにしておきます。
$ export GOPATH=`pwd`
したら、実行します。
$ go test -v ./src/... === RUN TestConstructorWithFullname --- PASS: TestConstructorWithFullname (0.00 seconds) === RUN TestConstructorWithFirstName --- PASS: TestConstructorWithFirstName (0.00 seconds) === RUN TestConstructorWithEmptyString --- PASS: TestConstructorWithEmptyString (0.00 seconds) === RUN TestDevision --- PASS: TestDevision (0.00 seconds) === RUN TestFullName --- PASS: TestFullName (0.00 seconds) === RUN TestAddDevision --- PASS: TestAddDevision (0.00 seconds) PASS ok model 0.010s
全部通りました。
gom
でgospel
をインストール
依存パッケージはやっぱり設定ファイルとかで管理したいので、今回はgom
を使ってみます。
(gondler
の方がスタンダードなのかな?依存パッケージのバージョンとか、あまり管理しないのかな?)
まずはgomをインストール
ここではgo get
つかいます。
$ go get github.com/mattn/gom
Gomfileを書く
gom 'github.com/r7kamura/gospel', :commit => 'd575dd12c2eb84612ae5c84fab56ccb4ce156a1e'
Rubyっぽい...
mattn/gom · GitHub
とか見てもほぼGemfileですね。
tag
も使えるし、group
も使える。
じゃあ、こんなのもいけるのか??
gom 'github.com/r7kamura/gospel', commit: 'd575dd12c2eb84612ae5c84fab56ccb4ce156a1e'
で
$ gom install gom: Syntax Error at line 1
怒られました... orz
気を取り直して、Gomfileを元に戻してインストールしなおします。
$ gom install downloading github.com/r7kamura/gospel
無事完了。
gospel
でテスト書いてみる
では、使い方を参考に書いてみます。
user_gospel_test.go
package model import ( . "github.com/r7kamura/gospel" "testing" ) func TestUser(t *testing.T) { Describe(t, "NewUser", func() { Context("フルネーム(hoge aaa)を指定した場合", func() { user := NewUser("hoge aaa") It("FirstName should be hoge", func() { Expect(user.FirstName).To(Equal, "hoge") }) It("LastName should be aaa", func() { Expect(user.LastName).To(Equal, "aaa") }) }) Context("空文字を渡した場合", func() { user := NewUser("") It("FirstName should be empty string", func() { Expect(user.FirstName).To(Equal, "") }) It("LastName should be empty string", func() { Expect(user.LastName).To(Equal, "") }) }) }) Describe(t, "Divisions", func() { user := NewUser("") It("default divisions is empty slice", func() { Expect(len(user.Divisions)).To(Equal, 0) }) }) }
Rspec風に書けてBDDな感じで書きやすいですね。
実行する時はgom exec
使います。
bundle exec
みたいな。
$ gom exec go test -v ./src/... === RUN TestUser NewUser フルネーム(hoge aaa)を指定した場合 FirstName should be hoge LastName should be aaa 空文字を渡した場合 FirstName should be empty string LastName should be empty string Divisions default divisions is empty slice PASS ok model 0.011s
※ 急に日本語で書いたのは気にしない ※ だいぶテストケースが減ってるのも気にしない
結果も見やすくて良い感じです。 (ちなみに、Describeをネストするとエラーになりました)
独自matcherを書いてみる
スライスとか配列のlengthをチェックするmatcherとかあっても良いかなぁという気がしていて、 この辺とか見ていたら、 自分で書けそうなので、書いてみました。
package model import ( . "github.com/r7kamura/gospel" "testing" "fmt" "reflect" ) func EqualLength(values ...interface{}) (failureMessage string) { actualValue := reflect.ValueOf(values[0]) if actualValue.Kind() != reflect.Slice && actualValue.Kind() != reflect.Array { failureMessage = fmt.Sprintf("`%v` is not slice or array", values[0]) return } if actualValue.Len() != values[1] { failureMessage = fmt.Sprintf("Expected `%v` length `%v` to equal `%v`", values[0], actualValue.Len(), values[1]) } return } func TestUser(t *testing.T) { Describe(t, "Divisions", func() { user := NewUser("") It("default divisions is empty slice", func() { Expect(user.Divisions).To(EqualLength, 0) }) }) }
reflect
パッケージ使って、TypeがSliceかArrayならlengthチェックするみたいな事をしています。
ためしに、
It("length", func() { Expect([]string{"hoge"}).To(EqualLength, 0) }) It("length", func() { Expect([1]string{"hoge"}).To(EqualLength, 0) }) It("length", func() { Expect(100).To(EqualLength, 0) })
こんなんでfailさせてみます。
=== RUN TestUser Divisions length Expected `[hoge]` length `1` to equal `0` /Users/natsuki/workspace/go-samples/test/unit-test/src/model/user_gospel_test.go:63 62. It("length", func() { 63. Expect([]string{"hoge"}).To(EqualLength, 0) 64. }) length Expected `[hoge]` length `1` to equal `0` /Users/natsuki/workspace/go-samples/test/unit-test/src/model/user_gospel_test.go:66 65. It("length", func() { 66. Expect([1]string{"hoge"}).To(EqualLength, 0) 67. }) length `100` is not slice or array /Users/natsuki/workspace/go-samples/test/unit-test/src/model/user_gospel_test.go:69 68. It("length", func() { 69. Expect(100).To(EqualLength, 0) 70. }) --- FAIL: TestUser (0.00 seconds) FAIL exit status 1 FAIL model 0.009s gom: exit status 1
おぉ、悪くないかも。