Юнит-тестирование контроллера в Рубин на рельсы


Я хочу, чтобы это был общий вопрос о создании эффективного набора тестов для действия контроллера.

Я включают следующие ингредиенты:

  • Рубин на рельсы
  • Для RSpec: тестирование рамках. Я размышлял о ванили рельсы вопрос, но лично я использую RSpec и я чувствую, что многие другие люди тоже. Принципы, которые выходят из этой дискуссии должны быть переносимыми на другие рамки тестирования, хотя.
  • Будем нумеровать: я включаю это, чтобы дать пример кода, реализация blackboxed к контроллеру. Это крайний пример просто, используя модели, методы, как @программы = программы.все. Я решил пойти по этому пути, чтобы включить дополнительный фактор для обсуждения, а чтобы показать, что те же принципы применимы ли через внешний код (например, код модели) или внешних модулей.

Там, кажется, не хватает, учитывая мой скромный гугл-фу, стиля руководства для тестирования вас на этом уровне, поэтому я надеюсь, что на мне улучшить мой код, это может стать полезным руководством для моих попутчиков.

Для примера, допустим у меня на данный момент в моем контроллере следующим образом:

class ProgramssController < ApplicationController
  def index
    @programs = Program.paginate :page => params[:page], :per_page => params[:per_page] || 30
  end
end

Боковой панели: для тех, кто незнаком с will_paginate, это кнопки на и ActiveRecord связь (все, во-первых, граф, to_a и другие примеры таких методов) и обеспечивает постраничную результирующий набор класса WillPaginate::коллекции, которая в основном ведет себя как массив, несколько полезных методов члена.

Каковы эффективные тесты, которые я должен выполнить в этой ситуации? Используем RSpec, это то, что я задумывался на данный момент:

describe ProgramsController do
  def mock_program(stubs={})
    @mock_program ||= mock_unique_program(stubs)
  end

  def mock_unique_program(stubs={})
    mock_model(Program).as_null_object.tap do |program|
      program.stub(stubs) unless stubs.empty?
    end
  end

  describe "GET index" do
    it "assigns @programs" do
      Program.stub(:paginate) { [mock_program] }
      get :index
      response.should be_success
      assigns(:programs).should == [mock_program]
    end

    it "defaults to showing 30 results per page" do
      Program.should_receive(:paginate).with(:page => nil, :per_page => 30) do
        [mock_program]
      end
      get :index
      response.should be_success
      assigns(:programs).should == [mock_program]
    end

    it "passes on the page number to will_paginate" do
      Program.should_receive(:paginate).with(:page => '3', :per_page => 30) do
        [mock_program]
      end
      get :index, 'page' => '3'
      response.should be_success
      assigns(:programs).should == [mock_program]
    end

    it "passes on the per_page to will_paginate" do
      Program.should_receive(:paginate).with(:page => nil, :per_page => '15') do
        [mock_program]
      end
      get :index, 'per_page' => '15'
      response.should be_success
      assigns(:programs).should == [mock_program]
    end
  end
end

Я написал это со следующими принципами:

  • Не тест не контроллер код: я не вникаю в реальную работу will_paginate на всех и абстрагироваться от его результатов.
  • Проверить все код контроллера: контроллер делает четыре вещи: присваивает @программы, переходит на страницу параметров модели, проходит по per_page параметра модели, и по умолчанию per_page параметр 30, и ничего больше. Каждый из этих вещи тестируются.
  • Нет ложных срабатываний: если вы убираете способ тела из индекса, все проверить не удастся.
  • Порадуюсь, когда это возможно: нет доступа к базе данных (другими ApplicationController логике вопреки)

Мои опасения, и поэтому я отправляю это здесь:

  • Я слишком педантичен? Я понимаю, что TDD это коренится в тщательное тестирование. Действительно, если этот регулятор действует как часть более широкого применения, и сказать, он остановился, проходя на страницы, в результате поведение нежелательно. Тем не менее, целесообразно, чтобы проверить такие элементарные код с такой строгостью?
  • Я тестирование вещи, которые я не должен быть? Я не испытывая, что я должен? Я думаю, что я сделал хорошую работу на этом, если что, выехав на сторону тестирования слишком много.
  • Это will_paginate глумится уместно? Вы видите, например, с последних трех тестов, я возвращать массив, содержащий только одну программу, в то время как will_paginate вполне рискует вернуться больше. А на самом деле вникая этого поведения, добавив, скажем 31 записи, может (?) нарушать модульного кода и тестирования, я немного неловко возвращая нереально или ограничительных пробный результат.
  • Может ли тест код упростить? Это кажется довольно длинным, чтобы проверить одну строку кода контроллера. Пока я в полной мере оценить удивительность ТДД, кажется, что бы Бог меня. При написании этих случаях испытания были не слишком интенсивный от головного мозга и в основном сводилось к веселой дрель ВИМ, я чувствую, что, учитывая все обстоятельства, написав этот набор тестов может стоить больше времени, чем он экономит, даже в долгосрочной перспективе.
  • Это хороший набор тестов? Общий вопрос.


Комментарии
4 ответа

Вы забыли, чтобы проверить, что представление должно быть вынесено. Если вы используете эту, ваши характеристики будут намного чище. Последние четыре должны быть стандартными первомайскую. Увидеть это в качестве примера.

describe ProgramsController do
let(:programs) { [mock_model(Program)] }

describe "GET index" do
it { should paginate(Program).with_default_per_page(30) }

describe "after pagination" do
before(:each) do
Program.stub(:paginate).and_return(programs)
get :index
end

it { should assign_to(:programs).with(programs) }
it { should respond_with(:success) }
it { should render_template(:index) }
it { should_not set_the_flash }
end
end
end

18
ответ дан 8 февраля 2011 в 04:02 Источник Поделиться

Вы закодированы ваши принципы довольно разумно.

Мое единственное реальное предложение только одно утверждение за тест, включая #should_receive или #должны= заявлениями. Целью большинства тестов, кажется, чтобы проверить одну единицу функциональности, поэтому нет необходимости повторять утверждения, особенно когда они являются зависимыми. Это поможет более четко сосредоточиться на цели индивидуального испытания, сократив их объем и повысить читаемость.

Насколько стиль для RSpec, есть несколько идиом, которые я видел чаще используется:


  • используя #and_return в место возврат значений из блоков

  • используя mock_model

  • делаешь настройки В до блоки

В целом, я бы скорее написал так:

describe ProgramsController do
before(:each) do
@mock_programs = [mock_model(Program)]
Program.stub(:paginate).and_return(@mock_programs)
end

describe "GET index" do
it "succeeds" do
get :index

response.should be_success
end

it "renders the index template" do
get :index

response.should render_template("programs/index")
end

it "assigns @programs" do
get :index

assigns(:programs).should == [mock_program]
end

it "defaults to showing 30 results per page" do
Program.should_receive(:paginate).with(:page => nil, :per_page => 30).and_return(@mock_programs)

get :index
end

it "passes on the page number to will_paginate" do
Program.should_receive(:paginate).with(:page => '3', :per_page => 30).and_return(@mock_programs)

get :index, 'page' => '3'
end

it "passes on the per_page to will_paginate" do
Program.should_receive(:paginate).with(:page => nil, :per_page => '15').and_return(@mock_programs)

get :index, 'per_page' => '15'
end
end
end

Я думаю, для общества рамках, которые вы предоставили, это хороший набор тестов. Вы можете быть в состоянии пропустить тест на базовые успех, поскольку все остальные анализы зависят от успешных действий. Я лично не могу говорить слишком глубоко о тестировании will_paginate, поскольку я делаю так же, при тестировании использовать его от контроллера. Если он хорошо опробован в модели, которая использует его, вы рассмотрели основные поведения.

3
ответ дан 1 февраля 2011 в 01:02 Источник Поделиться

просто то, что я рассмотрел как спрятать разбит позади вспомогательный метод, а затем заглушку вспомогательный метод таким образом, вы можете поменять реализации пагинации в любое время вы хотите
а потом просто проверить вспомогательный метод передачи параметров will_paginate

class ProgramssController < ApplicationController
def index
@programs = paginate_progams(params)
end
end

теперь вам огрызок метод paginate_programs
и тогда вы можете спектрометр вспомогательный метод разбиения по страницам

1
ответ дан 7 февраля 2011 в 10:02 Источник Поделиться

Я несколько лет из Рор, но работал в компании, которая была тяжелой на тестировании мы использовали для RSpec.

В таком случае мы бы испытывали некоторые дополнительные моменты, в основном о содержании ответа. Несколько строк, как (синтаксис может быть совершенно не так, как сказал Я из этого):

проверить количество записей
@программами.следует.есть(30).записи

тогда была команда, чтобы проверить, если некоторые записи, в результате
@программами.следует.содержат(...)
это требует приспособления конечно.

Идея заключалась в том, чтобы убедиться, что мы могли бы заменить некоторых плагинов на новые и быть уверенным, что оно ничего не сломать или содержит ошибки. (Например, плагин acts_as_taggable была ошибка, которая сделала невозможным, чтобы удалить Теги, что было нарваться на бесконечный цикл)
И конечно, чтобы избежать программистов для внесения изменений при тестировании в браузере или похожие. С will_paginate типичная ошибка будет установить нумерацию страниц до 10, чтобы увидеть несколько страниц в браузере (потому что есть только 20 записей светильников...) и забудьте установить для этого параметра значение 30. И если это не программист, то, возможно, парень работает на HTML и CSS. Мы убедились, что каждый из этих деталей были проверены

Больше тестов бы убедится, что нужный файл будет использоваться для рендеринга.
что-то вроде ответа.следует.render_template(имя)

Это, кажется, много работы. Но на положительной стороне: когда мой первый сайт для покупателя зашел в интернет, после шести недель программирования и моя скромная 3 месяца, зная о Ruby на рельсы, у него нет ошибок и стоял 1.000 просмотров в минуту без единой строчки кода, чтобы быть изменен.

1
ответ дан 8 февраля 2011 в 08:02 Источник Поделиться