Carpe Diem

備忘録

GoでMockを書く時のTips

概要

「GoはDuck TypeだからMock用意するの大変だよね」とよく言われますが、そんなことはないですよ、という話。

環境

1. interface自体埋め込めば実装済みと解釈してくれる

例えば以下のような複数のメソッドを持つinterfacedoEverythingがあり、showUserAge()にDIして表示させるロジックがあるとします。
※DIについては GoでDependency Injection - Carpe Diem を参考にしてください

type doEverything interface {
    getAge() int
    setAge(int) error
    getName() string
    setName(string) error
    getSex() string
    setSex(string) error
}

func showUserAge(d doEverything) {
    fmt.Println(d.getAge())
}

Mock化する場合、Duck Typeなので全メソッドを実装すれば引数に入れることができますが、それを1つ1つ書くのは非常に手間です。
そんな時は使うメソッドだけ実装し、それ以外はinterface自体を埋め込めば実装済みと解釈してくれます

func main() {
    m := &Mock{}
    showUserAge(m)
}

type Mock struct {
    doEverything
}

func (m *Mock) getAge() int {
    return 10
}

The Go Playground

※当然ですが、実装してないものを使おうとするとpanicになるのでモックとしての使い方とだけ考えてください

2. なるべく小さいinterfaceにする

そもそも1つのinterfaceに色々なメソッドを用意するから大変なのであって、Go Proverbs

The bigger the interface, the weaker the abstraction

とあるように、メソッドが多すぎるのは設計自体がよろしくない、と言えます。
複数の責務を持つことになって凝集度は下がりますし、具体性が増す分、抽象度が下がります。
なので標準パッケージのようになるべくinterfaceが持つメソッドは少なくすると良いです。

悪い例

type doesEverything interface {
    countChickens() int
    hatch()
    checkWeather(lat, lng float64) string
    checkScores(home, away team) (int, int)
    checkYourself() bool
    wreckYourself()
}

良い例

io package - io - pkg.go.dev

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

まとめ

Goでモック化するのに疲弊している方にとって参考になれば幸いです。

ソース