Опубликован: 27.01.2016 | Уровень: для всех | Доступ: платный
Лекция 3:

В основном статические страницы

Наши первые тесты

Rails Tutorial имеет интуитивно понятный подход к тестированию, который уделяет особое внимание поведению приложения, а не деталям его реализации, что является вариантом разработки через тестирование (TDD) известным как разработка через поведение (BDD). Нашими основными инструментами будут интеграционные тесты (начиная с этого раздела) и юнит тесты (начиная с Главы 6). Интеграционные тесты, известные в контексте RSpec как request specs, позволят нам симулировать действия пользователя взаимодействующего с нашим приложением через веб браузер. Совместно с замечательным синтаксисом предоставляемым Capybara, интеграционные тесты обеспечивают мощную методику тестирования функционала нашего приложения без необходимости вручную проверять каждую страницу в браузере. (Другой популярный инструмент для BDD, называемый Cucumber, будет представлен в Разделе 8.3.)

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

Важно понимать, что TDD не всегда правильный инструмент для работы. В частности, когда вы не полностью уверены, в том, как решить данную проблему программирования, часто бывает полезным пропустить тесты и писать только код приложения, просто чтобы получить представление о том, как решение будет выглядеть. (На языке Экстремального Программирования (XP) этот поисковый этап называется spike.) Как только вы увидите общую форму решения, вы сможете использовать TDD для реализации более изысканной версии.

В этом разделе мы будем запускать тесты используя rspec команду, предоставляемую гемом RSpec. Такая практика эффективна, но не идеальна, и если вы являетесь более продвинутым пользователем, то я советую настроить вашу систему как это описано в Разделе 3.6.

Разработка через тестирование

В Test-Driven Development, мы вначале пишем провальный тест, который в большинстве инструментов тестирования представлен красным цветом. Затем мы реализуем код необходимый для прохождения теста, проходящий тест обычно представлен зеленым цветом. Наконец, если необходимо, мы реорганизуем (рефакторим) код, меняя его форму (например, устраняя повторения) не меняя его функций. Этот цикл известен как "Красный, Зеленый, Реорганизация".

Мы начнем с того что добавим немного контента в Home страницу используя разработку через тестирование, включая заголовок верхнего уровня (<h1>) содержащий Sample App. Первый шаг это генерация интеграционного теста (request spec) для наших статических страниц:

$ rails generate integration_test static_pages
      invoke  rspec
      create    spec/requests/static_pages_spec.rb

Это создает static_pages_spec.rb в директории spec/requests. Как и бОльшая часть генерируемого кода, полученный результат удручает, так что мы откроем static_pages_spec.rb в текстовом редакторе и заменим его содержимым Листинга 3.9.

require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end
  end
end
Листинг 3.9. Код для тестирования контента Home страницы. spec/requests/static_pages_spec.rb

Код в Листинге 3.9 является чистым Ruby, но даже если вы изучали Ruby прежде, он, вероятно, выглядит не очень знакомым. Это происходит потому, что RSpec использует общую гибкость Ruby для определения предметно-ориентированного языка (DSL) созданного только для тестирования. Важным моментом является то, что вам не нужно понимать синтаксис RSpec, чтобы иметь возможность использовать RSpec. На первый взгляд это может показаться магией, но RSpec и Capybara разработаны так, чтобы быть более или менее похожими на английский и если вы будете следовать примерам generate скрипта и другим примерам в этом учебнике вы разберетесь с ним довольно быстро.

describe "Home page" do

  it "should have the content 'Sample App'" do
    visit '/static_pages/home'
    expect(page).to have_content('Sample App')
  end
end
Листинг 3.10. содержит describe блок с одним примером, т.е., блоком

Первая строка указывает на то, что мы описываем (describing) Home страницу. Это описание является простой строкой и может содержать все что вам угодно; это не важно для RSpec, но имеет значение для вас и прочих читателей вашего кода. Затем spec (# далее по тексту - спек, спеки) говорит что при посещении Home страницы по адресу /static_pages/home, контент должен содержать слова "Sample App". Как и в случае с первой строкой, происходящее внутри кавычек не имеет значения для RSpec и предназначено для повышения читабельности кода. Затем строка

visit '/static_pages/home'

использует Capybara функцию visit для симуляции посещения URL /static_pages/home в браузере, в то время как строка

expect(page).to have_content('Sample App')

использует переменную page (также предоставленную Capybara) для описания ожидания того что данная страница должна содержать правильный контент.

Для того чтобы правильно запустить тесты мы должны добавить строку в файл spec_helper.rb, как это показано в Листинге 3.10. (В полноценном третьем издании Rails Tutorial я планирую избавиться от этого требования с помощью более новой техники которая называется feature specs.)

# This file is copied to spec/ when you run 'rails generate rspec:install'
.
.
.
RSpec.configure do |config|
  .
  .
  .
  config.include Capybara::DSL
end
Листинг 3.10. Добавление Capybara DSL во вспомогательный RSpec файл. spec/spec_helper.rb

Существует несколько способов запуска тестов, в том числе удобные, но более продвинутые инструменты описанные в Разделе 3.6. В данный момент мы будем использовать консольную команду rspec (выполняемую с bundle exec для вящей уверенности в том что RSpec запускает ее в окружении которое определено нашим Gemfile):6Постоянно писать bundle exec это довольно обременительно; в разделе 3.6 описано несколько способов избежать этого.

$ bundle exec rspec spec/requests/static_pages_spec.rb

Что выдаст провальный тест. Внешний вид результата зависит от вашей системы; на моей системе красный провальный тест выглядит как на рис. 3.3.7Я на самом деле использую черный фон и для терминала и для редактора, но светлый фон лучше выглядит на скриншотах.

Красный (провальный) тест.

Рис. 3.3. Красный (провальный) тест.

Для прохождения первого теста мы заменим текст дефолтной Home страницы на HTML из Листинга 3.11.

<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>
  sample application.
</p>
Листинг 3.11. Код необходимый для прохождения теста Home страницы. app/views/static_pages/home.html.erb

Что дает нам заголовок верхнего уровня (<h1>) с контентом Sample App, необходимый для прохождения теста. Мы также включили якорный тег a, который создает ссылку на конкретный URL (называемую "href", или "гипертекстовая ссылка", в контексте якорного тега):

<a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>

Теперь перезапустим тест чтобы увидеть эффект:

$ bundle exec rspec spec/requests/static_pages_spec.rb

На моей системе проходящий тест выглядит как на рис. 3.4.

Зеленый (проходящий) тест.

Рис. 3.4. Зеленый (проходящий) тест.

Опираясь на пример Home страницы вы возможно предвидите аналогичный тест и код приложения для страницы Help. Мы начнем с тестирования на наличие значимого контента, в данном случае, строки ’Help’ (Листинг 3.12).

require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end
  end

  describe "Help page" do

    it "should have the content 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_content('Help')
    end
  end
end
Листинг 3.12. Добавление кода для тестирования содержимого страницы Help. spec/requests/static_pages_spec.rb

Затем запускаем тесты

$ bundle exec rspec spec/requests/static_pages_spec.rb

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

Как видно из Листинга 3.13, код приложения аналогичен коду из Листинга 3.11.

<h1>Help</h1>
<p>
  Get help on the Ruby on Rails Tutorial at the
  <a href="http://railstutorial.org/help">Rails Tutorial help page</a>.
  To get help on this sample app, see the
  <a href="http://railstutorial.org/book">Rails Tutorial book</a>.
</p>
Листинг 3.13. Код необходимый для прохождения теста страницы Help. app/views/static_pages/help.html.erb

Теперь тест должен пройти:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Добавление страницы

Посмотрев на разработку через тестирование в действии на простом примере, мы используем ту же технику для решения немного более сложной задачи по добавлению новой страницы, а именно, страницы About которую мы умышленно пропустили в Разделе 3.1. Посредством написания теста и запуска RSpec на каждом шаге, мы увидим как TDD может играть роль проводника при разработке кода нашего приложения.

Красный

Мы доберемся до Красной части цикла Красный-Зеленый написав провальный тест для страницы About. Следуя модели из Листинга 3.12, вы, вероятно, можете предугадать правильный тест (Листинг 3.14).

require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end
  end

  describe "Help page" do

    it "should have the content 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_content('Help')
    end
  end

  describe "About page" do

    it "should have the content 'About Us'" do
      visit '/static_pages/about'
      expect(page).to have_content('About Us')
    end
  end
end
Листинг 3.14. Добавление кода для тестирования содержимого About страницы. spec/requests/static_pages_spec.rb

Зеленый

Вспомним из Раздела 3.1 что в Rails мы можем генерировать статические страницы создавая действие и соответствующее ему представление с названием страницы. В нашем случае, странице About в первую очередь понадобится действие about в контроллере StaticPages. Имея написанный провальный тест, мы можем быть уверены в том, что получив его прохождение, мы действительно создали рабочую страницу About.

Если вы запускаете RSpec используя

$ bundle exec rspec spec/requests/static_pages_spec.rb

вывод содержит следующую жалобу:

No route matches [GET] "/static_pages/about"

Что намекает на необхоимость добавления правила для URL /static_pages/about в файле маршрутов, мы можем сделать это следуя паттерну из Листинга 3.5, как это показано в Листинге 3.15.

SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  get "static_pages/about"
  .
  .
  .
end
Листинг 3.15. Добавление about маршрута. config/routes.rb

Теперь запуск

$ bundle exec rspec spec/requests/static_pages_spec.rb

выдает жалобу

The action 'about' could not be found for StaticPagesController

Для решения этой проблемы мы последуем примеру home и help из Листинга 3.6, добавив about действие в StaticPages контроллер (Листинг 3.16).

class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about
  end
end
Листинг 3.16. Контроллер StaticPages с добавленным about действием. app/controllers/static_pages_controller.rb

Теперь запуск

$ bundle exec rspec spec/requests/static_pages_spec.rb

говорит что нам не хватает "шаблона", т.е., представления:

Missing template static_pages/about

для решения этой проблемы мы добавим about представление. Для этого потребуется создать новый файл about.html.erb в директории app/views/static_pages с контентом показанным в Листинге 3.17.

app/views/static_pages/about.html.erb
<h1>About Us</h1>
<p>
  The <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>
  is a project to make a book and screencasts to teach web development
  with <a href="http://rubyonrails.org/">Ruby on Rails</a>. This
  is the sample application for the tutorial.
</p>
Листинг 3.17. Код для About страницы

Теперь запуск RSpec должен вернуть нас к Зеленому:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Конечно, это никогда не плохая идея, чтобы посмотреть на страницу в браузере, чтобы убедиться, что наши тесты не являются абсолютно сумасшедшими ( рис. 3.5).

Новая страница About (/static_pages/about).

Рис. 3.5. Новая страница About (/static_pages/about).

Реорганизация

Теперь, когда мы Позеленели, мы свободны, чтобы реорганизовать наш код, изменяя его форму не меняя функции. Часто код начинает "вонять", что означает, что он становится уродливым, раздутым, или наполненным повторениями. Компьютеру, конечно, все равно, но не людям, поэтому важно хранить базу кода в чистоте, часто делая рефакторинг (реорганизацию). Наличие хорошего набора тестов является бесценным инструментом в этой связи, так как это резко снижает вероятность внесения ошибки в процессе рефакторинга.

Наш пример приложения слишком мал, чтобы реорганизовать его прямо сейчас, но вонь от кода проникает в каждую трещину, так что нам не придется долго ждать: мы займемся рефакторингом (реорганизацией) в Разделе 3.3.4.

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

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

Акбар Ахвердов
Акбар Ахвердов
Россия, г. Москва
Артём Зайцев
Артём Зайцев
Украина, ДНР