2012-02-13 1 views
6

현재 컨트롤러 사양을 DRY 및 간결하게 유지하고 예제마다 단 하나의 주장을 유지하려고 애쓰는 중입니다. 특히 실제 컨트롤러 요청 호출을 다양한 엣지 케이스와 일치하도록 중첩 된 구조 내에 배치해야하는 경우에 어려움이 있습니다.RSpec을 사용한 DRY 컨트롤러 사양

describe MyController do 
    let(:item) { Factory(:item) } 
    subject { response } 

    describe "GET #show" do 
    before(:each) do 
     get :show 
    end 

    context "published item" do 
     it { should redirect_to(success_url) } 
    end 

    context "unpublished item" do 
     before(:each) do 
     item.update_attribute(published: false) 
     end 

     it { should redirect_to(error_url) } 
    end 
    end 
end 

는 분명히 이것은 인위적인 예이지만, 내가하고 싶은 것을하고 작동하지 않는 것을 보여

여기에 문제를 보여주기 위해 간단한 예를 들어,입니다. 주로 "게시되지 않은"컨텍스트 아래의 before 블록이 문제입니다. Context가 중첩되어있는 방식으로 인해 설정 데이터에 실제로 변경 한 내용이 인 경우get 호출이 발생합니다. 따라서 해당 컨텍스트의 예제는 실제로 의도 한 시나리오가 아닌 초기 시나리오에서 작동합니다.

나는 이런 일이 발생하는 이유와 컨텍스트가 어떻게 중첩되는지 이해합니다. 내가 가지고 같은 을 거라고 추측 무엇 나는 그것이 주어진 컨텍스트 내에서 전에 주장을 아직까지 잘 before 어떤 후 후크를 실행하고 싶은 것을 RSpec에 말할 수있는 몇 가지 방법입니다. 이것은 컨트롤러 사양에 적합합니다. 내 컨트롤러 사양에 중첩을 활용하여 get 호출을 분산시키지 않고도 가장자리 사례를 점진적으로 구현하거나 do_get 도우미 호출을 내 it 어설 션에 각각 적용 할 수 있습니다. 이것은 특히 내가 사용하고있는 맞춤 매크로 it_should과 계속 동기화하는 것이 귀찮습니다.

RSpec에는이 작업을 수행하는 데 필요한 것이 있습니까? 가까운 길을 가기 위해 사용할 수있는 트릭이 있습니까? 그것은 많은 사람들이 자신의 컨트롤러 스펙을 작성하는 것을 본 방식에 완벽하게 어울리는 것 같습니다. 내가 찾은 것에서 사람들은 기본적으로 모든 주장 앞에 도움을 청한 사람들이 do_get 인 것으로 정착했습니다. 더 좋은 방법이 있습니까? 당신을 위해

답변

6

건조 원칙 상태 : 당신은 또한 같은 익명의 상황과 사례를 분리 할 수 ​​있습니다 당신이하고있는 일은 DRY를 유지하는 것보다 여기 저기에있는 몇 가지 문자를 저장하는 것이 훨씬 더 중요합니다. 그 결과는 볼 수 있듯이 무엇을 할 수있는 계집 인 위아래로 종속성의 얽힌 웹입니다. 당신은 그것을 깨닫고 결과적으로 연약하고 부서지기 쉽습니다.

의 당신은 자세한이고 작동하는 방식으로 쓰여있어 한 것을 시작하자 :

이제 예제의 이름은 중복되고있는 전용 "지식의 조각", 수
describe MyController do 
    describe "GET #show" do 
    context "published item" do 
     it "redirects to the success url" do 
     item = Factory(:item, published: true) 
     get :show, :id => item.id 
     response.should redirect_to success_url 
     end 
    end 

    context "unpublished item" do 
     it "redirects to the error url" do 
     item = Factory(:item, published: false) 
     get :show, :id => item.id 
     response.should redirect_to error_url 
     end 
    end 
    end 
end 

각 예제의 끝에서 matchers에 의해 생성됩니다.이것은 it의 별칭 인 example 방법을 사용하여 읽을 수있는 방법으로 해결할 수 있습니다

describe MyController do 
    describe "GET #show" do 
    context "published item" do 
     example do 
     item = Factory(:item, published: true) 
     get :show, :id => item.id 
     response.should redirect_to success_url 
     end 
    end 

    context "unpublished item" do 
     example do 
     item = Factory(:item, published: false) 
     get :show, :id => item.id 
     response.should redirect_to error_url 
     end 
    end 
    end 
end 

을있다. 마른. 그리고 아주 읽기 쉽고 바꿀 수 있습니다.

describe MyController do 
    describe "GET #show" do 
    context "published item" do 
     let(:item) { Factory(:item, published: true) } 
     example do 
     get :show, :id => item.id 
     response.should redirect_to success_url 
     end 

     example do 
     # other example 
     end 
    end 
    # ... 
    end 
end 

지금 유일하게 중복 된 코드합니다 (DRY 원칙과 동일하지)는 get입니다 : 당신이 상황 중 하나에 대한 더 많은 예제를 추가 일어날 때 지금, 당신은 let 추가 할 수 있습니다. 정말로 그것에 대해 강하게 느끼는 경우 get_show(id) 또는 그와 같은 메소드로 호출을 위임 할 수 있지만 그 시점에서는별로 구매하지 않습니다. get의 API가 아래에서 변경되는 것과 같지 않으며 get의 유일한 인수는 예에서 실제로 염려하는 item의 ID입니다. 따라서 불필요한 정보는 없습니다.

subject을 사용하여 응답을 캡처하고 한 줄짜리 거래를 끝내면 실제로 읽는 것이 어려워지고 많은 돈을 절약하지 못합니다. 사실, subject을 이런 식으로 사용하려고 생각했습니다. to be a smell.

희망이 모두가 도움이됩니다.

건배, 데이비드

+0

좋은 지적. 'get' 호출이 반복 되더라도, 스펙을 명료하게 유지하는 것이 보람 될 것입니다. 그래도 REST 액션에 대한 응답을 구체화하는 매우 구체적인 사용 사례가있는 컨트롤러 스펙과 같은 느낌은 그러한 반복을 어떻게 든 줄일 수 있습니다. –

+0

Chris - 나는 일종의 지름길이 좋을 것이라는데 동의하며, 나는 그 아이디어에 대해 개방적이지만, 나는 분명히 적절한 수준의 선명도를 유지하는 것을 보았다. 아이디어가있는 경우 꼭 https://github.com/rspec/rspec-rails/issues에 기능 요청을 제출하십시오. –

3

context "unpublished item" do 
    let(:item) do 
    Factory(:item, published: false) 
    end 

    it { should redirect_to(error_url) } 
end 

사용할 수 있습니까? BTW, before은 기본적으로 before(:each)입니다. 그래야 사양을 조금 더 건조시킬 수 있습니다.

업데이트 : "지식의 모든 조각이 시스템 내에서 하나의 명확한 권위있는 표현이 있어야합니다."고

describe "GET #show" do 
    let(:show!) do 
    get :show 
    end 

    context do 
    before { show! } 

    context "published item" do 
     it { should redirect_to(success_url) } 
    end 

    # another examples with show-before-each 
    end 

    context "unpublished item" do 
    before do 
     item.update_attribute(published: false) 
     show! 
    end 

    it { should redirect_to(error_url) } 
    end 
end 
+0

이것은 확실히 아니다. 기존의 항목을 완전히 대체하는 대신이 솔루션이 수행 할 수있는 모든 항목을 편집 할 수 있습니다. 그 이유는 재사용 가능한 컨텍스트를 구축하는 것이 더 쉽기 때문에 설치 상태를 점진적으로 변경하여 이러한 컨텍스트를 결합하여 다른 순열을 테스트 할 수 있습니다. –

+0

업데이트 된 답변에서 다른 접근 방식을 살펴보십시오. 중첩 된 후크 논리를 변경할 수있는 다른 방법은 없다고 생각합니다. –

+0

나는 유사한 해결책을 사용하여 끝냈다. 아직도 조금 더러운 느낌이지만 작동합니다. 'show!'가 중첩 된 컨텍스트에서 올바르게 호출되는지 확인하고 두 번 호출되지 않도록해야합니다. 그것은 내가 원하는 것보다 더 정신적 인 오버 헤드이지만, 지금은 일을 끝내게됩니다. –