我正在为我的Rails 4应用程序进行测试,而且我很擅长使用RSpec。我有一个名为AppsController的控制器,它有标准索引,new,show,create ...方法,它们都按照Rails建议的方式工作。“new”创建对象的新实例,创建实际保存它,显示,显示它和索引显示所有对象。以下是我目前的测试,任何人都可以看到任何潜在的问题或我可以改进的事情吗?
FactoryGirl.define do
factory :developer do
email 'example@me.com'
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 :(得分:0)
小事 - 你的#http_success方法测试完全相同的东西两次。
你也可以通过在#before块之后放一个#let语句来分解对app的引用:
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)在规范中第一次引用app
时,#let块将创建一个应用程序的实例。
这意味着您无法在#index规范中分解应用创建,因为在这种情况下,规范会在创建应用之前调用该操作。
答案 1 :(得分:0)
我想到的一些事情,阅读你的代码:
您不需要在不带参数的方法调用中包含parens。只需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
尝试一致地使用一种哈希语法。更重要的是,Rails 4无法使用Ruby 1.8运行,因此(几乎)没有理由使用哈希火箭哈希语法。
如果我变得非常,非常挑剔,我通常认为规范中的实例变量是一种气味。几乎在所有情况下,实例变量都应重构为已记忆的let
/ given
块。
如果我真的,真的,真的挑剔,我更倾向于将控制器规格视为控制器的严格单元测试,而不是集成测试(这就是Capybara的用途),因此你根本不应该运用你的模型层。您应该仅测试控制器是否正在向模型层发送正确的消息。换句话说,所有模型层的东西都应该被删除。例如:
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
我知道这最后是个人偏好,不是形而上学的事实,甚至不一定是广泛的标准惯例,所以你可以选择接受或离开它。我更喜欢它,因为它使我的规格非常快速,并且在我的控制器动作做得太多时给了我一个非常明确的启发式,我可能需要考虑重构。这可能是一个很好的习惯。
答案 2 :(得分:0)
首先,我不确定,但我怀疑你的invalid app
工厂可能是错的。你的意思是
factory :invalid_app, parent: :app do
name nil
tos '0'
end
nil
作为红宝石NilClass
而不是"nil"
作为字符串?关于清理和其他内容的其他评论,以下是我的一些想法。
通过对每个before
使用describe
块,您可以避免使用某些辅助方法和重复。只需要进行索引测试就可以得到更像
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
或者,将let
更改为let!
,这会在规范实际运行之前强制创建。
至于其他失败,想想@DanielWright提出了辅助方法,我发现那些使调试变得复杂。我无法看到您将应用名称设置为&#34;更新应用程序&#34;的位置。也许更清晰的测试(对于那个特定的测试)不会使用辅助方法,但可能更明确。像
这样的东西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
对于其他错误,您可能希望开始调试代码。你确定它应该有用吗?你看过输出日志了吗?也许测试正在执行工作并在控制器代码中发现错误。你做过任何逐步调试吗?