Опубликован: 27.01.2016 | Доступ: свободный | Студентов: 914 / 58 | Длительность: 23:07:00
Лекция 8:

Войти, выйти

Введение в Cucumber (опционально)

Закончив основу системы аутентификации примера приложения, мы собираемся воспользоваться подвернувшейся возможностью для того чтобы показать как написать тесты входа с помощью Cucumber - популярного инструмента для разработки через поведение, который пользуется значительной популярностью в сообществе Ruby. Этот раздел необязателен и может быть пропущен без потери целостности повествования.

Cucumber позволяет определять текстовые истории описывающие поведение приложения. Множество Rails программистов находят Cucumber особенно полезным при работе над клиентскими проектами; поскольку они могут быть прочитаны даже технически не подкованными пользователями, тесты на Cucumber могут быть прочитаны (а иногда даже написаны) клиентом. Конечно же, применение тестового фреймворка, который не является чистым Ruby, имеет и оборотную сторону, и я считаю что текстовые истории зачастую могут быть излишне многословными. Тем не менее, Cucumber занял прочные позиции в Ruby-инструментарии тестирования и мне особенно нравится его акцент на поведении верхнего уровня, а не на деталях реализации.

Поскольку акценты в этой книге смещены в сторону RSpec и Capybara, последующая презентация совершенно не претендует на полноту и исчерпывающее раскрытие темы. Ее цель - просто дать вам возможность ощутить вкус Cucumber-а (несомненно свежий и сочный) — если он поразит ваше воображение, существуют целые книги на эту тему готовые удовлетворить ваш аппетит. (Я особенно рекомендую The RSpec Book (David Chelimsky) и Rails 3 in Action (Ryan Bigg и Yehuda Katz), и The Cucumber Book (Matt Wynne и Aslak Hellesoy).)

Установка и настройка

Для того чтобы установить Cucumber, во-первых, добавьте гем cucumber-rails и служебный гем database_cleaner в группу :test в Gemfile (Листинг 8.31).

.
.
.
group :test do
  .
  .
  .
  gem 'cucumber-rails', '1.4.0', :require => false
  gem 'database_cleaner', github: 'bmabey/database_cleaner'
end
.
.
.
Листинг 8.31. Добавление гема cucumber-rails в Gemfile.

Затем установите как обычно:

$ bundle install

Для того чтобы настроить приложение для использования Cucumber, мы затем генерируем несколько необходимых, поддерживающих его работу файлов и директорий:

$ rails generate cucumber:install

Это создает директорию features/ где будут жить файлы связанные с Cucumber.

Фичи и шаги

Огурцовые фичи это описания ожидаемого поведения с помощью plain-text языка называемого Gherkin. Gherkin тесты читаются во многом как хорошо написанные примеры RSpec, но, поскольку они написаны простым текстом, они более доступны для тех, кому комфортнее читать английский, а не код Руби.

Наши Огурцовые фичи будут реализовывать небольшое количество примеров входа в Листинге 8.5 и Листинге 8.6. Для того чтобы начать, мы создадим файл signing_in.feature в директории features/.

Огурцовые фичи начинаются с короткого описания функционала:

Feature: Signing in

Затем они добавляют индивидуальные сценарии. Например, для того, чтобы протестировать провальный вход, мы можем написать следующий сценарий:

  Scenario: Unsuccessful signin
    Given a user visits the signin page
    When they submit invalid signin information
    Then they should see an error message

Аналогично, для того, чтобы протестировать успешный вход, мы можем добавить следующее:

  Scenario: Successful signin
    Given a user visits the signin page
      And the user has an account
    When the user submits valid signin information
    Then they should see their profile page
      And they should see a signout link

Собрав все это вместе мы приходим к файлу Огурцовой фичи показанному в Листинге 8.32.

Feature: Signing in

  Scenario: Unsuccessful signin
    Given a user visits the signin page
    When they submit invalid signin information
    Then they should see an error message

  Scenario: Successful signin
    Given a user visits the signin page
      And the user has an account
    When the user submits valid signin information
    Then they should see their profile page
      And they should see a signout link
Листинг 8.32. Огурцовые фичи для тестирования входа. features/signing_in.feature

Для запуска фич мы используем исполняемую команду cucumber:

$ bundle exec cucumber features/

Сравните это с

$ bundle exec rspec spec/

В данном контексте стоит отметить, что, как и RSpec, Cucumber может быть вызван с помощью Rake-задачи:

$ bundle exec rake cucumber

(По непонятным для меня причинам, это иногда пишут как rake cucumber:ok.)

Все что мы пока сделали, это лишь написали немного простого текста, так что не думаю что для вас стало сюрпризом что Огурцовые сценарии пока не проходят. Для того чтобы получить зеленый набор тестов, нам необходима добавить файл step, который свяжет строки простого текста с Руби-кодом. Файл отправляется в директорию features/step_definitions; мы назовем его authentication_steps.rb.

Строки Feature и Scenario нужны в основном для документации, но каждой последующей строке нужен соответствующий Ruby. Например, строка

Given a user visits the signin page

в файле фич будет обработана соответствующим определением шага

Given /^a user visits the signin page$/ do
  visit signin_path
end

В фиче, Given это просто строка, но в файле с шагами Given является методом который принимает регулярное выражение и блок. Регулярное выражение соответствует тексту строки в сценарии, а содержимое блока является чистым Руби кодом, необходимым для реализации шага. В данном случае, "a user visits the signin page" реализуется посредством

visit signin_path

Если это выглядит знакомым, все правильно: это просто Capybara, которая включена по умолчанию в файлы с Огурцовыми шагами. Следующие две строки тоже должны выглядеть знакомо; шаги сценария

When they submit invalid signin information
Then they should see an error message

в файле фич обрабатываются следующими шагами:

When /^they submit invalid signin information$/ do
  click_button "Sign in"
end

Then /^they should see an error message$/ do
  expect(page).to have_selector('div.alert.alert-error')
end

Первый шаг также использует Capybara, при этом второй использует объект Capybara page вместе с RSpec. Очевидно, вся работа с тестами, которую мы проделали с RSpec и Capybara также полезна с Cucumber.

Остальные шаги обрабатываются аналогично. Конечный файл определения шагов преставлен в Листинге 8.33. Попробуйте добавлять шаги по одному, запуская

$ bundle exec cucumber features/

каждый раз до тех пор пока все тесты не пройдут.

Given /^a user visits the signin page$/ do
  visit signin_path
end

When /^they submit invalid signin information$/ do
  click_button "Sign in"
end

Then /^they should see an error message$/ do
  expect(page).to have_selector('div.alert.alert-error')
end

Given /^the user has an account$/ do
  @user = User.create(name: "Example User", email: "user@example.com",
                      password: "foobar", password_confirmation: "foobar")
end

When /^the user submits valid signin information$/ do
  fill_in "Email",    with: @user.email
  fill_in "Password", with: @user.password
  click_button "Sign in"
end

Then /^they should see their profile page$/ do
  expect(page).to have_title(@user.name)
end

Then /^they should see a signout link$/ do
  expect(page).to have_link('Sign out', href: signout_path)
end
Листинг 8.33. Завершенные шаги, необходимые для прохождения фич входа. features/step_definitions/authentication_steps.rb

С кодом в Листинге 8.33, Огурцовые тесты должны пройти:

$ bundle exec cucumber features/

Контрапункт: кастомные проверки RSpec

Написав несколько простых Огурцовых сценариев, стоит сравнить результат с эквивалентным примером на RSpec. Для начала, взглянем на Огурцовую фичу в Листинг 8.32 и соответствующее определение шагов в Листинге 8.33. Затем взглянем на RSpec request specs (интеграционные тесты):

describe "Authentication" do

  subject { page }

  describe "signin" do
    before { visit signin_path }

    describe "with invalid information" do
      before { click_button "Sign in" }

      it { should have_title('Sign in') }
      it { should have_selector('div.alert.alert-error') }
    end

    describe "with valid information" do
      let(:user) { FactoryGirl.create(:user) }
      before do
        fill_in "Email",    with: user.email.upcase
        fill_in "Password", with: user.password
        click_button "Sign in"
      end

      it { should have_title(user.name) }
      it { should have_link('Profile',     href: user_path(user)) }
      it { should have_link('Sign out',    href: signout_path) }
      it { should_not have_link('Sign in', href: signin_path) }
    end
  end
end

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

Один из замечательных эффектов Огурцового разделения является то, что он работает на более высоком уровне абстракции. Например, мы пишем

Then they should see an error message

для того, чтобы выразить ожидание увидеть сообщение об ошибке, и

Then /^they should see an error message$/ do
  expect(page).to have_selector('div.alert.alert-error')
end

для того чтобы реализовать тест. Что особенно удобно в этом, так это то, что только второй элемент (шаг) зависит от реализации, таким образом, если мы изменим, например, класс CSS используемый для сообщений об ошибках, файл фич останется неизменным.

В этом случае можно запечалиться переписывая

should have_selector('div.alert.alert-error')

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

should have_error_message('Invalid')

Мы можем определить такие проверки в том же вспомогательном файле, в который мы поместили тестовый хелпер full_title в Разделе 5.3.4. Сам код выглядит примерно следующим образом:

RSpec::Matchers.define :have_error_message do |message|
  match do |page|
    expect(page).to have_selector('div.alert.alert-error', text: message)
  end
end

Мы можем также определить вспомогательные функции для общепринятых операций:

def valid_signin(user)
  fill_in "Email",    with: user.email
  fill_in "Password", with: user.password
  click_button "Sign in"
end

Получившийся в результате вспомогательный код показан в Листинге 8.34 (который включает в себя результаты Листинга 5.41 и Листинга 5.42 из Раздела 5.6). Я нахожу этот подход более гибким, нежели Огурцовые определения шагов, в особенности когда проверки или помощники долженствования натурально принимают аргумент, такой как valid_signin(user). Определения шагов может повторить эту функциональность с помощью проверок регулярных выражений, но я считаю такой подход гораздо более громоздким (# в оригинале - (cu)cumbersome).

include ApplicationHelper

def valid_signin(user)
  fill_in "Email",    with: user.email
  fill_in "Password", with: user.password
  click_button "Sign in"
end

RSpec::Matchers.define :have_error_message do |message|
  match do |page|
    expect(page).to have_selector('div.alert.alert-error', text: message)
  end
end
Листинг 8.34. Добавление вспомогательного метода и кастомной RSpec проверки. spec/support/utilities.rb

С кодом из Листинга 8.34, мы можем написать

it { should have_error_message('Invalid') }

и

describe "with valid information" do
  let(:user) { FactoryGirl.create(:user) }
  before { valid_signin(user) }
  .
  .
  .

В наших тестах есть множество примеров тесной связи между тестами и реализацией сайта. Прохождение по текущему набору тестов и разрыв связей между тестами и деталями реализации с помощью создания кастомных проверок и методов остается в качестве упражнения (Раздел 8.5).

Вадим Обозин
Вадим Обозин

Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора?