2014-05-17 2 views
10

여러 모델에 접촉하는 서비스 개체에 대한 Rspec 테스트를 작성하고 있지만 테스트가 메서드의 내부에 너무 의존적이어서 매우 중요하지 않습니다. 실제로 (성능) 시험 데이터베이스에 아무것도 저장하지 않고 동작을 모의/스텁 복식을 사용하고 싶습니다서비스 개체 테스트를위한 Rspec advice

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을 만들면 serve_meal() 메서드의 특정 구현을 테스트하는 것처럼 느껴지며이 테스트는 특정 구현과 너무 얽혀 있습니다. 예를 들어, 내 customerorder에 이중 응답하고 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 

합니다 (DATBASE에 아마도 저장) 적절하게 연결되어 실제 식사, 고객 및 주문 개체의 인스턴스를보다 철저하고 의미있게 다른이를 테스트하는 또 다른 방법이 있나요, 그리고 그 MealServicer.serve_meal을 확인하십시오 여기를 참조하십시오 (...) 예상대로 개체 속성을 업데이트합니까? update_attributes는 저장 호출을 수행하고 Service 객체 메소드에 포함 할 메소드 중 몇 가지를 수행하기 때문에 결국 데이터베이스에 저장이 끝납니다.

마지막으로 테스트가 구현에 의존하기 때문에 TDD 옹호자가 권장하는 방법보다 먼저 테스트를 작성할 수 없습니다. 이것은 거꾸로 느낀다. Performant하지만 유용한 테스트 작성에 대한 조언은 무엇입니까?

+0

그래, 당신은 기본적으로 실제 코드의 대부분을 스텁하고, 그래서 정말 당신이 실제로에서 줄을 삭제하지 않는 한 방법이 실패 할 경우가되지 않습니다 . 객체의 본질을 감안할 때, 이런 종류의 비즈니스 로직은 단위 테스트 대신 통합 레벨에서 더 많은 테스트를 수행 할 것입니다. 당신은 다른 일을하는 여러 객체를 다루고 있습니다. 그리고 실제로 당신의 방법을 읽으면, 그것은 특집 사양처럼 들립니다. 앱에서 MealServicer를 어떻게 활용하고 있는지 궁금 할 것입니다. 그러나 실제 객체 사용에 대해서는 걱정하지 마십시오.해당 메소드가 호출 될 때 발생할 것으로 예상되는 것에 대한 테스트를 작성하십시오. – agmcleod

답변

17

마틴 파울러 (Martin Fowler)의 Mocks Aren't Stubs에서 다루는 '모의자가 대 고전주의'딜레마입니다. 처음부터 끝까지 모의 (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 

이제 스텁은 항상 의존성, 그리고 기대 사양의 의도를 명확하게 테스트되고있는 것들이다. 테스트는 구현에 의존하기 때문에

, 나는 방법 내가 동의

하기 전에 테스트를 작성할 수 없습니다. 기대를 분리한다면, 한 번에 한 가지 예제로 작업한다면 테스트를 통과하고 테스트를 통과하는 코드를 작성할 수 있습니다.

편집

은 마이런 말 스톤 양은하여이 blog post 참조