Goでテスト中に現在時刻を差し替えたりするflextimeというのを作った
https://github.com/Songmu/flextime
flextimeはテストコードの中で現在時刻を切り替えるためのライブラリです。Sleep時に実際に時間を止めずに時間が経過したように見せかける機能もあります。
つまり、PerlのTest::MockTimeやRubyのtimecop的なことをしたいわけですが、Goだとグローバルに関数の挙動を切り替えるといったことはできないため、利用にあたってはtimeパッケージで使っている関数を、flextimeパッケージに切り替える必要があります。
具体的には、flextimeはtimeパッケージと同様のインターフェースを備える以下の9つの関数を提供しています。
now := flextime.Now()
flextime.Sleep()
d := flextime.Until(date)
d := flextime.Since(date)
<-flextime.After(5*time.Second)
flextime.AfterFunc(5*time.Second, func() { fmt.Println("Done") })
timer := flextime.NewTimer(10*time.Second)
ticker := flextime.NewTicker(10*time.Second)
ch := flextime.Tick(3*time.Second)
これらはデフォルトでは標準timeパッケージと同様の動作をするため、単純に置き換え可能です。ちょっと乱暴ですが、以下のような単純置換でも大体動くでしょう。本来静的解析でやるべきですが…。
go get github.com/Songmu/flextime
find . -name '*.go' | xargs perl -i -pe 's/\btime\.((?:N(?:ewTi(?:ck|m)er|ow)|After(?:Func)?|Sleep|Until|Tick))/flextime.$1/g'
goimport -w .
使い方
Set
やFix
関数を使うことで上記の9つの関数の挙動を差し替えます。Restore
でもとに戻します。
heisei := time.Date(1989, time.January, 8, 0, 0, 0, 0, time.Local)
flextime.Set(time.Date(heisei) // 時刻をセットする
now := flextime.Now() // 現在時刻がセットした時間(heisei)になる
// ...
flextime.Restore() // 元の挙動に戻す
Set
とFix
の違いは、Set
は実際の時間経過の影響を受けますが、Fix
は完全に固定されるところです。
仕組み
内部的に時刻を返すオブジェクト(内部clock)を差し替えることで実現しています。それは、上の9つの関数を備えるClock
interfaceを満たしたオブジェクトです。flextime.Now
などの関数はそのオブジェクトに処理を委譲している形になります。
type Clock interface {
Now() time.Time
Sleep(d time.Duration)
Since(t time.Time) time.Duration
Until(t time.Time) time.Duration
After(d time.Duration) <-chan time.Time
AfterFunc(d time.Duration, f func()) *Timer
NewTimer(d time.Duration) *Timer
NewTicker(d time.Duration) *Ticker
Tick(d time.Duration) <-chan time.Time
}
このClock
interafaceというアプローチは以下の様な先行実装を参考にしました。
そのinterfaceをより標準timeパッケージに近づけつつ、内部的に差し替えるアプローチにより、easyに使えるようにしたのが、このflextimeです。
おまけ
組み込みのSet
, Fix
だけではなく、独自のClock
インターフェースを備えたオブジェクトを作って、それに内部clockを差し替えることもできます。
flextime.Switch(clock)
また、Clock
インターフェースの関数群は、Now
とSleep
の2つさえあれば、他の関数を実現することができます。これを NowSleeper
インターフェースとして定義しており、これを満たすだけで、以下のように独自Clockを簡単に作ることができます。
var ns flextime.NowSleeper
var clock Clock = flextime.NewFakeClock(ns)
flextime.Swtich(clock)
なかなかflexibleと言えるのではないでしょうか。名前のflextimeも気に入っています。
testabilityの向上に有用ではないでしょうか。ぜひ使ってみてください。
ちなみに、弊社はフレックスタイム制を採用しています。採用にご興味があればぜひご応募ください。