#285 Spork
- Download:
- source codeProject Files in Zip (94.6 KB)
- mp4Full Size H.264 Video (22.2 MB)
- m4vSmaller H.264 Video (11.5 MB)
- webmFull Size VP8 Video (13.7 MB)
- ogvFull Size Theora Video (27.8 MB)
Наряду с тем, что Spork это столовый прибор, Spork это Ruby gem, который поможет наборам тестов приложения загружаться быстрее. В этом эпизоде мы покажем вам как можно ускорить работу тестов приложения.
Обычно, когда мы запускаем набор тестов для приложения Rails возникает пауза в несколько секунд, пока тесты начнут выполняться. Можно увидеть сколько она длится для тестов нашего приложения с помощью команды time
.
$ time rspec . ......... Finished in 1.98 seconds 9 examples, 0 failures real 0m11.090s user 0m9.735s sys 0m1.306s
Хотя RSpec утверждает, что тесты заняли всего пару секунд, но время, которое проходит между запуском rspec
и возвратом в командную строку значительно дольше, около 11 секунд. Дополнительное время требуется для загрузки приложения Rails, и, как мы уже успели убедиться, это может занимать больше времени, чем работа тестов.
Установка Spork
Spork устанавливается так же, как и любой другой gem, но раз он используется только для тестирования, мы добавим его в группу test
в Gemfile
. Так как мы устанавливаем его для приложения Rails 3, нам потребуется установить prerelease версию гема, которая на данный момент 0.9.0.rc
.
gem "rspec-rails", :group => [:test, :development] group :test do gem "factory_girl_rails" gem "capybara" gem "guard-rspec" gem "spork", "> 0.9.0.rc" end
Как всегда, нам надо запустить bundle
, как только мы изменим Gemfile
, чтобы все гемы в нем установились.
Затем нам нужно запустить команду Spork bootstrap
чтобы подготовить файл helper.
$ spork --bootstrap Using RSpec Bootstrapping /Users/eifion/auth/spec/spec_helper.rb. Done. Edit /Users/eifion/auth/spec/spec_helper.rb now with your favorite text editor and follow the instructions.
Можно увидеть из вывода выше, Spork обнаружил, что мы используем RSpec и он изменил файл spec_helper
. Давайте взглянем, что поменялось.
require 'spork' Spork.prefork do # Loading more in this block will cause your tests to run faster. However, # if you change any configuration or code from libraries loaded here, you'll # need to restart spork for it take effect. end Spork.each_run do # This code will be run each time you run your specs. end
Spork добавил два метода к spec_helper
, каждый из которых принимает блок. Первый – prefork
запускается, когда запускается сервер Spork. Другой – each_run
, запускается каждый раз, когда мы запускаем наш набор тестов. Разумно будет переместить как можно больше кода spec_helper
в prefork
, чтобы он выполнился только однажды. Мы переместим весь код файла в этот метод и проверим, будут ли теперь работать тесты.
require 'rubygems' require 'spork' Spork.prefork do # This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'capybara/rspec' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} RSpec.configure do |config| config.mock_with :rspec config.use_transactional_fixtures = true config.include(MailerMacros) config.before(:each) { reset_email } end end Spork.each_run do # This code will be run each time you run your specs. end
Используем Spork, введя команду spork
, чтобы запустить его сервер.
$ spork Using RSpec Preloading Rails environment Loading Spork.prefork block... Spork is ready and listening on 8989!
Как только сервер запустился, он выполнит код в методе prefork
, потому как этот код должен быть выполнен только один раз. Теперь мы можем открыть другое окно терминала и запустить наш набор тестов. Нужно запустить тесты через сервер Spork, что мы и сделаем, передав параметр --drb
.
Когда мы запустим тесты через Spork, набор тестов отработает быстрее, и, если мы снова запустим их через команду time
, то увидим, что теперь это занимает значительно меньше времени для завершения. Этот выигрыш в скорости будет расти с ростом нашего приложения.
<p>Spork действительно полезен, потому что запускает приложение в среде test только один раз и позволяет нам запустить его тесты столько раз, сколько нам нужно без нужды перезапускать всё приложение каждый раз.</p> <h3>Использование Guard со Spork</h3> <p>Guard это гем, который запускает заново набор тестов приложения каждый раз, когда изменяется его код. Мы рассмотрели его детально в эпизоде 264 [<a href="http://railscasts.com/episodes/264-guard">смотреть</a>, <a href="http://asciicasts.com/episodes/264-guard">читать</a>]. Guard может быть использован со Spork с помощью гема <code><a href="https://github.com/guard/guard-spork">guard-spork</a></code>. Он устанавливается так же, как мы установили Spork, добавлением в группу <code>test</code> в <code>Gemfile</code> и запуском <code>bundle</code>.</p> ``` /Gemfile gem "rspec-rails", :group => [:test, :development] group :test do gem "factory_girl_rails" gem "capybara" gem "guard-rspec" gem "spork", "> 0.9.0.rc" gem "guard-spork" end
Когда гем установлен, нужно запустить guard init spork
чтобы добавить Spork в наш Guardfile
. Если затем мы взглянем на Guardfile
, то увидим раздел Spork ниже раздела RSpec.
guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do watch('config/application.rb') watch('config/environment.rb') watch(%r{^config/environments/.+\.rb$}) watch(%r{^config/initializers/.+\.rb$}) watch('spec/spec_helper.rb') end
Нам нужно переместить этот раздел выше раздела RSpec, так как он должен быть запущен прежде любых других разделов связанных с тестированием. Нам нужно также изменить guard
для RSpec и добавить параметр :cli
со значением --drb
. Это обеспечивает, чтобы тесты были запущены через Spork, и это применяется к любым другим guards, которые у нас могли бы быть, например Cucumber.
<p>Теперь нам больше не нужно запускать сервер Spork отдельно. Когда мы запускаем Guard, он автоматически запустит Spork в фоновом режиме и запустит набор тестов, когда мы изменим любой Ruby файл нашего приложения.</p> ``` terminal $ guard Please install rb-fsevent gem for Mac OSX FSEvents support Using polling (Please help us to support your system better than that.) Please install growl_notify or growl gem for Mac OS X notification support and add it to your Gemfile Guard is now watching at '/Users/eifion/auth' Starting Spork for RSpec Using RSpec Preloading Rails environment Loading Spork.prefork block... Spork is ready and listening on 8989! Spork server for RSpec successfully started Guard::RSpec is running, with RSpec 2! Running all specs Running tests with args ["--color", "--format", "progress", "--format", "Guard::RSpec::Formatter::NotificationRSpec", "--out", "/dev/null", "--require", "/Users/eifion/.rvm/gems/ruby-1.9.2-p180@rails31/gems/guard-rspec-0.4.5/lib/guard/rspec/formatters/notification_rspec.rb", "spec"]... ......... Finished in 4.29 seconds 9 examples, 0 failures Done.
Это значительно проще, чем управлять Spork в отдельности. Когда мы меняем один из файлов из нашего приложения или тестов и сохраняем его, то соответствующие тесты будут заново запущены автоматически через пару секунд.
Настройка Spork
Теперь мы взглянем на настройку Spork. Иногда, когда мы изменяем файл приложения, Spork не реагирует на изменение – это значит, что его нужно перезапустить. Это случается, потому что файл загружается в блоке prefork
, но не перезагружается в each_run
. Это часто случается, если используется Factory Girl. Его фабрики загружаются в prefork
, поэтому любые изменения, которые мы производим с ними не подгружаются, когда перезапускается набор тестов. Нам нужно, чтобы любые изменения, которые мы делаем автоматически подгружались, поэтому нам нужно перезагружать фабрики в each_run
. В последних версиях Factory Girl есть метод reload, который сделает это, поэтому всё просто.
Spork.each_run do FactoryGirl.reload end
Любые изменения, которые касаются фабрик теперь будут подобраны на следующем запуске тестов.
Подобные проблемы могут возникнуть с файлами в директории /spec/support
. Опять же, они подгружаются в блоке prefork
, поэтому любые изменения, которые мы осуществляем, будут загружены, когда будут заново запущены тесты. Мы можем переместить код загрузки файлов в each_run
, однако, чем больше кода мы помещаем там, тем дольше будет задержка прежде чем тесты начнут выполняться. Будет лучше, если мы сможем сохранить код в блоке preload
и перезагружать Spork автоматически как только один из файлов в /spec/support
изменится.
Это то, с чем нам может помочь guard-spork
. Guardfile
содержит список шаблонов имен файлов в блоке spork
, который определяет файлы, за которыми ведется наблюдение. Если любой подходящий файл изменится, Spork будет перезапущен. Мы можем спокойно добавить сюда директорию spec/support
.
guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do watch('config/application.rb') watch('config/environment.rb') watch(%r{^config/environments/.+\.rb$}) watch(%r{^config/initializers/.+\.rb$}) watch('spec/spec_helper.rb') watch(%r{^spec/support/.+\.rb$}) end
Любые изменение в файлах из директории spec/support
теперь будут подобраны гемом Guard и перезапустят Spork.
Раз мы рассматриваем Guardfile
, то стоит упомянуть о небольшой хитрости в том, что делать с медленно работающим набором тестов. Если для работы тестов потребовалось больше минуты, то мы не хотим, чтобы они запускались так часто. Есть два параметра, которые мы можем передать в rspec guard
, которые помогут: all_on_start
и all_over_pass
. Установим оба значения в false
.
guard 'rspec', :version => 2, :cli => '--drb', :all_on_start => false, :all_after_pass => false do # команды watch опущены. end
С этими установленными параметрами Guard не будет запускать заново весь набор тестов, после того как пройдут тесты, провалившиеся в прошлый раз. Это дает нам больше контроля над тем, когда запускаются все тесты. Мы можем нажать клавишу ввода в терминале Guard (если мы используем последнюю версию Guard), чтобы запустить все тесты.
Другая подсказка, которая не относится к Spork, но может быть полезной, когда имеешь дело с большими наборами тестов - добавление трех строк к блоку конфигурации RSpec (нижние три строки в блоке, представленном ниже).
RSpec.configure do |config| config.mock_with :rspec config.use_transactional_fixtures = true config.include(MailerMacros) config.before(:each) { reset_email } config.treat_symbols_as_metadata_keys_with_true_values = true config.filter_run :focus => true config.run_all_when_everything_filtered = true end
Теперь, когда бы мы ни добавили метку :focus
к какому-то тесту, только этот тест будет запущен вместо них всех. Например, добавим :focus
к тестам, которые проверяют модель User
.
require 'spec_helper' describe User do describe "#send_password_reset" do let(:user) { Factory(:user) } it "generates a unique password_reset_token each time", :focus do user.send_password_reset last_token = user.password_reset_token user.send_password_reset user.password_reset_token.should_not eq(last_token) end # Остальные тесты опущены. end end
Когда мы сохраним вышеприведенный файл, Guard подберет изменения, но из-за метки :focus
только единственный тест будет запущен.
Running: spec/models/user_spec.rb Running tests with args ... Run filtered including {:focus=>true} . Finished in 1.93 seconds 1 example, 0 failures Done.
Бывают случаи, когда кусок кода вызывается в блоке Spork prefork
в то время, как мы не хотим этого, но нет простого способа переместить его в each_run
. Spork предоставляет метод trap_method
чтобы обслуживать такие сценарии. Этот метод останавливает перехваченный метод от немедленного запуска, а вместо этого запускает его после отделения процесса (fork). Это будет полезно в определенных случаях, таких как использование Mongoid или Devise. Оба из них загружают в блоке prefork, код, который не должен быть запущен. Это описывается более подробно на вики-страницах Spork.
На этом закончим этот эпизод про Spork. Это отличный инструмент для ускорения процесса TDD, и, когда он совмещен с подсказками, которые мы рассмотрели выше, он отлично масштабируется даже для больших проектов на Rails.