【问题标题】:Optimize Rails RSpec Tests优化 Rails RSpec 测试
【发布时间】:2013-07-27 01:34:48
【问题描述】:

我正在为我的 Rails 4 应用程序进行测试,而且我对使用 RSpec 还是很陌生。我有一个名为 AppsController 的控制器,它具有标准索引、新、显示、创建...方法,它们都按照 Rails 建议的方式工作等。“新”创建对象的新实例并创建实际保存它、显示、显示它和索引显示所有对象。这是我目前的测试,任何人都可以看到任何潜在的问题或我可以改进的地方吗?

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

【问题讨论】:

    标签: ruby-on-rails ruby rspec factory-bot ruby-on-rails-4


    【解决方案1】:

    小事 - 您的 #http_success 方法正在测试完全相同的东西两次。

    您还可以通过在您的 #before 块之后放置 #let 语句来排除对应用程序的引用:

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

    然后在你的规格中,只是

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

    编辑: 那么操作的顺序将是 1)@developer 在#before 块中创建,2)规范被输入,3)在规范中第一次引用app 时,#let 块将创建一个实例一个应用程序。

    这意味着您无法在#index 规范中排除应用创建,因为在这种情况下,规范将在创建应用之前调用该操作。

    【讨论】:

    • 谢谢,我是否还必须将“开发人员”添加到“应用程序”下的 FactoryConfig 中,因为开发人员有_many 应用程序,或者它的方式和“新”方法都可以吗?控制器以这种方式设置它'@app = current_developer.apps.new',我该如何添加测试?
    • 谢谢我修复了它,现在索引测试可以工作了,对上面的评论有什么建议吗?
    • 我不同意这些。
    【解决方案2】:

    读你的代码我想到的几点:

    您不需要在不带参数的方法调用中包含括号。只需http_success 即可。

    您应该尝试始终如一地使用现代 RSpec 期望语法。代替assigns(:app).should eq(app),使用expect(assigns(:app)).to eq(app)。 (对此有一个例外,即对模拟的期望(即should_receive(:message)),它只会采用从RSpec 3 开始的现代期望语法。

    对于控制器规范,我喜欢为实际调用该操作的每个操作创建一些小方法。您会注意到在GET #show 规范中多次调用get :show, id: app。为了进一步完善您的规范,您可以改为在 describe 块中编写以下方法:

    def show!
      get :show, id: app
    end
    

    尝试一致地使用一种哈希语法。更重要的是,Rails 4 不能与 Ruby 1.8 一起运行,因此(几乎)没有理由使用 hash-rocket Hash 语法。

    如果我变得非常非常挑剔,我通常会认为规范中的实例变量是一种气味。在几乎所有情况下,实例变量都应该重构为记忆化的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
    

    我知道最后一个是个人喜好,而不是形而上学的真理,甚至不一定是广泛传播的标准惯例,因此您可以选择接受或放弃。我更喜欢它,因为它可以让我的规范非常快速,并且当我的控制器动作做太多时给我一个非常清晰的启发式,我可能需要考虑重构。这可能是一个好习惯。

    【讨论】:

    • 感谢您的建议,我重构了一些def方法并更新了语法。就存根测试而言,我不确定如何用我目前拥有的东西来实现它们。此外,当我运行规范测试时,我得到了 13 个通过和 5 个失败。我用我的完整代码和底部的确切错误更新了我的问题,我不确定是什么原因造成的。有什么建议吗?
    【解决方案3】:

    首先,我不确定,但我怀疑您的 invalid app 工厂可能是错误的。你是说

      factory :invalid_app, parent: :app do
        name nil
        tos '0'
      end
    
    • nil 作为红宝石 NilClass 而不是 "nil" 作为字符串?

    至于其他有关清理和东西的 cmet,这是我的一些想法。

    您可以通过为每个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
    

    或者,将let 更改为let!,这将在规范实际运行之前强制创建。

    至于其他失败,我认为@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
    

    对于其他错误,您可能需要开始调试代码。你确定它应该工作吗?你看过输出日志吗?也许测试在那里工作并在您的控制器代码中发现错误。您是否进行过任何逐步调试?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多