3.2.2 TDD: Красный, Зеленый, Refactor (реорганизация)
В Test-Driven Development, мы вначале пишем провальный тест: в нашем случае, кусок кода, который выражает идею, что “должна быть about
” страница. Затем мы заставим тест пройти, в нашем случае, добавив about
действие и соответствующиее представление. Причина, по которой мы обычно не делаем наоборот — вначале реализация а затем тест, в том , что таким образом мы убеждаемся, что мы на самом деле тестируем функцию, которую добавляем. Прежде чем я начал использовать TDD, я был поражен, узнав как часто мои "тесты" на самом деле тестировали не то, или даже вообще ничего не тестировали. Убедившись, что вначале тест не пройден, а затем проходит, мы можем быть более уверены, что тест делает правильные вещи.
Важно понимать, что TDD не всегда правильный инструмент для работы. В частности, когда вы не полностью уверены, в том, как решить данную проблему программирования, часто бывает полезным пропустить тесты и писать только код приложения, просто чтобы получить представление о том, как решение будет выглядеть. (На языке Экстремального Программирования (XP), этот поисковый этап называется spike.) Как только вы увидите общую форму решения, вы сможете использовать TDD для реализации более изысканной версии.
Один из способов работы в test-driven development это цикл, известный как “Красный, Зеленый, Рефакторинг (Реорганизация)”. Первый шаг, Красный, относится к написанию провального теста, который многие инструменты тестирования обозначают красным цветом. Следующий шаг, Зеленый, относится к проходящим тестам, обозначаемым зеленым (ждать его) цветом. После того как наши тесты (или набор тестов) прошли, мы свободны реорганизовывать наш код, изменяя его форму (устраняя дублирование, например) без изменения функции.
У нас еще нет никаких цветов, так что давайте начнем с Красного. В первый раз RSpec (и тестирование в целом) может быть немного пугающим, поэтому мы будем использовать тесты, сгенерированные rails generate controller Pages
в Листинге 3.4 для раскачки. Так как я не пристрастен к отдельным тестам для представлений или хелперов, которые я нахожу весьма хрупкими наш первый шаг это их удаление. Если Вы используете Git, Вы можете сделать это следующим образом:
$ git rm -r spec/views $ git rm -r spec/helpers
Или без Git:
$ rm -rf spec/views $ rm -rf spec/helpers
Мы сделаем тесты для представлений и хелперов непосредственно в контроллере теста, в Разделе 3.3.
Для начала работы с RSpec, взглянем на Pages контроллер spec11 который мы только что сгенерировали (Листинг 3.7).
spec/controllers/pages_controller_spec.rb
require 'spec_helper' describe PagesController do describe "GET 'home'" do it "should be successful" do get 'home' response.should be_success end end describe "GET 'contact'" do it "should be successful" do get 'contact' response.should be_success end end end
Этот код является чистым Ruby, но даже если вы изучали Ruby прежде, он, вероятно, выглядит не очень знакомым. Это происходит потому, RSpec использует общую гибкость Ruby для определения предметно-ориентированного языка (DSL) созданного только для тестирования. Важным моментом является то, что вам не нужно понимать синтаксис RSpec, чтобы иметь возможность использовать RSpec. На первый взгляд это может показаться магией, но RSpec разработан так, чтобы быть более или менее похожим на английский, и если вы следуете примерам generate
сценариев и другим примерам в этом учебнике вы разберетесь с ним довольно быстро.
Листинг 3.7 содержит два описывающих
блока, каждый с одним примером (т.е., блок начинается с it "…" do
). давайте сфокусируемся на первом, чтобы почувствовать, что он делает:
describe "GET 'home'" do it "should be successful" do get 'home' response.should be_success end end
Первая строка указывает на то, что мы описываем (describing) GET операцию для home
действия. Это всего лишь описание, и это может быть все, что угодно; для RSpec это не важно, но, вероятно, важно для вас и других читателей. В данном случае, "GET ’home’"
указывает, что тест соответствует HTTP запросу GET, как обсуждалось в Блоке 3.1. Затем spec говорит что когда Вы посещаете home страницу, она должна быть успешной. Как и в первой строке, то, что происходит внутри кавычек не имеет отношения к RSpec, и предназначено для наглядности. Третья строка, get ’home’
, это первая строка, которая действительно что-то делает. Внутри RSpec, эта строка на самом деле представляет запрос GET; другими словами, он действует, как браузер и попадает на страницу, в данном случае /pages/home. (Он автоматически знает что нужно "ударить" контроллер Pages, потому что это тест Pages контроллера; знает, что нужно попасть на home страницу, потому что мы сказали ему это явно.), наконец, четвертая строка говорит, что ответ ответ нашего приложения должен указывать на успех (т.е. он должен возвращать код статуса 200; см. Блок 3.2).
После того как клиент (такой как веб браузер) посылает запрос соответствующий одному из глаголов HTTP (Блок 3.1), сервер отвечает цифровым кодом указывающим на HTTP статус ответа. Например, код статуса 200 значит “успех”, а код статуса 301 значит “постоянную переадресация”. Если Вы установите curl, клиент командной строки, который может выдавать HTTP-запросы, вы сможете видеть их непосредственно, например, www.google.com (где --head флаг не позволяет curl
возвращать всю страницу):
$ curl --head www.google.com HTTP/1.1 200 OK . . .
Здесь Google указывает, что запрос был успешным, возвращая статус 200 OK. В отличие от, постоянно перенаправляющего google.com (на www.google.com, естественно), который указывает код статуса 301 (a “301 redirect”):
$ curl --head google.com HTTP/1.1 301 Moved Permanently Location: http://www.google.com/ . . .
(Примечание: Приведенные выше результаты могут варьироваться в зависимости от страны.)
Когда мы пишем response.should be_success
в тесте RSpec, RSpec проверяет, что ответом нашего приложения на запрос является код статуса 200.
Теперь пришло время запустить тест. Есть несколько различных, в основном эквивалентных, способов сделать это.12 Одним из способов для выполнения всех тестов является использование rspec
сценария в командной строке следующим образом:13
$ rspec spec/ .... Finished in 0.07252 seconds 2 examples, 0 failures
На некоторых системах (особенно командная строка Windows), на этом этапе вы можете получить ошибку указывающюю на проблемы с нахождением rspec
программы:
C:\Sites\sample_app>rspec spec/ 'rspec' is not recognized as an internal or external command, operable program or batch file.
В этом случае Вам нужно выполнить rspec
через Bundler используя bundle exec
команду:
C:\Sites\sample_app>bundle exec rspec spec/ .... Finished in 0.07252 seconds 2 examples, 0 failures
Если какой-либо тест не удается, убедитесь, что вы сделали миграцию базы данных с rake db:migrate
как описано в Разделе 1.2.5. Если RSpec вообще не работает, попробуйте удалить и переустановить его:
$ gem uninstall rspec rspec-rails $ bundle install
Если он все же не заработал, и если Вы используете RVM, попробуйте удалить Rails Tutorial gemset и переустановить гемы:
$ rvm gemset delete rails3tutorial $ rvm --create use 1.9.2@rails3tutorial $ rvm --default 1.9.2@rails3tutorial $ gem install rails -v 3.0.7 $ bundle install
(Даже если он все еще не работает, мои идеи кончились.)
Когда мы запускаем rspec spec/
, rspec
является программой, предоставляемой RSpec, в то время как spec/
это директория , в которой вы хотите запустить spec . Вы также можете запустить spec только в конкретном подкаталоге. Например, эта команда выполняет только spec контроллера:
$ rspec spec/controllers/ .... Finished in 0.07502 seconds 2 examples, 0 failures
Вы также можете запустить один файл:
$ rspec spec/controllers/pages_controller_spec.rb .... Finished in 0.07253 seconds 2 examples, 0 failures
Результат всех трех команд одинаковый так как pages_controller_spec.rb в настоящее время наш единственный тестовый файл. В остальной части этой книги, я обычно не показываю результат работы тестов, но вы должны запускать rspec spec/
(или один из его вариантов) регулярно по мере продвижения вперед, или, еще лучше, используйте Autotest для автоматического запуска набора тестов. Говоря о котором…
Если у Вас установлен Autotest, вы можете запустить его на своих Rspec тестах следующим образом:
$ autotest
Если не сработало, попробуйте
$ bundle exec autotest
Если вы используете Mac с включеными уведомлениями Growl, вы, должно быть, в состоянии повторить мои установки, показанные на Рис. 3.6. С Autotestом работающем в фоновом режиме и Growl уведомлениями, говорящими вам о состоянии ваших тестов, TDD может вызывать сильное привыкание.

Рисунок 3.6: Autotest (через autotest
) в действии, с уведомлением Growl.