Rails: как протестировать обратные вызовы before_save

Учитывая типичную модель ActiveRecord, у меня часто есть обратные вызовы before_save, которые анализируют ввод, например, беря что-то вроде time_string от пользователя и анализируя его в поле time.

Эта установка может выглядеть так:

before_save :parse_time
attr_writer :time_string

private
def parse_time
  time = Chronic.parse(time_string) if time_string
end

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

Итак, для вас, опытных тестировщиков Rails, как вы справляетесь с тестированием такого рода вещей?


person Andrew    schedule 19.12.2012    source источник
comment
Где вы используете эту переменную time? это атрибут вашего объекта?   -  person Leo Correa    schedule 20.12.2012
comment
Приведенный выше пример несколько сфабрикован, но да, переменная времени является атрибутом объекта.   -  person Andrew    schedule 20.12.2012


Ответы (3)


В Ruby частные методы по-прежнему доступны через Object#send

Вы можете использовать это для своего модульного тестирования следующим образом:

project = Project.new
project.time_string = '2012/11/19 at Noon'
assert_equal(project.send(:parse_time), '2012-11-19 12:00:00')
person Unixmonkey    schedule 19.12.2012
comment
Интересно, я не знал, что #send так работает. Спасибо! - person Andrew; 20.12.2012

Что я хотел бы сделать, так это сохранить состояние экземпляра new или build вашего объекта, сохранить объект и сделать утверждение или ожидание на основе значения атрибута, который был изменен before_save

post = Post.new
post.time_string = '2012/11/19'
expected_time = Chronic.parse(post.time_string)
post.save
assert_equal(post.time, expected_time)

Таким образом вы проверяете поведение объекта, а не обязательно реализацию метода.

person Leo Correa    schedule 19.12.2012
comment
Хорошо, но у этого есть два основных недостатка: (1) это медленно. (2) #save запускает все обратные вызовы, и если другой обратный вызов выдает ошибку, то тест для него также не будет выполнен, даже если он может работать. - person Andrew; 20.12.2012
comment
Мне также нравится этот способ, так как не должно иметь значения, как работает внутреннее устройство, пока конечное состояние правильное, но есть преимущества (скорость, охват, понимание) для непосредственного тестирования методов. - person Unixmonkey; 20.12.2012
comment
Re: тестирование поведения по сравнению с реализацией, я понимаю, что вы имеете в виду, но цель здесь — проверить поведение метода изолированно, а не интеграционный тест, охватывающий несколько вариантов поведения. Тестирование методов по отдельности не означает, что вы обязательно тестируете реализацию, а не поведение. - person Andrew; 20.12.2012

Бывают случаи, когда у меня есть условие if для моих обратных вызовов, и в этом случае я использую run_callbacks.

before_save :parse_time, :if => Proc.new{ |post| post.foo == 'bar' } 

проходит положительный тест

post = Post.new
post.foo = 'bar'
expected_time = Chronic.parse(post.time_string)
post.run_callbacks :before_save
assert_equal(post.time, expected_time)

и отрицательно по

post = Post.new
post.foo = 'wha?'
post.run_callbacks :before_save
assert_nil(post.time)

См. API и блог для получения дополнительной информации.

person eebbesen    schedule 03.06.2016