Ускорение тестирования контроллеров rspec: использовать до того, как все выйдет из строя?

У меня есть простой тест контроллера, содержащий 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.

Это по дизайну? Есть ли способ обойти это?


person nathanvda    schedule 06.10.2011    source источник
comment
Вы нашли решение этой проблемы? Я столкнулся с той же проблемой.   -  person ktusznio    schedule 22.02.2012


Ответы (3)


Я задал этот вопрос в списке рассылки rspec и получил следующий ответ от самого @dchelimsky:

Да. rspec-rails является оболочкой среды тестирования rails, в которой нет концепции before(:all), поэтому все данные сбрасываются перед каждым примером. Даже если бы мы захотели поддерживать это в rspec-rails (а я этого не делаю), сначала потребовались бы изменения в rails.

Поэтому выполнение вызовов контроллера невозможно в before(:all), его можно использовать только для настройки вашей БД или переменных экземпляра.

person nathanvda    schedule 07.04.2012

Если вы хотите пойти по пути грязной глобальной переменной и получить выгоду от увеличения скорости, вы можете использовать это, но осторожно. Эта запутанная логика делает свое дело, но лишает смысла вождение с кристально четкими тестами. Рефакторинг в помощнике с 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

Надеюсь, это поможет, и еще раз, используйте с осторожностью

person giglemad    schedule 12.03.2014

Я не уверен, что это хорошая идея, но установка переменной класса с ||= в блоке 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
person Zubin    schedule 27.02.2012
comment
Вы пробовали это? Когда я тестировал это, POST даже не работал, потому что вы еще не находитесь ни в каком контексте контроллера. - person nathanvda; 27.02.2012
comment
К сожалению, я хотел сказать, что не пробовал это. Обновленный ответ с другой техникой. - person Zubin; 28.02.2012
comment
Теперь вы снова делаете before(:each), чего я хотел избежать, а затем есть гораздо более красивые/читабельные способы написать это. Если вы делаете before :each, вы можете просто написать get :index и использовать response. - person nathanvda; 07.04.2012
comment
@nathanvda обратите внимание на использование переменной класса и ||= - это должно выполнять код только в первый раз. - person Zubin; 16.04.2012
comment
почему это не было принято в качестве ответа? Может кто-нибудь прокомментировать, является ли это хорошей практикой или нет. Кажется, это работает - person Faraaz Khan; 04.04.2013