こんにちは。SI部の r_maeda です。
Webアプリケーションを作っていると、現在時刻に依存したコードを書くことがよくあります。(e.g. 「期限が設定された課題」があり、「期限が迫っている」場合は表示を目立たせたい、など。)
そんな「時間に依存したコード」をテストする場合、
といったことを考えると思います。
このような場面において、Rubyコードのテストでは、以下の記事でも紹介されているとおり
Timecop
モジュール)ActiveSupport::Testing::TimeHelpers
モジュール)あたりの gem がよく用いられています。
今回の記事では、そんな Timecop
と ActiveSupport::Testing::TimeHelpers
にはどんな違いがあるのか、これをもう少し掘り下げて比較してみようと思います。
Timecop
では「任意の日時に移動しつつ、時間は止めない」ことができますが、 ActiveSupport::Testing::TimeHelpers
では「移動すると同時に時間を止める」ことしかできません。
## Timecop
Timecop.freeze do
# 現在時刻で時間を止める
end
Timecop.travel(time_or_date) do
# time_or_date に渡された時間に移動するが、時間は止まらない
end
Timecop.freeze(time_or_date) do
# time_or_date に渡された時間に移動し、時間が止まる
end
## ActiveSupport::Testing::TimeHelpers
ActiveSupport::Testing::TimeHelpers.travel_to(date_or_time) do
# time_or_date に渡された時間に移動し、時間が止まる
# **このとき、date_or_time.to_time.usec は強制的に 0 に設定されます**
end
ActiveSupport::Testing::TimeHelpers.freeze_time do
# 現在時刻で時間を止める
# = travel_to(Time.now) と同じ
end
# NOTE: Rails 7.1 からは、秒未満の時間を保持したまま旅行するためのオプションが追加されました
ActiveSupport::Testing::TimeHelpers.freeze_time(with_usec: true) do
...
end
Timecop
では、「1秒あたりの経過時間」を変更することが可能です。これは ActiveSupport::Testing::TimeHelpers
ではサポートされていません。
## https://github.com/travisjeffery/timecop/tree/master#timecopscale より引用
# seconds will now seem like hours
Timecop.scale(3600)
Time.now
# => 2012-09-20 21:23:25 -0500
# seconds later, hours have passed and it's gone from 9pm at night to 6am in the morning
Time.now
# => 2012-09-21 06:22:59 -0500
これも Timecop
のみですが、freeze
travel
scale
のそれぞれのメソッドが、ブロックを与えられずに呼び出された場合に、例外を raise するようになります。
ブロックなしでのこれらのメソッドの呼び出しを禁止することで、Timecop.return
の呼び忘れを防ぐことができます。
## https://github.com/travisjeffery/timecop/tree/master#timecopsafe_mode より引用
# turn on safe mode
Timecop.safe_mode = true
# check if you are in safe mode
Timecop.safe_mode?
# => true
# using method without block
Timecop.freeze
# => Timecop::SafeModeException: Safe mode is enabled, only calls passing a block are allowed.
今回の記事では、時間に依存したRubyコードをテストするために良く使われる Timecop
と ActiveSupport::Testing::TimeHelpers
の 2 つのモジュールを取り上げ、その機能の違いを比較してみました。
比較してみたところ、Timecop
モジュールのほうが多機能であることがわかりましたが、Ruby on Rails を使っている方にとっては ActiveSupport::Testing::TimeHelpers
モジュールのほうが導入が容易であり、
また個人的には「多くのテストケースにおいては ActiveSupport::Testing::TimeHelpers
モジュールでも必要十分である」とも考えています。
この記事を書き始めたきっかけは
Timecop.freeze
を ActiveSupport::Testing::TimeHelpers.freeze_time
に置き換えた結果、テストが失敗するようになったといったものからでしたが、「秒未満の時間が勝手に切り捨てられる」挙動も、考え方を変えれば「テストを書くにあたって、秒未満の時間を気にする必要がなくなる」とも言い換えられるでしょう。
これら 2つのモジュールの違いを理解した上で、より「自分のテストコードに合っているものはどちらなのか」を考えて使いわけられるようになると良いですね。