Pull to refresh

Rails: Хватит отмазываться, начинаем BDD-ить!

Reading time 9 min
Views 32K

Кто здесь?


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

11:24:21 PM Michael: ну хз, надо пробовать
11:24:24 PM Michael: наверное так лучше
11:24:27 PM Michael: даже я думаю наверняка
11:24:36 PM Michael: но пока меня че-то останавливает
11:24:38 PM Michael: лень наверное :)

Знакомо? «Не хочется разбираться? Нет времени?» Тогда читаем дальше. В статье расскажу, как настроить свое любимое рельсовое окружении на разработку с подходом BDD и начать новую жизнь (опционально).

Исходные данные


Для чистоты эксперимента, начнем все с нуля. Надеюсь, с RVM все давно уже дружат (если нет, бегом знакомиться). Отличная штука для управления версиями Ruby и всеми сопутствующими джемами. Ставится за одну команду в окошке терминала (ну или что там не в макосе, консоль, наверное). Сразу забываем о рутовских привилегиях.
Итак, создаем чистое окружение:

$ rvm gemset create bdd
$ rvm gemset use bdd

Все, никаких джемов, чистый космос. Ставим последние рельсы:

$ gem install rails --no-ri --no-rdoc
...
23 gems installed

Последние два ключа нужны, чтобы лишнее время не тратить и не ставить всякую ненужную документацию. Добавлял эти ключи постоянно, поэтому в конце концов отправил это дело в .gemrc:

$ echo 'gem: --no-ri --no-rdoc' >> ~/.gemrc

Отлично, рельсы есть. На момент написания статьи используем версию 3.0.3.

Собираем все джемы


Создаем чистое приложение:

$ rails new bdd

Сразу же открываем Gemfile, тот, что в корне. Если не открывается, то, видимо, не тот текстовый редактор, удаляем TextMate — ставим MacVim (все, закрыли тему, дальше молчу). Добавляем в файл все необходимое для тестирования. В нашем случае будем использовать Cucumber и RSpec, а также много всяких вкусностей, о которых расскажу чуть позже.

source 'http://rubygems.org'

gem 'rails', '3.0.3'
gem 'mysql2'

group :development, :test do
  gem 'rspec-rails'
  gem 'cucumber-rails'
end

group :test do
  gem 'capybara'
  gem 'database_cleaner'
  gem 'factory_girl_rails'
  gem 'email_spec'
  gem 'timecop'
  gem 'launchy'
end

Файл подправили, теперь все ставим:

$ bundle install
...
Your bundle is complete!..

Сразу же неплохо было бы в Gemfile зафиксировать все установленные версии, т.е. в явном виде прописать версию также, как у джема rails (тот, что самый первый), но в принципе для тестирования не критично, к тому же Gemfile.lock есть.
Дружим рельсы с RSpec:

$ rails g rspec:install

Дружим рельсы с Cucumber и попутно последнего с только что подруженным RSpec. Не забываем и про Capybara (да, и такое бывает, о нем чуть ниже):

$ rails g cucumber:install --rspec --capybara

Наведем красивости:

$ echo '--colour --format documentation' > .rspec

После этого RSpec будет выводить информацию по прохождению тестов в красивом формате. То же самое можно сделать и для Cucumber. Открываем config/cucumber.yml и выполняем :%s/progress/pretty/g (заменяем слово progress на pretty).
Наконец, заигнорим папку test (на всякий случай, вдруг чего) и прочий хлам:

$ echo 'test' >> .gitignore
$ echo '*.swp' >> .gitignore
$ echo '.DS_Store' >> .gitignore

Все, что понаделали, занесем в git (тут тоже должно быть без вопросов, дядю Линуса все знают):

$ git init .
$ git add .
$ git commit -m 'Bare Rails 3.0.3 application with BDD environment'

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

Кто есть кто?


Cucumber

https://github.com/aslakhellesoy/cucumber
Итак, Cucumber, он же огурец, — это инструмент для «элегантного и радостного BDD», о чем можно прочесть на его главной странице. Да, радости будет хоть отбавляй, особенно в первое время с непривычки. Про RSpec, думаю, авторы могли бы сказать нечто подобное. В любом случае по этим друзьям документации и примеров очень много, чего только стоят одни wiki-странички на гитхабе, поэтому особо распространяться не буду, только коротенько. Сразу оговорюсь, что все в контексте рельсов, хотя, конечно, этим ни RSpec, ни Cucumber не ограничиваются.
Вернемся к Cucumber. Он позволяет описать поведение системы с позиции внешнего наблюдателя (читать как «заказчика, конечного пользователя»). При этом описание дается на естественно языке, никаких вам begin end. Каждый вариант использования системы в огурце называется «фичей» (feature). Все они лежат в одноименной папочке features с непредсказуемым расширением файла *.feature. В каждом файле описывается один или несколько «сценариев» (scenario), характеризующих фичу. Сценарии состоят из ряда шагов, объявленных в файлах из папки features/step_definitions/*_steps.rb. Шаги бывают трех типов: Given (что-то данное, некоторое предварительное условие), When (что-то, что происходит, какие-то действия пользователя) и Then (результат, реакция, отклик), но о таких вещах лучше на живых примерах говорить. Одна из последних моих фич (в НГ по телевизору слышал «я не волшебник, я только учус…», в общем здесь очень даже подходит, не судите строго):

@javascript @redis
Feature: A user sees who is online
  In order to know that the portal is alive
  As a regular user
  I want to see who is online

  Background:
    Given the section "Personal profiles" exists

  Scenario Outline: See a special label next to the name of a user who is online
    Given nobody has been on the portal for a lot time
    And a user exists with name: "<name>"
    When the user "<name>" <user action> the portal
    And I go to the profile of the user "<name>"
    Then I should <my action> the online label for the user "<name>"
    When I go to the section "Personal profiles"
    Then I should <my action> the online label for the user "<name>"

    Examples:
      | name     | user action    | my action |
      | Victoria | visits         | see       |
      | Michael  | does not visit | not see   |

Ключевое слово "Feature" предшествует имени фичи, по правилам должно кратко и ясно описывать действия пользователя. Три строчки под ним говорят, для чего, кто и что хочет. Раздел "Background" по желанию, задает общие шаги для всех сценариев фичи. Далее идут либо "Scenario", либо "Scenario Outline". Первое описывает сценарий без параметров, второе — с параметрами, идущими после ключевого слова "Examples" снизу. Через собачку (@) расставляются метки, позволяющие наложить дополнительные условия на всю фичу или конкретный сценарий, а так же выборочно выполнить тест. В принципе все читабельно. В идеале должно быть так, чтобы заказчик взял этот файл, открыл простым текстовым редактором и не напрягаясь прочел, все понял, осознал и подтвердил: «Оно!»
Что касается настройки огурца, то она проводится в сгенерированном файле features/support/env.rb. Там добавляются необходимые строчки кода для подключения всего, что необходимо для тестирования.

RSpec

https://github.com/rspec/rspec
RSpec тоже используется для описания внешнего поведения системы, однако больше подходит для копания в ее внутренностях. Т. е. если нужно проверить, что какая-то модель ведет себя адекватно или что какая-нибудь самописная библиотека оправдывает возложенные на нее надежды, то RSpec самое то. Но если же хочется проверить, что «пользователь, введя в первое окошко свой e-mail, а во второе — пароль, увидел сообщение об успешном входе в систему», то это к Cucumber. Каждый файл у RSpec в простонародье называется «спекой» (spec, от specification), находится в папке spec и заканчивается на *_spec.rb. Принято файлы в папке spec разбивать по вложенным папкам, чтобы они отражали структуру рельсового проекта (models, controllers, helpers, etc.). Вот слегка наигранный пример:

describe User do
  it { should ensure_length_of(:email).is_at_least(6).is_at_most(100) }
  it { should validate_format_of(:email).with('ma1f0rmed emai1 address') }

  subject { Factory :user }
  it { should validate_uniqueness_of :email }
  it { should validate_uniqueness_of :address }
end

Тут, чувствую, без Shoulda не обошлось, но речь не об этом, а о том, что так приблизительно выглядят спеки.
Конфигурация RSpec сконцентрирована в файле spec/spec_helper.rb. Аналогично env.rb у Cucumber сюда идут все вспомогательные require и прочее.

Capybara

https://github.com/jnicklas/capybara
Capybara — это удобная штука для автоматизации «браузерного» тестирования приложения. Судя по иллюстрациям из гугла это какая-то морская свинка, но речь не о ней. Джем дает множество вспомогательных методов в тестовое окружение, в результате чего с легкостью можно проверить такие вещи как, например, переход по ссылкам, заполнение форм (поля ввода, выпадающие списки, чекбоксы и так далее), наличие какого-либо элемента на странице. Более того, при подключении к нему другой штуки с не менее загадочным названием Selenium, позволяет проходить шаги тестового сценария прямо в браузере, в прямом смысле. У вас откроется, например, любимый Chrome и странички будут бегать одна за другой. В результате, можно отлаживать и JavaScript. Интересно было наблюдать в первый раз как оно само собой проверяло сортировку списка на страничке путем перетаскиивания его элементов. Selenium не единственный движок, с которым умеет работать Capybara, но является, на мой взгляд, самым удобным, да и не требует установки ничего лишнего, типа JRuby. Selenium достаточно легко попросить проверить что-либо не только в Chrome, но и в Firefox (вариант по умолчанию) и даже в осле. В случае Cucumber для этого достаточно открыть файл env.rb и добавить следующий код с указанием нужного браузера (список доступных смотрим в документации по Selenium):

Capybara.register_driver :selenium do |app|
  Capybara::Driver::Selenium.new app, :browser => :chrome
end


Database Cleaner

https://github.com/bmabey/database_cleaner
Джем для чистки базы данных перед или после тестов. Удобно, чтобы замести следы и начать с нуля. Поддерживает разные БД и адаптеры к ним. Использую его как для чистки MySQL + ActiveRecord, так и mongoDB + Mongoid.
Если этот джем подключен в Gemfile, то при генерации окружения Cucumber добавит его в свой env.rb автоматически, что-то вроде:

require 'database_cleaner'
DatabaseCleaner.strategy = :truncation
Before do
  DatabaseCleaner.clean
end

Для RSpec можно прописать в spec_helper.rb следующее (разберетесь куда):

config.before(:suite) do
  DatabaseCleaner.strategy = :truncation
end

config.before(:each) do
  DatabaseCleaner.clean
end


Factory Girl Rails

https://github.com/thoughtbot/factory_girl_rails
О, и до девочек дошли! Так, эту библиотеку используют для удобного создания экземпляров моделей (замена нативных для рельсов fixtures, если кому-то это о чем-то скажет). Другими словами, нужно же на примере чего-то тестировать. Нам нужны и пользователи (User) какие-нибудь, и статьи (Article), и проекты (Project) с задачами (Task). Не создавать же каждый раз объекты с заполнением всех обязательных полей. Вот тут-то на помощь и приходят подобные фабрики. Достаточно один раз определить шаблон и далее генерировать новые сущности на его основе. В связке с RSpec фабрики обычно хранятся в папке spec/factories или напрямую в spec/factories.rb. Например, вот такая:

Factory.define :user do |factory|
  factory.sequence(:email) { |i| "user#{ i }@example.org" }
  factory.password 'password'
  factory.password_confirmation { |user| user.password }
  factory.confirmed_at Time.now
end

Все, теперь в тестах достаточно Factory(:user) и у нас тепленький валидный юзер.

Email Spec

https://github.com/bmabey/email-spec
По названию наверное так сразу и не скажешь, но джем используется для тестирования отправки почты. Конечно, нативно вписывается как в Cucumber, так и в RSpec. В env.rb пишем:

require 'email_spec' # add this line if you use spork
require 'email_spec/cucumber'

В spec_helper.rb:

require "email_spec"

Далее:

$ rails g email_spec:steps

Добавит вспомогательные готовые шаги для тестов.

Timecop

https://github.com/jtrupiano/timecop
Вот эта штука мне особенно понравилась, позволяет путешествовать во времени. Сначала, что-то самописное использовал, а потом наткнулся на этого копа. Незаменима, когда нужно проверить что-то завязанное на времени, например, «поставив в печку пирожок, подождав четыре часа, мы получаем угольки». Просмотрите обязательно статью о нем (в списке литературы), там и шаги есть для Cucumber. Единственное, что хотелось бы заметить, тесты со временем лучше помечать особой меткой (да-да, те самые метки, как в Cucumber, так и в RSpec), например, @travel_through_time для того, чтобы после них всегда возвращаться в настоящее время, а то инструменты будут с ума сходить. У меня в env.rb есть следующие строки:

After('@travel_through_time') do
  Timecop.return
end

Строчки говорят огурцу исполнить код после каждого сценария с соответствующей меткой. В RSpec можно сделать нечто подобное.

Launchy

https://github.com/copiousfreetime/launchy
Иногда бывает так, что тест валится (бывает же), и сложно определить почему. Хочется взглянуть на страницу, на которой, например, Cucumber не нашел кнопку «Создать». Для этих целей ставится этот джем и в шагах огурца добавляется нечно вроде:

Then 'WTF?' do
  save_and_open_page
end

Дальше просто в сценарии вызываем соответствующий шаг перед тем, который не проходит, и видим в своем браузере по умолчанию сохраненную страничку с формой входа на сайт и надписью «У вас недостаточно прав для выполнения данной операции», что-то в таком духе.

В заглючение :%s/глюч/ключ/g


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

$ rspec spec
$ cucumber

И вот тут-то уже станет видно, что ты или твой напарник… молодец, одним словом.
Спасибо за внимание, напишу еще, если тематика затронула тонкие струны чьей-нибудь трепетной души. Все, пора спать.

Список литературы (как в школе)


Про Cucumber:

Про RSpec:

Про Райана Бейтса (Ryan Bates):

Про Timecop:

Про RVM:

Про Git:

// vim: set ft=habrahabr
Tags:
Hubs:
+66
Comments 38
Comments Comments 38

Articles