おそらくはそれさえも平凡な日々

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 .

使い方

SetFix関数を使うことで上記の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() // 元の挙動に戻す

SetFixの違いは、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 インターフェースの関数群は、NowSleepの2つさえあれば、他の関数を実現することができます。これを NowSleeper インターフェースとして定義しており、これを満たすだけで、以下のように独自Clockを簡単に作ることができます。

var ns flextime.NowSleeper
var clock Clock = flextime.NewFakeClock(ns)
flextime.Swtich(clock)

なかなかflexibleと言えるのではないでしょうか。名前のflextimeも気に入っています。

testabilityの向上に有用ではないでしょうか。ぜひ使ってみてください。

ちなみに、弊社はフレックスタイム制を採用しています。採用にご興味があればぜひご応募ください。

https://nature.global/jp/careers

created at
last modified at

2020-01-19T01:39:54+0900

comments powered by Disqus