Molto spesso sentiamo parlare di Test Driven Development (TDD), ma non altrettanto spesso vediamo applicare questo approccio durante la fase di sviluppo del software.
Tra le varie cause e "motivazioni" quella che sentiamo più spesso ripetere è attribuibile alla mancanza di tempo, mancanza di tempo per un'attività - vogliamo ribadirlo - necessaria tanto quanto il codice stesso per il corretto funzionamento dell'applicazione che si vuole sviluppare.
Ruby, in RubyOnRails in particolare, offre le "consuete" librerie TestUnit
per aiutarci a scrivere i test. In questo articolo esaminiamo ShouldA, un'interessante gemma che, estendendo TestUnit
ci permette di scrivere dei test molto leggibili, e con relativa semplicità.
Un esempio per "stuzzicare" la curiosità, questo script di test (in Rails):
# test "classico" class PostTest < ActiveSupport::TestCase def setup @p = posts(:one) end def test_should_have_valid_title assert @p.valid? # il titolo non può essere nil @p.title = nil assert ! @p.valid? # il titolo non può essere vuoto @p.title = '' assert ! @p.valid? end end
è equivalente a questo:
# con "shoulda"
class PostTest < ActiveSupport::TestCase
context "A post instance" do
setup do
@p = posts(:one)
end
should_require_attributes :title
end
end
Come possiamo vedere, utilizzando Shoulda
è possibile scrivere dei test molto più concisi grazie all'uso delle macro che la gemma ci mette a disposizione. Ma l'aspetto più importante, e quello meno percepito in prima battuta, è il fatto che abbiamo una documentazione eseguibile del comportamento del nostro sistema.
Inoltre, Shoulda
è perfettamente integrato con TestUnit
, quindi possiamo tranquillamente continuare a scrivere i nostri test nella maniera "classica" (ovvero con metodi il cui nome inizia con 'test_
'), ovvero possiamo estendere i nostri test man mano che prendiamo confidenza con ShouldA
.
Installazione
A questo punto dovremmo essere abbastanza incuriositi per voler provare la gemma di persona. Prima di andare avanti, quindi, installiamo Shoulda:
$ sudo gem install shoulda
Anche come plugin all'interno della nostra applicazione Rails:
$ ruby script/plugin install git://github.com/thoughtbot/shoulda.git
Uso
Riprendiamo l'esempio precedente:
class PostTest < ActiveSupport::TestCase context "A post instance" do setup do @p = posts(:one) end should_require_attributes :title should "have some comment(s)" do assert @p.comments.size >= 0 end end end
Ogni "test" viene esplicitato con il metodo should
che prende come parametri una stringa (che cosa fa il test) ed un blocco (il test vero e proprio). All'interno di should
si utilizzano le consuete asserzioni fatte con tutte le variati di assert
.
La prima cosa da introdurre è il concetto di contesto dell'oggetto (un model in questo caso) che vogliamo testare. Molto spesso la leggibilità dei test in TestUnit
peggiora con l'aumentare dei contesti (situazioni) che circostanziano i nostri oggetti. Ad esempio, potremmo voler testare il comportamento del modello Post
quando si tratta di un nuovo oggetto (quindi ancora vuoto), quando non ha commenti, quando non è associato ad alcun tag, e così via.
Per gestire più facilmente queste situazioni ShouldA utilizza il metodo context
, che definisce - appunto - un contesto per i modelli oggetto del test.
Ogni context
può contenere un metodo setup
che inizializza l'oggetto, contestualizzando le sue proprietà (attributi e relazioni).
La cosa più interessante è che è possibile annidare più contesti per poter "specializzare" le caratteristiche dell'oggetto del test (es: un post, un post valido, un post valido con commenti, ...). Va notato che non è necessario, sebbene utile, definire un contesto. Vediamo un esempio:
class PostTest < ActiveSupport::TestCase context "A post instance" do setup do @p = posts(:one) end should "have a valid body" do assert_not_nil @p.body end # specializziamo il contesto: un post senza commento/i context "without comments" do setup do @p.comments.delete_all @p.save end should "not have comments attached" do assert @p.comments.size == 0 end end # torniamo al contesto "padre" should "attach a comment to itself" do @c = Comment.create :content => 'put some word here' comment_count = @p.comments.size @p.comments << @c @p.save assert @p.comments.size == comment_count + 1 end end
Macro per testare i modelli
Un test linere, semplice, scorrevole da leggere (e da scrivere!) rappresenta una risorsa preziosa per il programmatore: meno si scrive e meno errori si possono commettere. Per questo motivo sono presenti una serie di utili macro per testare i modelli in Rails, vediamole nel nostro esempio del Post:
class PostTest < ActiveSupport::TestCase load_all_fixtures should_belong_to :user should_have_many :comments should_require_unique_attributes :title should_require_attributes :body should_only_allow_numeric_values_for :rating should_ensure_value_in_range :rating, 1..5 end
Macro per testare i controller
Ovviamente è possibile testare anche i controller. Vediamo l'uso di alcune macro apposite:
class PostsControllerTest < ActionController::TestCase context "on GET to :show for 1st post" do setup do get :show, :id => post(:one) end should_respond_with :success should_assign_to :post should_render_template :show should_not_set_the_flash end end
Come possiamo vedere, con una sola linea di codice definiamo un test e la sua asserzione implicita (es: should_assign_to :post
), a tutto vantaggio della leggibilità.
Sono anche presenti comode macro per il testing dell'interfaccia RESTful
dei controller, sebbene abbiano ancora bisogno di qualche ritocco per funzionare con Rails 2.1.0 è consigliabile darci un'occhiata.
Conclusioni
Scrivere test può essere considerato un'attività noiosa e "collaterale", ma se cambiamo punto di vista ci accorgiamo che è invece estramemente importante (e utile!) descrivere cosa deve fare la nostra applicazione prima di scrivere come debba farlo. E se riusciamo ad alleggerire questo processo non avremo che vantaggi.
Riteniamo la community di Ruby (e di riflesso, quella di Rails) una delle più attive, pertanto assistiamo alla nascita quasi quotidiana di interessanti "gemme". Per non perdersi in questo immenso mare abbiamo piacere di segnalare le sperimentazioni che effettuiamo, sperando di "solleticare" sempre l'attenzione.