moqでinterfaceのMockを作る
この記事は Go3 Advent Calendar 2017の18日目の記事です。
moqでMockを作る
Goでアプリケーションを書いているとinterfaceにMockを投げ込みたくなる瞬間があると思います。
たとえば、以下のようなコードのテストを書きたい場合ですね。
User
を取りに行く処理が外部に依存しているためにClient
interfaceを切っていて、それをApplication
構造体が持っています。
type User struct { ID int FirstName string LastName string } type Client interface { GetAllUser() ([]*User, error) } type Application struct { Client Client } func (a *Application) FilterLastName(name string) ([]*User, error) { users, _ := a.Client.GetAllUser() var result []*User for _, u := range users { if u.LastName == name { result = append(result, u) } } return result, nil }
Goはダックタイピングなので、以下のようにClient
interfaceを満たす構造体を自分で定義するのもいいですが、
interfaceが持つメソッドが多いと自分で定義するのも面倒です。
type Mock struct { FilterLastNameFunc func(string) ([]*User, error) } func (m *Mock) FilterLastName(name string) ([]*User, error) { return m.FilterLastNameFunc(name) }
となれば「楽をしよう!」ということで、この記事ではMockを自動生成してくれる moqを紹介します。
他にも幾つかMockを自動生成してくれるツールはありますが、他のツールに比べてmoqが優れているところは、
自動生成されたMockが余計なパッケージをimportしていない
という点にあります。
とりあえず以下のコマンドでインストールします。
$ go get github.com/matryer/moq
インストールできたらMockを生成してみます。
$ moq -out client_mock.go . Client
以下が生成したclient_mock.goの内容です。
// Code generated by moq; DO NOT EDIT // github.com/matryer/moq package main import ( "sync" ) var ( lockClientMockGetAllUser sync.RWMutex ) // ClientMock is a mock implementation of Client. // // func TestSomethingThatUsesClient(t *testing.T) { // // // make and configure a mocked Client // mockedClient := &ClientMock{ // GetAllUserFunc: func() ([]*User, error) { // panic("TODO: mock out the GetAllUser method") // }, // } // // // TODO: use mockedClient in code that requires Client // // and then make assertions. // // } type ClientMock struct { // GetAllUserFunc mocks the GetAllUser method. GetAllUserFunc func() ([]*User, error) // calls tracks calls to the methods. calls struct { // GetAllUser holds details about calls to the GetAllUser method. GetAllUser []struct { } } } // GetAllUser calls GetAllUserFunc. func (mock *ClientMock) GetAllUser() ([]*User, error) { if mock.GetAllUserFunc == nil { panic("moq: ClientMock.GetAllUserFunc is nil but Client.GetAllUser was just called") } callInfo := struct { }{} lockClientMockGetAllUser.Lock() mock.calls.GetAllUser = append(mock.calls.GetAllUser, callInfo) lockClientMockGetAllUser.Unlock() return mock.GetAllUserFunc() } // GetAllUserCalls gets all the calls that were made to GetAllUser. // Check the length with: // len(mockedClient.GetAllUserCalls()) func (mock *ClientMock) GetAllUserCalls() []struct { } { var calls []struct { } lockClientMockGetAllUser.RLock() calls = mock.calls.GetAllUser lockClientMockGetAllUser.RUnlock() return calls }
Lockの処理があるため、syncパッケージをimportしていますが、それ以外にimportをしているものはありません。
基本的にやっていることはMock
構造体を定義していたときと同じです。
以下のように使うことができます。
mock := &ClientMock{ GetAllUserFunc: func() ([]*User, error) { users := []*User{ &User{ID: 1, FirstName: "Taro", LastName: "Suzuki"}, &User{ID: 2, FirstName: "Taro", LastName: "Tanaka"}, &User{ID: 3, FirstName: "Jiro", LastName: "Sato"}, &User{ID: 4, FirstName: "Saburo", LastName: "Tanaka"}, } return users, nil }, } app := &Application{ Client: mock, } users, err := app.FilterLastName("Tanaka") if err != nil { panic(err) } for _, u := range users { fmt.Printf("%+v\n", u) }
また、~~Calls
というメソッドで、当該のメソッドが何回呼ばれたかもチェックすることができます。
fmt.Printf("call count: %d\n", len(mock.GetAllUserCalls()))
~~Func
変数に値が入っていないときには、そのことを示した上でpanicを発生させてくれるので、
メソッドの数が多いinterfaceのMockを生成したときにも必要な部分だけを実装させるのもそこまで難しくありません。
moqに合わないユースケース
上記例のようなClient
interfaceとそれを持つ構造体が同じパッケージ内部に存在する場合にはとても重宝しますが、
外部のパッケージのMockを生成しようとするとちょっと使いづらいです。
たとえば以下のようにfooパッケージとbarパッケージでそれぞれ提供されている
Client
interfaceを作りたい場合はつらみが出てきます。
type Application struct { FooClient foo.Client BarClient bar.Client }
moqで指定するinterfaceのパスは、Goのimportパスではなく、普通のディレクトリパスのため、 生成されたファイルのimportに相対パスが入り込んでしまいます。
import ( "./vendor/github.com/nametake/foo" "sync" )
また、生成されたMockの構造体は、{interface名}Mock
という名前になるので、上記のように別のパッケージで
同じ名前のinterfaceを利用している場合は、どちらもClientMock
という名前になるので、ビルドでコケてしまいます。
まとめ
ユースケースに合わない部分もありますが、Mockの生成のようなやり方が定形になっている部分は どんどん自動化をして楽をしていきましょう!
Golang.tokyo#11でLTしてきた
「アドベントカレンダーとLT同じ時期だし同じネタでやったろw」って思ってたら完全にネタがかぶったので、アドベントカレンダーとは別記事で公開。
LTしてきました。
ベストスピーカー賞頂けました。ありがとうございます。
頂いたGolang.tokyo仕様モバイルバッテリー、大事に使います。
GocCon2017感想とか
行ったので書く(VimConfの記録は出遅れたのであとでこっそり)。
パネルディスカッション
VimConfに続きmattnさんを招待しての他セッション。
聞きながら取ったメモを貼り付けておきます。抜け漏れは許容していただけると。
Keynote
Digital Oceanのエンジニアであり、vim-goの作者であるFatih氏の発表。
Go+Microservices at Mercari
https://talks.godoc.org/github.com/tcnksm/talks/2017/11/gocon2017/gocon2017.slide#1
メモ : gRPCを触ってみる。
Story of our own Monitoring Agent in golang
Gocon2017:Goのロギング周りの考察
個人的に、Goのロギングは下みたいなLoggerのinterfaceを用意して 使いたいライブラリを薄くWrap、DIすればなんでも良いかなぁ、と思ってるんですがどうなんですかね?
type Logger interface { Fatalf(...interface{}) Errorf(...interface{}) Warnf(...interface{}) Infof(...interface{}) Debugf(...interface{}) }
How to achieve parallel compilation in Go 1.9
Async, Persistent, Fast, and Sable "Enought" Queue/Worker Using Go and PostgreSQL
Diff algorithm in Go
TODO スライドを追加する。
EBITEN
reviewdog and static analysis for Go
https://docs.google.com/presentation/d/1_BWQXamZvIhL3l9ziL9zb25yP9RjpgXoxkWX-48ECss/edit#slide=id.p
今後
込み入ったGoの部分については掘っていないのであんまり凝った話はできませんが、パッケージ構成とかについてはAdventCalendarとかに書いていこうかなと思います。
golang.tokyo #5 感想
4月27日に開催されたGolang.tokyo#5に行ってきた。 GCPUG Tokyoとの共同開催ということで30分枠はGAEでGo言語を使ったお話。
直前に人数を見たら珍しく定員割れをしてましたそれでも100人超えてるあたりGoの注目度の高さが伺える。
GO *GAE
株式会社カブクさんの方の発表(資料未公開)。 GAEの基本的な部分の説明と、実際に業務で使ったときのお話。 Go言語は業務で使っていますが、GCPは触れたことがない勢なので基本的なところを説明していただいてとてもありがたかった。
以下、気になったところ箇条書きで書く。
- GAEにはStandardとFlexibleの2種類ある
- Standard Environment(SE)
- PaaS
- スピンアップが速い
- Goは1.6
- Flexble Environment(FE)
- Dockerのコンテナを立ち上げる
- バイナリを扱える
- Standard Environment(SE)
- 基本はSEを使う
- バイナリを扱いたいならFEしかない
- カブク内で使っているところは3D解析のAPI
- SEとFEを両方合わせて使用している
- SEだけだと他の3D解析APIのリクエストに60秒以上かかるためタイムアウトする
- FEだとTaskQueueが使えない
社内でもGCPを使う話はちょこちょこ聞くけど60秒制限はよく引っかかりそうなのでちゃんと覚えておく。 あと、辛かったことで言っていた事と全く同じことを自分もGoを最初に触った時に思っていたのでわかりがあった。
GAE/GOの勘どころ
株式会社ソウゾウの@osamingoさんの発表。
以下、気になったところ箇条書きで書く。
- CloudSQLの最大同時接続数が12(Master,Slave合計で)
- Vendoringは出来るらしい(質疑応答より)
- gb
- direnv
- Deployもよしなに
- Monitoringも楽
- GCPは運用が楽
- GCPは運用が楽(重要)
GCPでの細かいハマりどころとかツール等々。 AWSの運用めんどくさくない?と最近気づき始めたのでGCPの運用が楽という話を聞いて触りたくなった。
Datastore/Go のデータ設計と struct の振る舞いについて
ここからLT枠。 @pospomeさんのDatastoreでの設計のお話。
設計の話は特に好きなのでfmfmと思いながら聞いていたが、@vvakameさんの
Datastoreを使う上でネストしたstructは自動生成系ツールとの相性が悪い可能性があるのでバッドプラクティスだよ論者です #golangtokyo
— わかめ@TypeScript味 (@vvakame) 2017年4月27日
というツイートを見てなるほど自動生成系ツールもあるのかという知見を得た。
となると@__timakin__さんもツイートしていたように、サービスロジックで扱う構造体とDatastoreで扱う構造体を分離するのが、 ベターかなぁという感じがした。
DataStoreは保存するときにそれ用のEntity定義してるから、完全にそのpackage以外のstructからは区別して使ってる #golangtokyo
— timakin (@__timakin__) 2017年4月27日
Contextアンチパターン
神(Context)を殺した話。
面白かった(小並感)。
自分ではまだContext.Valueを使っていなかったが、使う時には気をつける。
あと、現在自分のチームではechoを使っているが、スライドの内容と同じように殆どRoutingにしか使ってないんだったら少し重い。 なので、最近は、個人でWebのものを書くときとかプロダクトから少し離れたツールを書くときは httprouterを使っている。
ASTライブコーディング
@tenntennさんのASTライブコーディング。
以下のコードから文字列のhoge変数だけを取り出すコードを10分で書いていた(すごい)。
package main import ( "fmt" ) func main() { var hoge = "hoge" fmt.Println(hoge) func() { var hoge = 0 fmt.Println(hoge) }() }
触ろう触ろうと思ってastは全然触っておらず、いい機会でもあったので自分でも書いてみた。
https://gist.github.com/nametake/1fd82014bdfc434a32aba6590c4c66bb
(@tenntennさんがやっていたやり方とは違う気がするけど取り出せてるからセーフ)
astを触ったことがなくてもast.Print
の内容を見ていれば何となくどうなっているのかわかって、思っていた以上に楽ちんに書けた。
fish-shellのpecoプラグインをforkした
最近fishに乗り換えた。
pluginはfishermanで管理することにしたが、そうなるとoh-my-fish/plugin-pecoの以下の点が気に食わなかった。
fish_user_key_bindings
に自分でbind設定を書く必要がある- pecoのlayoutがbottom-up
fishermanを使っているのであればfish_user_key_bindings
になるべく自分設定を書き込みたくなかったので、forkしてnametake/plugin-pecoを作った。
Go言語でのDispatcher-Workerパターン実装メモ
最近Dispatcher-Workerパターンをよく書くので、パターンとして書き残しメモ。