2013-07-27 100 views
0

我正在爲我的Rails 4應用程序的測試工作,我很新的使用RSpec。我有一個名爲AppsController的控制器,它具有標準索引,新的,顯示,創建...方法,它們都以Rails建議的方式工作。「新建」創建對象的新實例並實際保存它,顯示,顯示它和索引顯示所有的對象。這裏是我目前的測試,任何人都可以看到任何潛在的問題或我可以改進的東西?優化Rails的RSpec測試

FactoryGirl.define do 
    factory :developer do 
    email '[email protected]' 
    password 'new_york' 
    password_confirmation 'new_york' 
    tos '1' 
    end 

    factory :app do 
    name 'New App' 
    tos '1' 
    end 

    factory :invalid_app, parent: :app do 
    name 'nil' 
    tos '0' 
    end 
end 

require 'spec_helper' 

def create_valid! 
    post :create, app: app_attributes 
end 

def create_invalid! 
    post :create, app: app_invalid_attributes 
end 

def show! 
    get :show, id: app 
end 

def update_valid! 
    put :update, id: app, app: app_attributes 
end 

def update_invalid! 
    put :update, id: app, app: app_invalid_attributes 
end 

def delete! 
    delete :destroy, id: app 
end 

def http_success 
    expect(response).to be_success 
end 

def expect_template(view) 
    expect(response).to render_template(view) 
end 

describe AppsController do 
    render_views 

    before(:each) do 
    @developer = FactoryGirl.create(:developer) 
    @developer.confirm! 
    sign_in @developer 
    end 

    let(:app) { FactoryGirl.create(:app, developer: @developer) } 
    let(:app_attributes) { FactoryGirl.attributes_for(:app) } 
    let(:app_invalid_attributes) { FactoryGirl.attributes_for(:invalid_app) } 

    describe 'GET #index' do 
    it 'responds with an HTTP 200 status' do 
     get :index 
     http_success 
    end 

    it 'renders the :index view' do 
     get :index 
     expect_template(:index) 
    end 

    it 'populates @apps with the current_developers apps' do 
     app = FactoryGirl.create(:app, :developer => @developer) 
     get :index 
     expect(assigns(:app)).to eq([app]) 
    end 
    end 

    describe 'POST #create' do 
    context 'with valid parameters' do 
     it 'creates a new app' do 
     expect { create_valid! 
     }.to change(App, :count).by(1) 
     end 

     it 'redirects to the new app keys' do 
     create_valid! 
     expect(response).to redirect_to keys_app_path(App.last) 
     end 
    end 

    context 'with invalid parameters' do 
     it 'does not create the new app' do 
     expect { create_invalid! 
     }.to_not change(App, :count) 
     end 

     it 'renders the :new view' do 
     create_invalid! 
     expect_template(:new) 
     end 
    end 
    end 

    describe 'GET #show' do 
    it 'responds with an HTTP 200 status' do 
     show! 
     http_success 
    end 

    it 'renders the :show view' do 
     show! 
     expect_template(:show) 
    end 

    it 'populates @app with the requested app' do 
     show! 
     expect(assigns(:app)).to eq(app) 
    end 
    end 

    describe 'PUT #update' do 
    context 'with valid parameters' do 
     it 'locates the requested app' do 
     update_valid! 
     expect(assigns(:app)).to eq(app) 
     end 

     it 'changes app attributes' do 
     update_valid! 
     expect(app.name).to eq('Updated App') 
     end 

     it 'redirects to the updated app' do 
     update_valid! 
     expect(response).to redirect_to app 
     end 
    end 

    context 'with invalid parameters' do 
     it 'locates the requested app' do 
     update_invalid! 
     expect(assigns(:app)).to eq(app) 
     end 

     it 'does not change app attributes' do 
     update_invalid! 
     expect(app.name).to_not eq('Updated App') 
     end 

     it 'renders the :edit view' do 
     update_invalid! 
     expect_template(:edit) 
     end 
    end 
    end 

    describe 'DELETE #destroy' do 
    it 'deletes the app' do 
     expect { delete! 
     }.to change(App, :count).by(-1) 
    end 

    it 'redirects to apps#index' do 
     delete! 
     expect(response).to redirect_to apps_url 
    end 
    end 
end 

count should have been changed by -1, but was changed by 0 - on DELETE #destroy 

expecting <"new"> but rendering with <[]> - on POST #create 

expected: "Updated App" 
    got: "New App"  - on PUT #update 

expecting <"edit"> but rendering with <[]> - on PUT #update 

expected: [#<App id: nil, unique_id: "rOIc5p", developer_id: 18, name: "New App">] 
    got: nil   - on GET #index 

回答

0

小東西 - 你的#http_success方法測試兩次完全相同的事情。

你也可以通過把一個#let聲明您#before塊後立即分解出到應用程序中的引用:

let(:app) { FactoryGirl.create(:app, developer: @developer) } 

然後在您的規格,只是

it 'renders the :show view' do 
    get :show, id: app 
    expect_template(:show) 
end 

編輯: 然後操作順序將爲1)在#before塊中創建@developer,2)輸入規範,3)在spec中第一次引用app時,#let塊將創建一個應用程序實例。

這意味着您不能在#index規範中將應用程序創建分解出來,因爲在這種情況下,規範將在創建應用程序之前調用該操作。

+0

謝謝,我還必須在'app'下添加'developer'到FactoryConfig,因爲開發者has_many應用程序,或者它的方式很好,也適用於我的控制器將其設置爲'new'的方法方式'@app = current_developer。apps.new「,我如何爲此添加測試? – ny95

+0

謝謝我修正了它,現在索引測試工作,對上面的評論的任何建議? – ny95

+0

我不反對任何。 – bgates

0

有幾件事情我認爲,閱讀你的代碼:

你並不需要包括在方法調用的括號沒有參數。只需http_success將工作。

您應該嘗試始終如一地使用現代RSpec期望語法。而不是assigns(:app).should eq(app),請使用expect(assigns(:app)).to eq(app)。 (有一個例外,這是模擬的期望(即should_receive(:message)),它只會採用現代期望的語法,從RSpec 3開始

對於控制器規格,我喜歡爲每個動作創建一些方法。實際上調用動作你會發現你叫get :show, id: app多次在GET #show規格乾涸的規格多一點,你可以代替寫describe塊內的以下方法:

def show! 
    get :show, id: app 
end 

嘗試一致地使用一種Hash語法,而且Rails 4不能在Ruby 1.8中運行,所以幾乎沒有理由使用h灰火箭哈希語法。

如果我真的變得很挑剔,我通常會認爲spec中的實例變量是一種氣味。在幾乎所有情況下,實例變量都應重構爲已記憶的塊。

如果我得到真的,真的,真的挑剔,我更願意認爲控制器的規格,如您作爲一個嚴格的單元測試控制器,而不是一個集成測試(這就是水豚是)的,因此你不應該運用你的模型層。您應該只測試您的控制器是否將正確的消息發送到模型層。換句話說,所有模型層的東西都應該被刪除掉。例如:

describe 'GET #show' do 
    let(:app) { stub(:app) } 

    before do 
    App.stub(:find).and_return(app) 
    end 

    it 'populates @app' do 
    get :show, id: app 
    assigns(:app).should eq(app) 
    end 
end 

我知道這最後是個人喜好,而不是形而上學的真理,甚至不一定是廣泛分佈的標準慣例,所以你可以選擇要麼接受,要麼離開它。我更喜歡它,因爲它能夠保持我的規格非常快,並且當我的控制器操作執行得太多時,給了我一個非常明確的啓發式,我可能需要考慮重構。這可能是一個很好的習慣。

+0

感謝您的建議,我重構了一些方法來確定並更新語法。至於殘存測試去我不知道如何實現他們與我目前有。另外當我運行規格測試時,我得到13通過,5失敗。我用我的完整代碼和底部的確切錯誤更新了我的問題,我不確定是什麼造成了這種情況。有什麼建議麼? – ny95

0

首先,我不確定,但我懷疑你的invalid app工廠可能是錯誤的。您的意思是

factory :invalid_app, parent: :app do 
    name nil 
    tos '0' 
    end 
  • nil作爲紅寶石NilClass"nil"作爲一個字符串?

至於其他有關清理和東西的意見,這裏有一些我的想法。

通過對每個describe使用before塊,可以避免使用某些輔助方法和重複操作。以剛剛索引測試,你可以有更多的東西一樣

describe 'GET #index' do 
    before do 
    get :index 
    end 
    it 'responds with an HTTP 200 status' do 
    http_success 
    end 

    it 'renders the :index view' do 
    expect_template(:index) 
    end 

    it 'populates @apps with the current_developers apps' do 
    expect(assigns(:app)).to eq([app]) 
    end 
end 

另請注意,你不需要重新app,因爲你let根據需要做。

在失敗,我懷疑delete計數更改可能會失敗,因爲在期望內,測試框架正在創建一個新的應用程序(從let),然後刪除它,導致計數更改爲0.對於該測試,您需要確保您的app是在您的期望之外創建的。由於您使用let,你能做到這一點是這樣的:

describe 'DELETE #destroy' do 
    it 'deletes the app' do 
    # ensure that app is already created 
    app 
    expect { 
     delete! 
    }.to change(App, :count).by(-1) 
    end 
end 

或者,改變letlet!這將迫使創建規範之前實際運行。

至於其他的失敗,想@DanielWright建議的幫手方法,我發現那些調試複雜。例如,我看不到您將應用名稱設置爲「更新的應用」的位置。也許更清楚的測試(對於那個特定的測試)不會使用助手方法,但可以更明確。類似於

describe 'PUT #update' do 
    let(:app_attributes) { FactoryGirl.attributes_for(:app, name: 'The New App Name') } 
    before do 
    put :update, id: app, app: app_attributes 
    end 
    context 'with valid parameters' do 
    it 'locates the requested app' do 
     expect(assigns(:app)).to eq(app) 
    end 

    it 'changes app attributes' do 
     # notice the reload which will make sure you refetch this from the db 
     expect(app.reload.name).to eq('The New App Name') 
    end 

    it 'redirects to the updated app' do 
     expect(response).to redirect_to app 
    end 
    end 
end 

對於其他錯誤,您可能需要開始調試代碼。你確定它應該工作嗎?你看過輸出日誌嗎?也許測試正在做那裏的工作,並找到控制器代碼中的錯誤。你有沒有做過任何直接調試?