Статьи

Начало работы с Padrino и BDD

Padrino — это веб-фреймворк, тесно связанный с его двоюродным братом, веб-фреймворком Sinatra. Он пытается предложить гибкость разработки Sinatra и широкий спектр помощников, инструментов и дополнений Rails.

Падрино, по-итальянски «крестный отец», построен на вершине Синатры, поэтому вы можете использовать все свои знания, полученные из предыдущих серий Синатры, опубликованных на этом сайте. Тем не менее, он несколько отличается, так как предоставляет некоторые соглашения, которые очень похожи на те, которые были найдены в ранее упомянутых статьях, как вы увидите. Кстати, я буду использовать Ruby 1.9.2 для этого урока.

Чтобы было проще, я буду управлять через RVM. Шаги по настройке RVM, если вы еще этого не сделали, описаны в этой статье Гленном Гудричем, известным из Rubysource. После установки RVM и Ruby 1.9.2 создайте набор гемов для этого приложения. Я называю мой «рубисокурс», как вы можете видеть ниже:

$ rvm use 1.9.2@rubysource --create

Установить Падрино

Чтобы установить Padrino:

 $ gem install padrino

После этого создайте новый проект, запустив:

 $ padrino g project rubytoday -t cucumber -b

г — короткая форма для генерации

-t это ключ, используемый для указания среды тестирования, в данном случае
Я указал огурец, это также включает в себя RSpec

-b говорит Падрино использовать Bundler для управления драгоценными камнями

Это создает проект с именем rubytoday .

Когда вы перейдете в каталог rubytoday , если вы использовали Rails, вы увидите структуру каталога, похожую на Rails:

 ,-- Gemfile
|-- Gemfile.lock
|-- app
|   |-- app.rb
|   |-- controllers
|   |-- helpers
|   `-- views
|       `-- layouts
|-- config
|   |-- apps.rb
|   `-- boot.rb
|-- config.ru
|-- cucumber.yml
|-- features
|   |-- add.feature
|   |-- step_definitions
|   |   `-- add_steps.rb
|   `-- support
|       |-- env.rb
|       `-- url.rb
|-- public
|   |-- favicon.ico
|   |-- images
|   |-- javascripts
|   `-- stylesheets
|-- spec
|   |-- spec.rake
|   `-- spec_helper.rb
`-- tmp

Некоторые заметки:

  • основное приложение находится в каталоге приложения
  • JavaScript и содержание таблиц стилей в публичном каталоге
  • Особенности огурца в каталоге функций
  • Папка RSpec в директории spec

Теперь, когда у нас есть исходное голое приложение, самое время проверить его в Git. Таким образом, вы можете снова вернуться в это состояние, если возникнет такая необходимость. Прежде чем мы это сделаем, откройте файл .gitignore и добавьте шаблон регулярного выражения для файлов резервных копий, которые создает ваш редактор. В моем случае это Emacs, а файлы резервных копий имеют окончание: * ~ .

Затем добавьте его в Git, вот так:

 $ git init
$ git add .
$ git commit -m "initial commit"
$ git status</p>

<h1>On branch master</h1>

<p>nothing to commit (working directory clean)

Последняя команда git status сообщает, что я в настоящее время на главной ветке master . Чтобы продолжить работу, я создам новую ветку и буду выполнять в ней свою работу. Как только я буду доволен этим, я объединю его с основной веткой.

 $ git checkout -b hello_world
Switched to a new branch 'hello_world'

С помощью команды git checkout -b helloworld я создал новую ветку helloworld и переключился на нее.

Вывод из ветки git подтверждает это:

 $ git branch
* hello_world
  master

Начальная буква * перед именем ветви указывает ветку, в которой я сейчас работаю.

Привет, мир в BDD

Если вам нужно указать название ветки, вы, наверное, догадались, что покажет следующая часть руководства. Создание страницы «Привет, мир».

Поскольку этот учебник пытается научить использованию разработки, основанной на Cucumber и Behavior, я начну с того, что напишу файл функций, объясняющий, как я хочу, чтобы мое приложение работало.

В каталоге функций я открываю файл hello.feature и добавляю:

 Feature: hello world
  I want the application to greet the world every time I use it

  Scenario: greet the world
    Given I visit the app
    Then it should greet the world

Функция и сценарий являются ключевыми словами. Особенность — это как бы главный заголовок, в котором кратко описывается то, что описывается, после чего следует описание. Сценарий — это пример того, как должен проявляться конкретный пример.
Существует также файл с именем add.feature , который не нужен, поэтому я его удалю:

 $ git rm add.feature

Затем я запускаю Cucumber, чтобы увидеть результат:

 $ bundle exec cucumber
Using the default profile...
Feature: hello world
  I want the application to greet the world every time I use it

Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/hello.feature:5
      Undefined step: "I visit the app" (Cucumber::Undefined)
      features/hello.feature:5:in 'Given I visit the app'
    Then it should greet the world # features/hello.feature:6
      Undefined step: "it should greet the world" (Cucumber::Undefined)
      features/hello.feature:6:in 'Then it should greet the world'

1 scenario (1 undefined)
2 steps (2 undefined)
0m0.002s

You can implement step definitions for undefined steps with these snippets:

Given /^I visit the app$/ do
  pending # express the regexp above with the code you wish you had
end

Then /^it should greet the world$/ do
  pending # express the regexp above with the code you wish you had
end

Огурец жалуется, что не распознает неопределенные шаги. Поэтому я копирую их и добавляю в новый файл:
features / stepdefinitions / hellosteps.rb :

 Given /^I visit the app$/ do
  pending # express the regexp above with the code you wish you had
end

Then /^it should greet the world$/ do
  pending # express the regexp above with the code you wish you had
end

Когда я снова запускаю огурец:

 $ bundle exec cucumber
Using the default profile...
Feature: hello world
  I want the application to greet the world every time I use it

Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/step_definitions/hello_steps.rb:1
      TODO (Cucumber::Pending)
      ./features/step_definitions/hello_steps.rb:2:in '/^I visit the app$/'
      features/hello.feature:5:in 'Given I visit the app'
    Then it should greet the world # features/step_definitions/hello_steps.rb:5

1 scenario (1 pending)
2 steps (1 skipped, 1 pending)
0m0.002s

Это терпит неудачу на первом шаге и пропускает второй. Поскольку на первом шаге ничего не определено, я добавляю следующее:

 Given /^I visit the app$/ do
  visit '/'
end

и снова запустить огурец:

 $ bundle exec cucumber
Using the default profile...
Feature: hello world
  I want the application to greet the world every time I use it

  Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/step_definitions/hello_steps.rb:1
    Then it should greet the world # features/step_definitions/hello_steps.rb:5
      TODO (Cucumber::Pending)
      ./features/step_definitions/hello_steps.rb:6:in '/^it should greet the world$/'
      features/hello.feature:6:in 'Then it should greet the world'

1 scenario (1 pending)
2 steps (1 pending, 1 passed)
0m0.367s

И первый шаг в сценарии пройден, но второй шаг еще не завершен. Поэтому я изменяю это:

 Then /^it should greet the world$/ do
  page.should have_content("Hello world")
end

тогда я снова запускаю огурец:

 $ bundle exec cucumber
Using the default profile...
Feature: hello world
  I want the application to greet the world every time I use it

  Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/step_definitions/hello_steps.rb:1
    Then it should greet the world # features/step_definitions/hello_steps.rb:5
      undefined local variable or method 'response_body' for #<Object:0x9741db0> (NameError)
      ./features/step_definitions/hello_steps.rb:6:in '/^it should greet the world$/'
      features/hello.feature:6:in `Then it should greet the world'

Failing Scenarios:
cucumber features/hello.feature:4 # Scenario: greet the world

1 scenario (1 failed)
2 steps (1 failed, 1 passed)
0m0.205s

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

Чтобы исправить, что я бегу:

 $ padrino gen controller events index

который в свою очередь создает events.rb в app / controllers и events_controller_spec.rb в spec / app / controllers / .

Если вы откроете spec / app / controllers / events_controller_spec.rb, вы увидите пример по умолчанию:

 require 'spec_helper'

describe "EventsController" do
  before do
    get "/"
  end

it "returns hello world" do
    last_response.body.should == "Hello World"
  end
end

что по совпадению именно то, что я пытаюсь описать.

Когда я запускаю спецификацию, вот так:

 $ padrino rake spec

это не удастся. Этого и следовало ожидать, так как у меня еще ничего не написано. Чтобы исправить это, я добавляю индексный маршрут к контроллеру событий в app / controllers / events.rb :

 Rubytoday.controllers :events do
  get :index, :map => "/" do
    "Hello world"
  end
end

Чтобы проверить маршрут, я могу сделать это через задачу Rake:

 $ padrino rake routes
=> Executing Rake routes ...

Application: Rubytoday
    URL                  REQUEST  PATH
    (:events, :index)      GET    /

Что подтверждает, что я установил корневой путь по умолчанию для метода индекса контроллера событий.

Затем, чтобы проверить это через RSpec:

 $ padrino rake spec
=> Executing Rake spec ...

EventsController
  returns hello world (FAILED - 1)

Failures:

1) EventsController returns hello world
     Failure/Error: last_response.body.should == "Hello World"
       expected: "Hello World"
            got: "Hello world" (using ==)
     # ./spec/app/controllers/events_controller_spec.rb:9:in `block (2 levels) in <top (required)>'

Finished in 0.28037 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/app/controllers/events<em>controller</em>spec.rb:8 # EventsController returns hello world
rake aborted!
ruby -S bundle exec rspec -fs --color ./spec/app/controllers/events<em>controller</em>spec.rb failed

Tasks: TOP => spec => spec:app
(See full trace by running task with --trace)

Что не получается, но если вы посмотрите на пример, единственное отличие состоит в том, что в этом примере ожидаемый результат будет «Hello World», а не «Hello world». Ошибка в написании. Как только я изменю это …

 $ padrino rake spec
=> Executing Rake spec ...

<p>EventsController
  returns hello world

<p>Finished in 0.22763 seconds
1 example, 0 failures

Пример проходит. Затем, чтобы убедиться, что огурец проходит:

 $ bundle exec cucumber
Using the default profile...

<p>Feature: hello world
  I want the application to greet the world every time I use it

Scenario: greet the world        # features/hello.feature:4
    Given I visit the app          # features/step<em>definitions/hello</em>steps.rb:1
    Then it should greet the world # features/step<em>definitions/hello</em>steps.rb:5

1 scenario (1 passed)
2 steps (2 passed)
0m0.402s

Я должен также проверить вывод для себя, запустив приложение:

 $ padrino start
=> Padrino/0.10.2 has taken the stage development at http://0.0.0.0:3000
[2011-10-03 00:51:01] INFO  WEBrick 1.3.1
[2011-10-03 00:51:01] INFO  ruby 1.9.2 (2010-08-18) [i686-linux]
[2011-10-03 00:51:01] INFO  WEBrick::HTTPServer#start: pid=16767 port=3000

и открыв URL в браузере: http://0.0.0.0:3000 .

Привет скриншот

Привет скриншот

Это завершает желаемый вывод и демонстрирует поток BDD:

Огурец -> RSpec -> Код

Сначала я написал функцию и описал ее поведение, затем написал пример в RSpec и, наконец, реализовал код, соответствующий как функции, так и примеру.

Теперь для последнего шага, чтобы передать его в Git:

 $ git status
# On branch hello_world
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   deleted:    features/add.feature
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   app/controllers/
#   app/helpers/
#   features/hello.feature
#   features/step_definitions/hello_steps.rb
#   spec/app/
no changes added to commit (use "git add" and/or "git commit -a")

В этом списке перечислены все новые материалы, которые необходимо зафиксировать в Git, а также удаленный контент: файл features / add.feature, который я удалил ранее.

 $ git add .
$ git status
# On branch hello_world
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
#  new file:   app/controllers/events.rb
#  new file:   app/helpers/events_helper.rb
#  new file:   features/hello.feature
#  new file:   features/step<em>definitions/hello</em>steps.rb
#  new file:   spec/app/controllers/events<em>controller</em>spec.rb
#
# Changed but not updated:
#  (use "git add/rm <file>..." to update what will be committed)
#  (use "git checkout -- <file>..." to discard changes in working directory)
#
#     deleted:    features/add.feature
#

Я создаю новый контент, а затем добавляю его:

 $ git commit -m "Implemented the 'Hello world' example"
[hello_world de9a0d4] Implemented the 'Hello world' example
 5 files changed, 58 insertions(+), 0 deletions(-)
 create mode 100644 app/controllers/events.rb
 create mode 100644 app/helpers/events_helper.rb
 create mode 100644 features/hello.feature
 create mode 100644 features/step_definitions/hello_steps.rb
 create mode 100644 spec/app/controllers/events_controller_spec.rb

На этом завершается первый урок в серии. В следующем уроке я покажу, как объединить контент в Git, как развернуть его в Heroku, и я продолжу создание остальной части приложения. Если это ваш первый взгляд на Падрино, я хотел бы услышать, что вы думаете в комментариях ниже. Весь код можно получить здесь: https://github.com/RubySource/simic_padrino .