Рекомендации Rspec для тестирования объектов обслуживания
Я пишу тесты Rspec для объекта службы, который касается нескольких моделей, но я чувствую, что мой тест слишком зависит от внутренних компонентов метода и поэтому не имеет большого значения. Вот пример:
class MealServicer
def self.serve_meal(meal, customer)
meal.update_attributes(status: "served", customer_id: customer.id)
order = customer.order
OrderServicer.add_meal_to_order(meal, order)
CRM.update_customer_record(customer) // external API call
end
end
Я хотел бы использовать double/stubs для издевательства над тем, чтобы ничего не сохранять в тестовой базе данных (для повышения производительности). Но если я создаю дубликаты, отвечающие на сообщения, то мне кажется, что я тестирую одну конкретную реализацию метода serve_meal(), и этот тест слишком связан с этой конкретной реализацией. Например, мне нужно убедиться, что мой customer
double отвечает на order
и возвращает заглушку order
. По сути, когда все просто двойное, и я должен явно указать все зависимости, убедившись, что двойники возвращают другие удвоения, похоже, что тесты заканчиваются довольно бессмысленными. См. Здесь:
it "has a working serve_meal method" do
meal = double(:meal)
customer = double(:customer)
order = double(:order)
allow(customer).to_receive(:order).and_return(order)
allow(OrderServicer).to_receive(:add_meal_to_order).and_return(true)
allow(CRM).to_receive(:update_customer_record).and_return(true)
expect(meal).to receive(:update_attributes).once
expect(OrderServicer).to receive(:add_meal_to_order).once
expect(CRM).to receive(:update_customer_record).once
end
Есть ли другой способ проверить это полностью и осмысленно, кроме создания объектов, связанных с продуктом, клиентом и заказом, соответствующим образом (и, возможно, сохраненным в базе данных), а затем проверить, что MealServicer.serve_meal (...) обновляет свойства объекта, как ожидалось? В конечном итоге это приведет к сохранению базы данных, поскольку update_attributes выполняет вызов сохранения, а также некоторые из методов, которые я намерен включить в мой метод объектов службы.
Наконец, поскольку тесты зависят от реализации, я не могу написать тесты перед методом, что рекомендуют сторонники TDD. Это просто отвратительно. Любые советы по написанию исполнителей, но полезные тесты?
Ответы
Ответ 1
Это дилемма "Моккист против классицизма", адресованная Мартину Фаулеру Mocks Are not Stubs. Использование mocks (doubleles) повсюду обязательно потребует отработки других методов на коллаборационистах и демонстрации реализации. Это часть цены, которую вы платите за скорость и гибкость насмешек.
Другая проблема заключается в том, что для спецификации нет естественного "субъекта", потому что это метод класса. Вы получаете три объекта, каждый из которых нуждается в обновлении; в некотором смысле они являются поочередно субъектами и сотрудниками в зависимости от того, какое ожидание осуществляется. Вы можете сделать это более понятным, установив одно ожидание на пример:
describe MealServicer do
context ".serve_meal" do
let(:order) { double(:order) }
let(:meal) { double(:meal) }
let(:customer) { double(:customer, id: 123, order: order }
it "updates the meal" do
allow(OrderServicer).to_receive(:add_meal_to_order)
allow(CRM).to_receive(:update_customer_record)
expect(meal).to receive(:update_attributes).with(status: "served", customer_id: 123)
MealServicer.serve_meal(meal, customer)
end
it "adds the meal to the order" do
allow(meal).to receive(:update_attributes)
allow(CRM).to_receive(:update_customer_record)
expect(OrderServicer).to receive(:add_meal_to_order).with(meal, order)
MealServicer.serve_meal(meal, customer)
end
it "updates the customer record" do
allow(meal).to receive(:update_attributes)
allow(OrderServicer).to_receive(:add_meal_to_order)
expect(CRM).to receive(:update_customer_record).with(customer)
MealServicer.serve_meal(meal, customer)
end
end
end
Теперь заглушки - это всегда зависимости, и ожидания проверяются, что уточняет намерение спецификации.
потому что тесты зависят от реализации, я не могу написать тесты перед методом
Я не согласен. Если вы отделите ожидания, вы можете сначала протестировать и написать код, чтобы пройти тесты, если вы работаете по одному примеру за раз.
ИЗМЕНИТЬ
см. также этот пост в блоге от Myron Marston