【问题标题】:Speeding up rspec controllers test: using before all fails?加速 rspec 控制器测试:在所有失败之前使用?
【发布时间】:2011-10-06 15:34:27
【问题描述】:

我有一个简单的控制器测试,包含 a.o.以下代码:

context "POST :create" do
  before (:each) do
    post :create, :user_id => @user.id,
         :account => { .. some data ... }
  end
  it { response.status.should == 201 }
  it { response.location.should be_present }
end

现在我想到了一个非常简单的方法来加速这个测试,并使用before(:all) 而不是before(:each)。在这种情况下,帖子只会发布一次。

所以我写道:

context "POST :create" do
  before (:all) do
    post :create, :user_id => @user.id,
         :account => { .. some data ... }
  end
  it { response.status.should == 201 }
  it { response.location.should be_present }
end

然后我收到以下错误:

 RuntimeError:
   @routes is nil: make sure you set it in your test's setup method.

这是设计使然吗?有没有办法绕过它?

【问题讨论】:

  • 您找到解决方案了吗?我遇到了同样的问题。

标签: ruby-on-rails rspec rspec2


【解决方案1】:

我在 rspec 邮件列表上问了这个问题,得到了@dchelimsky 本人的以下回复:

是的。 rspec-rails 包装了 rails 的测试框架,其中没有 before(:all) 概念,因此在每个示例之前都会重置所有数据。即使我们想在 rspec-rails 中支持这一点(我不这样做),也需要先对 rails 进行更改。

因此在before(:all) 中无法进行控制器调用,它只能用于设置您的数据库或实例变量。

【讨论】:

    【解决方案2】:

    如果你想走脏全局变量的方式并从加速增加中受益,你可以使用这个,但是注意。这种混乱的逻辑可以完成这项工作,但通过清晰易读的测试破坏了驾驶的目的。使用 yield 重构帮助器是非常值得推荐的。

    describe PagesController do
      describe "GET 'index'" do
        before(:each) do
          GLOBAL ||= {}
          @response = GLOBAL[Time.now.to_f] || begin
            get :index
            response
          end
        end
        it { @response.should redirect_to(root_path) }
        it { @response.status.should == 301 }
        it { @response.location.should be_present }
      end
    end
    

    您可以在规范/支持中放入您选择的文件中的重构如下

    RSPEC_GLOBAL = {}
    
    def remember_through_each_test_of_current_scope(variable_name)
      self.instance_variable_set("@#{variable_name}", RSPEC_GLOBAL[variable_name] || begin
        yield
      end)
      RSPEC_GLOBAL[variable_name] ||= self.instance_variable_get("@#{variable_name}")
    end
    

    因此,测试文件中的代码变为:

    describe PagesController do
      describe "GET 'index'" do
        before(:each) do
          remember_through_each_test_of_current_scope('memoized_response') do
            get :index
            response
          end
        end
        it { @memoized_response.should redirect_to(root_path) }
        it { @memoized_response.status.should == 301 }
        it { @memoized_response.location.should be_present }
      end
    end
    

    希望对你有帮助,再次谨慎使用

    【讨论】:

      【解决方案3】:

      我不确定这是否是个好主意,但在 before(:each) 块中使用 ||= 设置类变量似乎可行:

      describe PagesController do
        describe "GET 'index'" do
          before(:each) do
            @@response ||= begin
              get :index
              response
            end
          end
          it { @@response.should redirect_to(root_path) }
          it { @@response.status.should == 301 }
          it { @@response.location.should be_present }
        end
      end
      

      更新

      另一种可能更简洁的方法是在一个规范中包含多个断言。添加:aggregate_failures 标记(或将断言包装在aggregate_failures {...} 块中)将分别打印每个失败,这提供了单独测试的粒度:

      describe PagesController do
        describe "GET 'index'" do
          it "redirects to homepage", :aggregate_failures do
             get :index
             expect(response).to redirect_to(root_path)
             expect(response.status).to eq(301)
             expect(response.location).to be_present
          end
        end
      end
      

      【讨论】:

      • 你试过了吗?当我对此进行测试时,POST 甚至都不起作用,因为您还没有处于任何控制器上下文中。
      • 糟糕,我的意思是说没有尝试过这个。用不同的技术更新了答案。
      • 现在你再次执行before(:each),这正是我想要避免的,然后有很多更漂亮/可读的方式来编写它。如果你使用before :each,你可以写get :index并使用response
      • @nathanvda 注意使用类变量和||= - 这应该只在第一次执行代码。
      • 为什么这个答案没有被接受?有人可以评论这是否是一个好习惯。它似乎确实有效
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-27
      • 1970-01-01
      • 2014-02-11
      相关资源
      最近更新 更多