#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 es una gema de Ruby que acelera la carga de los tests de nuestras aplicaciones, en este episodio investigaremos cómo podemos usar esta gema para hacer que los tests se ejecuten más rápido.
Por lo general cuando ejecutamos la suite de tests de una aplicación Rails pasan unos segundos antes de que los tests empiecen a ejecutarse. Podemos medir la duración de esta pausa con la orden time
.
$ time rspec . ......... Finished in 1.98 seconds 9 examples, 0 failures real 0m11.090s user 0m9.735s sys 0m1.306s
Aunque RSpec dice que los tests le han llevado sólo unos segundos el tiempo transcurrido desde que ejecutamos rspec
y su finalización es mucho mayor, unos 11 segundos. El tiempo extra es el tiempo que se tarda en cargar el código de la aplicación Rails y, como puede verse, este tiempo puede ser incluso mayor que el tiempo de ejecución de los tests. Spork viene a resolver este problema.
Instalación
Spork se instala como cualquier otra gema, aunque como sólo se utiliza en el entorno de tests lo añadiremos al grupo test
del Gemfile
. Como lo estamos instalando para una aplicación Rails 3 tenemos que instalar la versión de pre-lanzamiento que es la 0.9.0.rc
.
gem "rspec-rails", :group => [:test, :development] group :test do gem "factory_girl_rails"43675-19804-85759-03545-08577 gem "capybara" gem "guard-rspec" gem "spork", "> 0.9.0.rc" end
Como siempre, tras cambiar el Gemfile
tenemos que ejecutar bundle
para que se instalen todas las gemas y sus dependencias.
Lo siguiente que tenemos que hacer es lanzar la orden bootstrap
de Spork para preparar el fichero spec_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.
Como puede verse, Spork ha detectado que estamos usando RSpec y ha modificado el fichero spec_helper
. Echémosle un vistazo a los cambios que ha hecho.
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 ha añadido dos métodos en el fichero spec_helper
, cada uno de los cuales recibe un bloque. El primero es prefork
, que se ejecuta cuando arranca el servidor de Spork. El otro, llamado each_run
se ejecuta cada vez que ejecutamos la suite de tests. La idea es quitar todo el código que se pueda de spec_helper
para pasarlo a prefork
de forma que sólo se ejecute una vez. Moveremos todo el código de este archivo y veremos si los tests siguen pasando.
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
Podemos usar la orden spork
para arrancar su servidor.
$ spork Using RSpec Preloading Rails environment Loading Spork.prefork block... Spork is ready and listening on 8989!
Cuando el servidor arranca ejecuta el código del método prefork
de forma que este código sólo se ejecuta una vez. Ahora podemos lanzar otra ventana de terminal y ejecutar nuestra suite de tests. Tenemos que ejecutar los tests mediante el servidor Spork, lo que se hace utilizando la opción --drb
.
Cuando Spork ejecuta los tests, la suite tarda menos en terminar, lo que podemos comprobar ejecutándolo con la orden time
otra vez. Esta mejora de velocidad se irá haciendo más notable según vaya creciendo nuestra aplicación.
$ time rspec . --drb ......... Finished in 2.21 seconds 9 examples, 0 failures real 0m4.125s user 0m0.342s sys 0m0.097s
Spork es muy útil porque carga todo el entorno de la aplicación una única vez y luego va ejecutando los tests tantas veces como queramos sin tener que volver a recargar la aplicación cada vez.
Uso de Guard con Spork
Guard es una gema que lanza la suite de tests de una aplicación cada vez que se hacen cambios en el código. La vimos en el episodio 264 [verlo, leerlo]. Guard se puede utilizar con Spork mediante una gema llamada guard-spork
que se instala igual que Spork añadiéndola al grupo <ode>test</ode> del Gemfile
y ejecutando bundle
.
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
Tras la instalación de la gema tenemos que ejecutar guard init spork
para añadir Spork al Guardfile
. Si luego inspeccionamos el Guardfile
veremos la sección correspondiente a Spork bajo las configuraciones de 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
Tenemos que poner esta sobre la de RSpec porque se tiene que ejecutar antes que el resto de guardas relacionadas con los tests. También tenemos que modificar la guarda de RSpec para añadir la opción :cli
con el valor --drb
. Con esto haremos que los tests se ejecuten contra el servidor de Spork. Lo mismo tendremos que hacer con el resto de guardas que tengamos para, por ejemplo, Cucumber.
guard 'rspec', :version => 2, :cli => '--drb' do # (se omite el contenido) end
Con esto ya no tenemos que lanzar el servidor de Spork por separado. Cuando arranquemos Guard, éste lanzará automáticamente Spork en segundo plano y ejecutará la suite de tests cuando cambiemos cualquiera de los ficheros Ruby de nuestra aplicación.
$ 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.
Esto es mucho más fácil que tener que gestionar Spork por separado. Cada vez que se cambie uno de los ficheros de nuestra aplicación se ejecutarán los tests relevantes aproximadamente un segundo después.
Configuración de Spork
Veamos ahora cómo configurar Spork. A veces cuando cambiamos un fichero en nuestra aplicación Spork no refleja el cambio, lo que implica que tendremos que reinicarlo. Esto ocurre porque el fichero se carga en el bloque prefork
pero no se recarga en each_run
y es algo que pasa con frecuencia si usamos Factory Girl. Las factorías se cargan en prefork
por lo que los cambios que hagamos no surtirán efecto cuando se vuelva a lanzar la suite de tets. Como queremos que estos cambios ocurran automáticamente tenemos que recargar las factorías en each_run
. Las versiones más recientes de Factory Girl tienen un método reload
que nos pone las cosas muy fáciles:
Spork.each_run do FactoryGirl.reload end
Ahora los cambios que hagamos en las factorías surtirán efecto cada vez que se ejecuten los tests.
Puede ocurrir un problema parecido con los ficheros que se encuentren en /spec/support
. De nuevo, estos archivos se cargan dentro del bloque prefork
, por lo que no se cogerán cuando se vuelva a ejecutar la suite de tests. Podríamos mover el código de carga a each_run
pero cuanto más código pongamos en este método más tardarán los tests en empezar a pasar. Sería mejor poder mantener el código en el bloque preload
y recargar Spork automáticamente cada vez que se modifiquen los archivos en /spec/support
.
Para esto nos puede servir guard-spork
. El Guardfile
contiene un listado de patrones de archivo en el bloque spork
que define los ficheros a vigilar. Aquí podemos añadir el directorio spec/support
para que se reinicie el servidor Spork cada vez que se produzca un cambio.
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
Con esto los cambios en spec/support
serán detectados por Guard, que reiniciará Spork.
Ya que estamos mirando el fichero Guardfile
veamos un truco para utilizar con suites de tests lentas. Si los tests tardan más de un minuto en pasar seguramente no querremos ejecutarlos con tanta frecuencia, por lo que hay dos argumentos a rspec guard
que nos pueden ser de ayuda: all_on_start
y all_over_pass
. Podemos hacer que los dos sean false
.
guard 'rspec', :version => 2, :cli => '--drb', :all_on_start => false, :all_after_pass => false do # watch commands omitted. end
Con estas opciones Guard no ejecutará todos los tests cuando pase un test que antes fallaba, lo que nos da más control sobre cuándo se ejecutan todos los tests. Para lanzar todos los tests podemos pulsar la tecla de retorno en el terminal de Guard (si utilizamos la última versión).
Otro truco que no tiene que ver con Spork pero que resulta útil cuando tenemos una suite de tests grande es añadir tres líneas en el bloque de configuración de RSpec (las tres líneas del final).
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
Si ahora añadimos la etiqueta :focus
en una especificación, sólo se ejecutará dicho tests en lugar de todos. Por ejemplo si añadimos :focus
en un tests del modelo 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 # Other specs omitted. end end
Cuando guardemos este archivo, Guard detectará los cambios pero como se usa la etiqueta :focus
sólo se ejecutará la especificación anterior.
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.
Hay otros casos en los que hay un fragmento de código que se ejecuta en el bloque prefork
y no hay una manera sencilla de moverlo a each_run
. Spork proporciona el método trap_method
para controlar estos escnarios. Este método hace que el método atrapado no se ejecute inmediatamente y lo lanza después de lanzar el proceso. Esto es útil en algunos casos como con Mongoid o Devise, los cuales cargan cosas en el bloque prefork
que realmente no queremos que se carguen ahí. Esto se explica en detalle en el wiki de Spork.
Con esto concluimos este episodio dedicado a Spork, que es una herramienta muy cómoda para acelerar el proceso TDD y que escala muy bien con proyectos Rails grandes si se combina con los trucos que hemos visto.