Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/mercurial/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby on rails 用于测试服务对象的Rspec建议_Ruby On Rails_Ruby_Unit Testing_Rspec - Fatal编程技术网

Ruby on rails 用于测试服务对象的Rspec建议

Ruby on rails 用于测试服务对象的Rspec建议,ruby-on-rails,ruby,unit-testing,rspec,Ruby On Rails,Ruby,Unit Testing,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_

我正在为一个涉及多个模型的服务对象编写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来模拟行为,而不实际将任何内容保存到测试数据库(为了性能)。但是如果我创建了响应消息的double,那么感觉就像我在测试一个特定的service_-mean()方法实现,而这个测试与该特定实现耦合太多。例如,我需要确保我的
customer
double响应
order
并返回一个
order
存根。本质上,当一切都只是一个double,我必须通过确保double返回其他double来显式地声明所有依赖项时,感觉测试最终是毫无意义的。请看这里:

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
除了实例化适当连接(并可能保存到数据库)的实际用餐、客户和订单对象,然后检查MealService.serve_Mein(…)是否按预期更新了对象属性之外,是否还有其他方法可以对此进行彻底而有意义的测试?这最终会保存到数据库中,因为update_属性会执行一个save调用,我打算在我的服务对象方法中包含的几个方法也是如此


最后,因为测试依赖于实现,所以我不能在方法之前编写测试,这是TDD倡导者推荐的。这感觉有点倒退。关于编写性能良好但有用的测试有什么建议吗

这就是马丁·福勒的《嘲讽者与古典主义者》中提到的“嘲讽者与古典主义者”的困境。在整个过程中使用mock(double)必然需要删除协作者上的其他方法并公开实现。这是你为模仿的速度和灵活性所付出的部分代价

另一个问题是规范没有自然的“主题”,因为这是一个类方法。最终得到三个对象,每个对象都需要更新;从某种意义上说,他们是交替的主体和合作者,这取决于正在实现的期望。通过为每个示例设置一个期望值,您可以更清楚地了解这一点:

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的这篇文章

是的,您基本上是在存根大多数实际代码,因此,除非您实际删除其中的行,否则该方法不会真正失败。鉴于对象的性质,这种业务逻辑是我将在集成级别而不是单元测试上进行更多测试的。您正在处理多个执行不同操作的对象。真正阅读你的方法,听起来像是一个功能规范。我很好奇你是如何在应用程序中使用MealService的。但是不要担心使用那些实际对象。为调用该方法时预期发生的情况编写测试