Go
unittest

go mockについての悩み


背景

例えば python だとこんな感じで関数を入れ替えることができちゃう。

class A(object):

def my_method(self):
return "this is original"

if __name__ == '__main__':
a = A()
print a.my_method() # "this is original"

def dummy_method():
return "this is dummy"

setattr(a, 'my_method', dummy_method) # method is replaced here.

print a.my_method() # "this is dummy"

この原理を使って、unittest時にAPI Clientを無理やりdummy methodに置き換えて、実際にリクエストが送出されるのを防止する・・・なんてこともその気になれば可能だし、場合によってはモックが難しい関数を無理やり置き換えるなんてこともできる。

さて、これと同じようなことを go でやりたいと思った。

何故かと言うと、


  • いろいろ調べてみても、PHPのrunkitみたいな強力な関数置換方法がない

  • gomock を見てみたが、世の中にあるモジュールをモックしたい場合に自由度が低いように見える(僕の勉強不足なだけかもしれません・・・)

で、いい方法はないものか・・・というのが目下の悩みで、以下のようなことを勉強してた。

goの継承の仕組みを試してみて


実際に困っていること

ということで、先のpythonの例とほぼ同じようなことを go でやってみる。

package main

import (
"fmt"
"time"
)

func FiveHoursAgo() time.Time {
n := time.Now()
return n.Add(-5 * time.Hour)
}

func main() {
layout := "2006-01-02 15:04:05 MST"
f := FiveHoursAgo()
fmt.Println(f.Format(layout))
}

例えばこの FiveHoursAgo をテストしたいとする。

しかし内部で time.Now が呼ばれてしまうので、returnされる値の期待値を書きたくても、テストの実行時刻によってそれが変わってしまう。

つまるところアサーションの比較対象を定義できない。

そこで、unitest時のみ time.Now を何かしらの方法でreplaceすることができれば、他の方が作ったものを楽にテストできないか・・・と考えたが。

が、実際やってみると、例として以下のような事はできない。

package main

import (
"fmt"
"time"
)

// こんな事はできない部
// cannot define new methods on non-local type time.Time
func (time.Time) Now() time.Time {
return time.Now()
}

func FiveHoursAgo() time.Time {
n := time.Now()
return n.Add(-5 * time.Hour)
}

func main() {
layout := "2006-01-02 15:04:05 MST"
f := FiveHoursAgo()
fmt.Println(f.Format(layout))
}

当然あくまでこれは例です。


ということで課題

とりあえずもっと調べないと・・・


  • 本質的に普通にモックできればいいだけなので、もう一度ちゃんと調べよう

  • 自作なら最初からテストを書きやすくしておけばいいのかもしれないが、他の方が作ったものに対してそれは無理

  • それすら可能にする強力な置換方法があればなぁ・・・