2012-02-13 44 views
6

Tôi hiện đang đấu tranh một chút cố gắng giữ thông số kỹ thuật của bộ điều khiển DRY và gọn gàng và xuống một xác nhận cho mỗi ví dụ. Tôi đang chạy vào một số khó khăn đặc biệt là với nơi để đặt cuộc gọi yêu cầu điều khiển thực tế trong một cấu trúc lồng nhau để phù hợp với các trường hợp cạnh khác nhau.Thông số kỹ thuật điều khiển DRY với RSpec

Dưới đây là một ví dụ, đơn giản hóa để chứng minh vấn đề:

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 

Rõ ràng đây là một ví dụ giả tạo, nhưng nó minh họa những gì tôi muốn làm và những gì không làm việc. Chủ yếu, khối before trong bối cảnh "chưa xuất bản" là vấn đề. Điều gì xảy ra là thay đổi tôi thực hiện cho dữ liệu thiết lập thực sự xảy ra sau khi gọi cuộc gọi get do cách ngữ cảnh được lồng nhau, vì vậy ví dụ trong bối cảnh đó thực sự hoạt động với kịch bản ban đầu chứ không phải là kịch bản tôi dự định.

Tôi hiểu tại sao điều này xảy ra và bối cảnh làm tổ như thế nào. Tôi đoán những gì tôi muốn như để có một số cách để nói cho RSpec những gì tôi muốn nó chạy ngay sau bất kỳ before móc nào đúng trước bất kỳ xác nhận nào trong một ngữ cảnh nhất định. Điều này sẽ là hoàn hảo cho thông số kỹ thuật điều khiển. Tôi muốn tận dụng lợi thế của việc lồng trong thông số bộ điều khiển của tôi để dần dần xây dựng các biến thể của các trường hợp cạnh mà không cần phải phân tán cuộc gọi get hoặc thậm chí gọi đến số trợ giúp do_get vào mỗi xác nhận it của tôi. Điều này đặc biệt khó chịu để giữ đồng bộ với bất kỳ macro tùy chỉnh it_should nào tôi đang sử dụng.

Hiện tại có bất kỳ điều gì trong RSpec thực hiện việc này không? Có bất kỳ thủ đoạn nào tôi có thể sử dụng để đến gần không? Nó có vẻ hoàn toàn phù hợp với cách tôi đã nhìn thấy rất nhiều người viết thông số kỹ thuật điều khiển của họ; từ những gì tôi đã tìm thấy, mọi người đã giải quyết cơ bản để có những người giúp đỡ do_get được gọi trước mỗi xác nhận. Có cách nào tốt hơn?

Trả lời

6

Các trạng thái nguyên tắc DRY rằng "Mỗi mảnh của tri thức phải có một duy nhất, rõ ràng, đại diện có thẩm quyền trong một hệ thống." Những gì bạn đang làm là tiết kiệm hơn một vài ký tự ở đây và ở đó hơn là giữ mọi thứ KHÔ, và kết quả là một mạng lưới phụ thuộc lộn xộn lên xuống một hệ thống phân cấp, như bạn thấy, là một bitch để làm những gì bạn muốn nó và do đó mong manh và giòn.

Hãy bắt đầu với những gì bạn đã đã được viết ra một cách đó là tiết và các công trình:

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 

Bây giờ "mẩu kiến ​​thức" chỉ đang được nhân đôi là tên của các ví dụ, đó có thể được tạo bởi các đối sánh ở cuối mỗi ví dụ.Điều này có thể được giải quyết một cách có thể đọc được bằng cách sử dụng phương pháp example, đó là một bí danh của it:

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 

Có. KHÔ. Và khá dễ đọc và dễ thay đổi. Bây giờ, khi bạn tình cờ thêm ví dụ về một trong các bối cảnh, bạn có thể thêm một let:

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 

Bây giờ mã trùng lặp chỉ (không giống như nguyên tắc DRY) là get. Nếu bạn thực sự cảm thấy mạnh mẽ về nó, bạn có thể ủy thác những cuộc gọi ra một phương pháp như get_show(id) hoặc một số như vậy, nhưng nó không thực sự mua nhiều vào thời điểm đó. Không giống như API cho get sẽ thay đổi từ bên dưới bạn và đối số duy nhất cho get là id của item, mà bạn thực sự quan tâm trong ví dụ (vì vậy không có thông tin không cần thiết).

Đối với việc sử dụng subject để nắm bắt phản hồi và nhận được một lớp lót trong giao dịch, điều đó chỉ làm cho mọi thứ thực sự khó đọc và không giúp bạn tiết kiệm được nhiều. Trong thực tế, tôi đã xem xét việc sử dụng subject theo cách này to be a smell.

Hy vọng tất cả điều này sẽ hữu ích.

Chúc mừng, David

+0

Điểm tốt. Độ dài thêm có thể đáng giá để giữ cho thông số kỹ thuật rõ ràng, ngay cả khi lệnh gọi 'get' vẫn được lặp lại. Tuy nhiên, nó có cảm giác giống như thông số kỹ thuật điều khiển, có một trường hợp sử dụng rất cụ thể trong việc xác định một phản ứng với một hành động REST, bằng cách nào đó có thể làm giảm sự lặp lại như vậy. –

+0

Chris - Tôi đồng ý rằng một số loại phím tắt sẽ tốt đẹp và tôi mở cho ý tưởng này, nhưng tôi chưa thấy cái nào duy trì những gì tôi cảm thấy là mức độ rõ ràng phù hợp. Nếu bạn có bất kỳ ý tưởng nào, bằng mọi cách, vui lòng gửi yêu cầu tính năng tới https://github.com/rspec/rspec-rails/issues. –

3

Will

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

    it { should redirect_to(error_url) } 
end 

công việc cho bạn? BTW, before theo mặc định là before(:each) vì vậy bạn có thể DRY bạn chỉ số kỹ thuật nhiều hơn một chút.

UPDATE: bạn cũng có thể cô lập ví dụ với bối cảnh vô danh, như:

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

Điều này không hoàn toàn. Tôi thà có thể chỉnh sửa các mục hiện có hơn là thay thế nó hoàn toàn, mà giải pháp này sẽ làm. Lý do là nó làm cho nó dễ dàng hơn để xây dựng bối cảnh tái sử dụng mà làm cho những thay đổi gia tăng để thiết lập nhà nước để hoán vị khác nhau có thể được kiểm tra bằng cách kết hợp những bối cảnh. –

+0

Hãy xem một cách tiếp cận khác trong câu trả lời được cập nhật. Tôi nghĩ không có cách nào khác để thay đổi logic móc lồng nhau. –

+0

Tôi đã sử dụng một giải pháp tương tự. Nó vẫn cảm thấy một chút bẩn, nhưng nó hoạt động. Bạn chỉ cần tiếp tục đảm bảo rằng 'show!' Được gọi chính xác trong các ngữ cảnh lồng nhau và đảm bảo rằng nó không kết thúc được gọi hai lần. Đó là chi phí tinh thần nhiều hơn tôi muốn, nhưng nó được công việc làm cho bây giờ. –

Các vấn đề liên quan