Timecop: Ruby Gem для написания тестов, чувствительных ко времени

Timecop: Ruby Gem для написания тестов, чувствительных ко времени

6 марта 2024 г.

Время — огромная тема. Я имею в виду, с начала времен. Наш вид думает о времени еще тысячелетия назад. Мы не знаем точную дату, когда это произошло, потому что не принимали во внимание время, пока не задумались об этом. Таким образом, мы могли иногда упускать из виду некоторые нюансы времени. И это случилось с командой, которая написала несколько тестов в проекте Rails.

Давным-давно существовал Ruby-проект, посвященный времени…

Я начал работать над этим проектом, взяв на себя роль технического руководителя. Я проводил больше проверок кода, чем кодирования. И одна из первых вещей, которые бросились мне в глаза при просмотре запросов на включение, заключалась в том, что команда меняла тесты, даже если это изменение было рефакторингом.

«Почему вы переписываете тесты для рефакторинга? При рефакторинге тесты должны оставаться прежними. Это говорит нам о том, что рефакторинг кода работает так же, как и старый код! — добавил я в комментариях.

Разочарованные члены команды отметили, что каждую неделю как минимум один тест оказывается неудачным, поскольку это зависит от даты. Один контроллер приложения получает результаты о людях, у которых день рождения приходится на эту неделю. Если они запускают набор тестов на другой неделе, им необходимо обновить ожидаемый результат.

Я был потрясен! Я планировал добавить задачу по рефакторингу этих тестов, пока владелец продукта не сказал: «Нет! В этом отношении нам необходимо создавать истории, которые повышают ценность для конечного пользователя. Тесты важны, но они не приносят пользы конечному пользователю!»

Мне не хотелось спорить, поэтому я стоически принял нелепую реальность. Я продолжал выполнять проверки кода и научился игнорировать изменения в тестах.

Но через некоторое время я нашел время заняться этими позорными тестами. И я нашел гем «timecop», гем, созданный Джоном Трупиано и поддерживаемый Трэвис Джеффри.

Что делает Timecop?

Timecop — это рубиновый драгоценный камень, созданный для тех конкретных случаев, когда у нас есть тесты, зависящие от времени. Это могут быть дни рождения, а также дни истечения срока действия токенов JWT или периодических счетов.

Timecop создает макет Time.now, Date.today и DateTime.now в одной строке. Как подчеркивают создатели Timecop в своем файле README, у гема нет зависимостей, поэтому он хорошо работает с любой тестовой средой Ruby.

Всего лишь два основных метода (Time.travel и Time.freeze) позволяют легко контролировать время!

With Timecop, you control the time of your tests!

Как установить Timecop

Timecop — это рубиновый драгоценный камень, доступный на Rubygems.org. Итак, вы можете просто добавить Timecop в свой Gemfile и после этого запустить bundle install. Если вы не используете Timecop для чего-то другого, кроме тестирования, добавьте драгоценный камень в тестовую группу.

Gemfile

...
group :test do
  ... # Other gems
  gem 'timecop'
end

Альтернативно запустите bundle add timecop или установите его на свой компьютер с помощью gem install timecop.

В Timecop.travel или в Timecop.freeze?

Как я уже говорил выше, вам нужно знать только два метода использования Timecop: Timecop.travel и Timecop.freeze. Но в чем разница между этими двумя методами?

Метод Timecop.travel

С помощью Timecop.travel вы устанавливаете время на определенную дату, после чего время будет перемещаться вперед.

Например, если вы Timecop.travel до 2 февраля 1993 г., 6:00:00, через десять секунд Time.now вернет 2 февраля 1993 г., 6: 00:10

groundhog_day = Time.local(1993, 2, 2, 6, 0, 0)
Timecop.travel(groundhog_day)
sleep(10)
groundhog_day == Time.now # ==> false

Метод Timecop.freeze

С другой стороны, с помощью Timecop.freeze время полностью останавливается. Итак, используя аналогичный пример, Time.now вернет ровно 2 февраля 1993 года, 6:00:00:

groundhog_day = Time.local(1993, 2, 2, 6, 0, 0)
Timecop.travel(groundhog_day)
sleep(10)
groundhog_day == Time.now # ==> true

Time is completely frozen now!

Другие полезные методы

Другие методы Timecop позволяют быстрее перемещать время или возвращаться к системной дате.

Timecop.scale

Двигайтесь в разы быстрее. Это хороший вариант для интеграционных тестов, когда вы хотите проверить выполнение некоторых заданий. Пример кода:

# 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.return

Снова переместите время на системную дату.

GROUNDHOG_DAY = Time.local(1993, 2, 2, 23, 59, 51)

def groundhog_day? = Time.now == GROUNDHOG_DAY

Timecop.freeze(GROUNDHOG_DAY)
sleep(10)

groundhog_day? # => True

Timecop.return

groundhog_day? #=> False

Установите время выполнения тестов

Информационный файл Timecop очень понятен в использовании. Для использования Timecop в тесте, чувствительном ко времени:

joe = User.find(1)
joe.purchase_home()
assert !joe.mortgage_due?
# move ahead a month and assert that the mortgage is due
Timecop.freeze(Date.today + 30) do
  assert joe.mortgage_due?
end

Для использования в группе тестов вы пишете:

describe "some set of tests to mock" do
  before do
    Timecop.freeze(Time.local(1990))
  end

  after do
    Timecop.return
  end

  it "should do blah blah blah" do
  end
end

Установите время начала тестовой среды для вашего проекта Rails

Если вы хотите, чтобы ваша тестовая среда всегда начиналась в одну и ту же дату, добавьте следующую конфигурацию:

config/environment/test.rb

... # your configuration
  config.after_initialize do
    # Set Time.now to February 2, 1993 06:00:00 AM (at this instant), but allow it to move forward
    # year, month, day, hour, min, sec
    t = Time.local(1993, 2, 2, 6, 0, 0)
    Timecop.travel(t)
  end
...
end

После этого каждый раз, когда вы будете запускать тест, дата будет начинаться 2 февраля в 6:00 утра.

Always the same date!

Вот и все! Надеюсь, Timecop оказался для вас таким же полезным, как и этот пост.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE