Sviluppare un extension di Spree con TDD
Qui da NebuLab, abbiamo appena creato un extension per Spree (Spree Subscriptions) che aggiunge alla piattaforma la possibilità di acquistare e gestire abbonamenti basati sul numero di edizioni (es. abbonamento annuo ad un magazine mensile, cioè 12 numeri). Partire da zero con la nostra prima extension ufficiale ci ha dato la possibilità di capire meglio come sviluppare extension per Spree usando un approccio TDD e abbiamo deciso di condividere la nostra esperienza e quelle che riteniamo essere delle best practices su questo argomento. Usando qualche semplice scenario reale e qualche riga di codice presi da spree-subscriptions, faremo una rapida panoramica creando una nuova extension che aggiunge una checkbox alla edit page dei prodotti nel pannello di amministrazione per identificare se il prodotto è un magazine o meno. Inizieremo configurando l'ambiente di sviluppo per poi passare a scrivere i test; solo alla fine faremo in modo che passino.
Prima di iniziare
Scegliamo gli strumenti giusti
Un'extension di Spree non è altro che un semplice engine Rails che ha come dipendenza la gem spree_core (un altro engine); ciò significa che potremmo usare le nostre librerie preferite per testarla. Siccome la comunità Spree ha deciso di usare Rspec, FactoryGirl e Capybara, pensiamo che l'approccio migliore sia quello di usare gli stessi strumenti sia per fare sentire a proprio agio gli altri sviluppatori della comunità qualora volessero contribuire, sia per riprodurre o usare qualche metodo presente nelle librerie di spree_core. Per esempio usando FaactoryGirl abbiamo a disposizione le factories di spree_core da usare liberamente nei test della nostra extension.
Creaiamo l'extension
È preferibile leggere Creating Extension Spree Official Guide, che esplora meglio questo passaggio, prima di continuare.
Installiamo le gem necessarie:
$ gem install rails
$ gem install spree
Ora possiamo creare l'extension usando l'apposito comando:
$ spree extension Subscriptions
$ cd spree_subscriptions
Nota: Questa guida si riferisce alla verisone 1.1 di Spree. L'unica differenza con la nuova verione (1.2, attualmente RC) dovrebbe essere con la gestione di spree_auth, dato che non sarà più una dipendeza stretta di spree_core ma ci permetterà di scegliere il sistema di autenticazione che preferiamo.
Generiamo una risorsa (con i file RSpec pronti)
Nonostante per gli scenari del nostro esempio non avremo bisogno di generare una risorsa, potrebbe essere utile vedere come fare, includendo la generazione dei file RSpec necessari con un singolo comando. Aggiungiamo la gem rspec al nostro Gemfile:
group :test do
gem 'rspec'
end
Dopo un bundle install possiamo lanciare il comando:
$ rails g resource spree/subscription
invoke active_record
create db/migrate/20120705112710_create_spree_subscriptions.rb
create app/models/spree/subscription.rb
create app/models/spree.rb
invoke rspec
create spec/models/spree/subscription_spec.rb
invoke controller
create app/controllers/spree/subscriptions_controller.rb
invoke erb
create app/views/spree/subscriptions
invoke rspec
create spec/controllers/spree/subscriptions_controller_spec.rb
invoke helper
create app/helpers/spree/subscriptions_helper.rb
invoke rspec
create spec/helpers/spree/subscriptions_helper_spec.rb
invoke assets
invoke js
create app/assets/javascripts/spree/subscriptions.js
invoke css
create app/assets/stylesheets/spree/subscriptions.css
invoke resource_route
route namespace :spree do resources :subscriptions end
per lasicare generare l'albero di file e directory, inclusi i file rspec per la nuova risorsa.
Includiamo facotries locali
Di dafault le estensioni di Spree includono le factories usate per testare spree_core. Infatti all'interno dello spec_helper troviamo:
require 'spree/core/testing_support/factories'
Queste factories possono essere molto utili perchè saremo sicuri di modellare i test con oggetti spree reali senza dover perdere tempo a ricostruirli fedelmente ma se stiamo sviluppando una nuova extension probabilmente avremo bisogno di qualche nuova factory, magari che eredita da quelle di spree core. Per aggiungere questa funzionalità possiamo semplicemente aggiungere queste linee allo spec_helper dopo il require delle factory core:
Dir["#{File.dirname(__FILE__)}/factories/**/*.rb"].each do |f|
fp = File.expand_path(f)
require fp
end
Ora possiamo aggiungere le nostre nuove factories nella directory spec/factories. Possiamo anche estendere factories di spree_core come segue:
FactoryGirl.define do
factory :subscribable_product, :parent => :product do
subscribable true
end
end
Usiamo le preferenza di Spree nei nostri test
Testando un extension probabilemnte ci imbatteremo nel setup di qualche preferenza di Spree. Nella gem spree_core viene definito un metodo molto comodo per resettare le preferenze prima di impostarne di nuove (assicurandoci che l'extension funzioni con le preferenze di default di Spree). Per riprodurre questo comportamento anche nella nostra extension possiamo aggiungere un file spec/support/preferences.rb che contiene:
def reset_spree_preferences
Spree::Preferences::Store.instance.persistence = false
config = Rails.application.config.spree.preferences
config.reset
yield(config) if block_given?
end
permettendoci di settare le preferenze nei nostri test in questo modo:
before(:each) do
reset_spree_preferences do |config|
config.default_country_id = create(:country).id
config.site_name = "my dummy test store for subscriptions"
end
end
Aggiungiamo spree_auth gem per testare come se fossimo admin users
Per testare le funzionalità di amminstrazione dobbiamo loggarci come admin user anche tramite i test. Per farlo ci basterà effettuare un require di spree_auth nel file lib/spree_subscriptions.rb, che diventerà:
require 'spree_core'
require 'spree_auth'
require 'spree_subscriptions/engine'
Per completare il login facilmente possiamo usare un metodo preso dalla gem spree_auth definendolo in un file spec/support/auth_methods.rb:
def sign_in_as!(user)
visit '/login'
fill_in 'Email', :with => user.email
fill_in 'Password', :with => 'secret'
click_button 'Login'
end
Lanciamo i test, finalmente
Per testare la nostra extension dobbiamo creare un'app di test che servirà solo per eseguire i test che abbiamo definito. Questa app risiederà nella directory spec/dummy/ e per crearla dovremo lanciare il task:
$ rake test_app
Questo comando crea anche il db di test, quindi andrà rilanciato ogni volta che, ad esempio, aggiungiamo una migrazione. Una volta completata la creazione dell'app di test, lanciamo i test con il comando:
$ rspec spec
Aggiungiamo un integration test
Diciamo di voler testare la possibilità da parte di un utente admin di "flaggare" un prodotto come magazine tramite l'apposita checkbox. Creiamo in primis un integration test in spec/requests/admin/product_spec.rb:
require 'spec_helper'
describe "Product" do
context "on edit" do
it "should be selected as subscribable" do
product = create(:product)
user = create(:admin_user, :email => "test@example.com")
sign_in_as!(user)
visit spree.admin_path
click_link "Products"
within('table.index tr:nth-child(2)') { click_link "Edit" }
check('product_subscribable')
click_button "Update"
page.should have_content("successfully updated!")
page.has_checked_field?('product_subscribable').should == true
end
end
end
Il test non passerà perché la checkbox non esiste ancora. Aggiungiamola con un override di Daface in app/overrides/add_subscribable_checkbox_to_products_form.rb:
Deface::Override.new(:virtual_path => "spree/admin/products/_form",
:name => "adds_subscribable_to_product",
:insert_bottom => "[data-hook='admin_product_form_right']",
:partial => "spree/admin/products/subscription_field")
Come si può notare, questo override cerca di renderizzare un partial contenente il nuovo field del form. Creiamolo nella posizione indicata dal parametro :partial dell'override inserendovi il seguente codice:
<%= f.field_container :subscribable do %>
<%= f.label :subscribable %><br />
<%= f.check_box :subscribable %>
<% end %>
I test continueranno a non passare perchè non esistono metodi per settare (e leggere) l'attributo "subscribable". È arrivato il momento di scrivere qualche test per i modelli.
Aggiungiamo i test per il modello
In spec/model/product_spec.rb:
require 'spec_helper'
describe Spree::Product do
let(:subscribable_product) { Factory(:subscribable_product) }
let(:simple_product) { Factory(:simple_product) }
it "should respond to subscribable method" do
subscribable_product.should respond_to :subscribable
end
it "should be subscribable" do
subscribable_product.subscribable.should be_true
end
it "should have subscribable to false by default" do
simple_product.subscribable?.should be false
end
end
Neanche questi test passeranno perchè la tabella spree_products non contiene la colonna "subscibable". Creiamola con una migrazione e ricordiamoci di ricreare l'app di test.
$ rails g migration add_subscribable_to_spree_products subscribable:boolean
$ rake test_app
Riproviamo a lanciare i test che questa volta... falliranno ancora, per fortuna! Non abbiamo ancora inserito l'attributo "subscribable" nella lista degli attributi accessibili. Facciamolo con un decorator in app/models/spree/product_decorator.rb:
Spree::Product.class_eval do
attr_accessible :subscribable
end
Ora i test passeranno quindi possiamo essere sicuri che gli utenti admin potranno flaggare i prodotti per denotare se sono magazine o meno.
Analizziamo uno screenshot ad ogni test fallito
A volte abbiamo trovato molto importante visualizzare uno screenshot (in formato .html) al fallimento dei test utilizzando una gem che si occupa esattamente di questo: 'capybara-screenshot'. Per aggiungerla ad un extension Spree modifichiamo il Gemfile agiiungendo al :test group:
group :test do
gem 'capybara-screenshot', :require => false
end
ad effettuando un require dopo rspec/rails nello spec_helper
...
require 'rspec/rails'
require 'capybara-screenshot'
require 'ffaker'
...
Potremo trovare gli screenshot nella cartella /spec/dummy/tmp/capybara/. Teniamo a mente che questi file verranno eliminati ogni volta che rilanciamo il comando rake test_app.
Configuriamo TravisCI per la nostra extension
Se decidiamo di servirci di un CI, configurare TravisCI è un gioco da ragazzi con le extension di Spree. La configurazione è praticamente standard tranne per l'aggiunta del comando rake test_app nel before_script. Ecco file .travis.yml di esempio:
before_install: gem install bundler --pre
before_script:
- "bundle exec rake test_app"
script: "DISPLAY=:99.0 bundle exec rspec spec"
notifications:
email:
- your@email.com
branches:
only:
- master
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
Conclusione
Vogliamo arrivare dritti al punto: i test ci hanno risparmitato un sacco di tempo sviluppando la nostra prima extension, specialmente quando abbiamo avuto a che fare con gli ordini. Immaginate di dover testare un feature critica per il completamento dell'ordine; ad ogni modifica si dovrebbero testare guest checkout, checkout come utente registrato, ordine completato tramite pannello di controllo e probabilmente qualche altro caso che non abbiamo considerato. Essere certi che tutto funzioni senza test di sicuro non sarebbe così divertente.