0

我正在为我的 Rails 4 应用程序进行测试,而且我对使用 RSpec 还是很陌生。我有一个名为 AppsController 的控制器,它有标准的 index、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
4

3 回答 3

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)@developer 在#before 块中创建,2)规范被输入,3)在规范中的第一个引用app,#let 块将创建一个实例应用程序。

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

于 2013-07-27T01:46:52.397 回答
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为 alet!将在规范实际运行之前强制创建。

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

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

于 2015-01-07T09:18:29.283 回答
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

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

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

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

于 2013-07-27T03:27:58.227 回答