嫁の顔忘れてもTimecop.returnは忘れないでねという話

夜釣りでは初心者向けの狙い目といえばアナゴなんだそうです。岸壁で釣れちゃうんだとか。ウナギはお高いのでアナゴで一杯、とかあるんだろうか。
さて、Rubyでいろいろ開発していて時間(時刻・期間…)が絡むテストをするときTimecopが便利で広く使われます。
travisjeffery/timecop – GitHub

# 現在時刻を変える
> Timecop.travel(Date.parse('1998/12/1'))
=> 1998-12-01 00:00:00 +0900
> Date.today
=> Tue, 01 Dec 1998
 
# 時間の進行を止める
> Timecop.freeze(Time.parse('1998/12/1 0:00:00'))
=> 1998-12-01 00:00:00 +0900
> sleep(10)
> Time.now
=> 1998-12-01 00:00:00 +0900  # it's still 0:00:00
 
# 時間の進行を早める
> Timecop.travel(Time.parse('1998/12/1 0:00:00'))
> Timecop.scale(3600)
> sleep(2)
> Time.now
=> 1998-12-01 02:00:19 +0900  # it took 2 hours although only slept for 2 seconds

便利ですね。
時間をいじくるので、

  • タイムゾーン周りをミスってないか
  • DBMSのタイムスタンプやredisのexpireのようにruby処理系の時間を使わないものでは効かない

こんなかんじで注意が必要なのは当然なのですが、一番しんどいのはTimecopで変えた時間を戻し忘れたときの挙動不審なかんじでしょうか。
テストでTimecopを使う場合、このテのミスをするとたいてい別のテストケースに影響しますね。順序依存で効いてくるし煽りを食ったテストケース自体は何も悪く無いことがほとんどなので、心神耗弱状態になります。

  • なぜかCapybaraのCookieが取れなくなっている
  • redisではうまくいくのにredis_mockでテストすると想定外の挙動になる
  • Timeoutが狂う

こんなことで時間をムダにするのはあほらしいので、Timecop.returnを呼ぶのは絶対に忘れないようにしましょう。

> Timecop.travel(Time.parse('1998/12/1 0:00:00'))
> Timecop.return
> Date.today
=> Sun, 20 Dec 2015

「忘れないでね」「気をつけてね」はものごとの解決策としては最悪な思考停止なので、
Rails/RSpecをお使いなら、spec/spec_helper.rbにて

config.after(:each) do
  Timecop.return
end

なる設定をしておくべきです。
また、別の安全策として、Timecopのsafe_modeといい、Timecopを使うときはblockを与えることを強制し、block末尾でreturnが自動で行われるようにする仕組みがあります。
blockを渡さずに呼ぶと例外が飛ぶので、すぐに気づきます。

> Timecop.safe_mode = true
> Timecop.travel(Time.parse('1998/12/1 0:00:00'))
Timecop::SafeModeException: Safe mode is enabled, only calls passing a block are allowed.
...
> Timecop.travel(Time.parse('1998/12/1 0:00:00')) do
>   Date.today
> end
=> Tue, 01 Dec 1998
> Date.today
=> Sun, 20 Dec 2015

Rails/RSpecのみで使うなら、spec_helperのafterでケアするのがシンプルにして安全十分かと。
テスト以外でも使うなら、safe_modeにするようinitializerなどで設定しておくほうが安全そうです。

全部Githubに書いてあるんですけども、1ヶ月近くこれにハマってしまった自分が本当にあほらしかったので忘れないように…。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です