Come collaudo

In una recente discussione su Google+, un mio amico ha commentato "Lo sviluppo basato sui test (TDD) e lo sviluppo basato sul comportamento (BDD) è Ivory Tower BS."Questo mi ha spinto a pensare al mio primo progetto, a come mi sentivo allo stesso modo in quel momento e a quello che provo ora. Da quel primo progetto, ho sviluppato un ritmo di TDD / BDD che non solo funziona per me, ma anche per il cliente.

Ruby on Rails viene fornito con una suite di test, denominata Test Unit, ma molti sviluppatori preferiscono utilizzare RSpec, Cucumber o alcune combinazioni dei due. Personalmente, preferisco quest'ultimo, usando una combinazione di entrambi.


RSpec

Dal sito RSpec:

RSpec è uno strumento di test per il linguaggio di programmazione Ruby. Nato sotto la bandiera di Behavior-Driven Development, è progettato per rendere lo sviluppo orientato al test un'esperienza produttiva e piacevole.

RSpec fornisce un potente DSL che è utile sia per i test di unità che per quelli di integrazione. Mentre ho usato RSpec per scrivere test di integrazione, preferisco usarlo solo in una capacità di test delle unità. Pertanto, tratterò di come utilizzo RSpec esclusivamente per i test di unità. Consiglio di leggere The RSpec Book di David Chelimsky e altri per una copertura completa e approfondita di RSpec.


Cetriolo

Ho trovato che i benefici di TDD / BDD superano di gran lunga gli svantaggi.

Cucumber è un framework di test di integrazione e accettazione che supporta Ruby, Java, .NET, Flex e una serie di altri linguaggi e framework web. Il suo vero potere deriva dalla sua DSL; non solo è disponibile in inglese semplice, ma è stato tradotto in oltre quaranta lingue parlate.

Con un test di accettazione leggibile dall'uomo, è possibile fare firmare il cliente su una funzione, prima di scrivere una singola riga di codice. Come con RSpec, riguarderò solo Cucumber nella capacità in cui lo utilizzo. Per la descrizione completa di Cetriolo, consulta il Libro di Cetriolo.


Il set up

Iniziamo innanzitutto un nuovo progetto, istruendo Rails a saltare la Test Unit. Digitare quanto segue in un terminale:

 rota nuovo how_i_test -T

All'interno del Gemfile, Inserisci:

 fonte 'https: //rubygems.org'... gruppo: test do gemma' capybara 'gem' cetriolo-rota ', richiede: gemma gemma' database cleaner 'gemma' factory_girl_rails 'gemma' shoulda 'gruppo finale: sviluppo,: test do gemma 'rspec-rails' fine

Principalmente uso RSpec per garantire che i miei modelli e i loro metodi rimangano sotto controllo.

Qui abbiamo inserito Cetriolo e amici all'interno del gruppo test bloccare. Ciò garantisce che vengano caricati correttamente solo nell'ambiente di test Rails. Notate come carichiamo anche RSpec all'interno del sviluppo e test blocchi, rendendolo disponibile in entrambi gli ambienti. Ci sono alcune altre gemme. di cui fornirò brevemente i dettagli di seguito. Non dimenticare di correre installazione bundle per installarli.

  • Capybara: simula le interazioni del browser.
  • Database Cleaner: pulisce il database tra le esecuzioni di test.
  • Factory Girl Rails: sostituzione del dispositivo.
  • shoulda: metodi helper e matcher per RSpec.

Dobbiamo eseguire i generatori di queste gemme per installarli. Puoi farlo con i seguenti comandi del terminale:

 rails g rspec: installa create .rspec crea spec create spec / spec_helper.rb rails g cetriolo: installa crea config / cucumber.yml crea script / cetriolo chmod script / cetriolo crea caratteristiche / step_definitions crea funzioni / supporta crea funzioni / supporto / env. rb esiste lib / tasks crea lib / tasks / cucumber.rake gsub config / database.yml gsub config / database.yml force config / database.yml

A questo punto, potremmo iniziare a scrivere specifiche e suggerimenti per testare la nostra applicazione, ma possiamo impostare alcune cose per semplificare i test. Iniziamo nel application.rb file.

 modulo HowITest classe Applicazione < Rails::Application config.generators do |g| g.view_specs false g.helper_specs false g.test_framework :rspec, :fixture => true g.fixture_replacement: factory_girl,: dir => 'spec / factories' end ... end end

All'interno della classe Application, sostituiamo alcuni generatori predefiniti di Rails. Per i primi due, salta le visualizzazioni e le specifiche di generazione degli helper.

Questi test non sono necessari, perché utilizziamo solo RSpec per i test delle unità.

La terza riga informa Rails che intendiamo utilizzare RSpec come nostro framework di test di scelta, e dovrebbe anche generare fixture durante la generazione di modelli. La linea finale assicura che usiamo factory_girl per i nostri dispositivi, dei quali vengono creati nel spec / fabbriche elenco.


La nostra prima caratteristica

Per semplificare le cose, scriveremo una semplice funzionalità per l'accesso alla nostra applicazione. Per brevità, salterò l'effettiva implementazione e rimarrò nella suite di test. Ecco i contenuti di Caratteristiche / signing_in.feature:

 Funzionalità: Accesso Per utilizzare l'applicazione Come utente registrato Voglio accedere tramite un modulo Scenario: Accesso tramite il modulo Dato che c'è un utente registrato con email "[email protected]" E sono sul cartello nella pagina Quando inserisco le credenziali corrette e premo il pulsante di accesso Quindi il messaggio flash deve essere "Firmato correttamente".

Quando eseguiamo questo nel terminale con caratteristiche di cetriolo / signing_in.feature, vediamo un sacco di output che termina con i nostri passi indefiniti:

 Dato / ^ c'è un utente registrato con email "(. *?)" $ / Do | arg1 | in sospeso # esprimi l'espressione regolare sopra con il codice che desideri terminare Dato / ^ Sono nella pagina di accesso $ / fai in sospeso # esprimi l'espressione regolare sopra con il codice che desideri terminare Quando / ^ Inserisco le credenziali corrette $ / do in sospeso # esprimi l'espressione regolare qui sopra con il codice che desideri terminare Quando / ^ premo il pulsante di accesso $ / do in sospeso # esprimi l'espressione regolare sopra con il codice che desideri terminare Quindi / ^ il messaggio flash dovrebbe essere " (. *?) "$ / do | arg1 | in sospeso # esprimi l'espressione regolare sopra con il codice che desideri terminare

Il prossimo passo è definire cosa ci aspettiamo che ognuno di questi passi faccia. Esprimiamo questo in Caratteristiche / stepdefinitions / signin_steps.rb, usando il semplice Ruby con i selettori Capybara e CSS.

 Dato / ^ c'è un utente registrato con email "(. *?)" $ / Do | email | @user = FactoryGirl.create (: user, email: email) end Given / ^ Sono nella pagina di accesso $ / do visita sign_in_path end Quando / ^ inserisco le credenziali corrette $ / do fillin "Email", con: @user .email fillin "Password", con: @ user.password end Quando / ^ premo il pulsante di accesso $ / do click_button "Accedi" fine Quindi / ^ il messaggio flash dovrebbe essere "(. *?)" $ / do | testo | all'interno (". flash") do page.should have_content text end end

All'interno di ciascuno dei Dato, quando, e Poi blocchi, usiamo Capybara DSL per definire cosa ci aspettiamo da ogni blocco (tranne nel primo). Nel primo blocco specificato, diciamo a factory_girl di creare un utente memorizzato in utente variabile di istanza per un uso successivo. Se corri caratteristiche di cetriolo / signing_in.feature di nuovo, dovresti vedere qualcosa di simile al seguente:

 Scenario: accesso tramite il modulo # features / signing_in.feature: 6 Dato che c'è un utente registrato con e-mail "[email protected]" # features / step_definitions / signing \ _in \ _steps.rb: 1 Factory non registrata: utente ( ArgumentError) ./features/step_definitions/signing\_in\_steps.rb:2:in '/ ^ c'è un utente registrato con email "(. *?)" $ /' Features / signing_in.feature: 7: in 'Given c'è un utente registrato con email "[email protected]" "

Possiamo vedere dal messaggio di errore che il nostro esempio non riesce sulla riga 1 con un ArgumentError della fabbrica dell'utente non registrata. Potremmo creare questa fabbrica da soli, ma parte della magia che abbiamo impostato prima farà sì che Rails lo faccia per noi. Quando generiamo il nostro modello utente, otteniamo la fabbrica dell'utente gratuitamente.

 rails g modello utente email: stringa password: stringa invoke active_record crea db / migrate / 20121218044026 \ _create \ _users.rb crea app / models / user.rb invoke rspec crea spec / models / user_spec.rb invoca factory_girl crea spec / factory / users .RB

Come puoi vedere, il generatore del modello richiama factory_girl e crea il seguente file:

ruby spec / factories / users.rb FactoryGirl.define do factory: utente invia email "MyString" password "MyString" end end

Non entrerò in una grande profondità di factory_girl qui, ma puoi leggere di più nella loro guida introduttiva. Non dimenticare di correre rake db: migrate e rake db: test: preparare per caricare il nuovo schema. Questo dovrebbe fare in modo che passi il primo passo della nostra funzione e iniziare la strada dell'utilizzo di Cucumber per i test di integrazione. In ogni passaggio delle tue funzionalità, Cucumber ti guiderà verso i pezzi che vede mancanti per farlo passare.


Test del modello con RSpec e Shoulda

Per lo più uso RSpec per assicurarmi che i miei modelli e i loro metodi rimangano sotto controllo. Lo uso spesso anche per alcuni test di controller di alto livello, ma ciò è più dettagliato di quanto consentito da questa guida. Utilizzeremo lo stesso modello utente precedentemente impostato con la nostra funzione di accesso. Guardando indietro all'output dall'esecuzione del generatore di modelli, possiamo vedere che anche noi abbiamo ottenuto user_spec.rb gratuito. Se corriamo rspec spec / models / user_spec.rb dovremmo vedere il seguente output.

 In sospeso: l'utente aggiunge alcuni esempi a (o elimina) /Users/janders/workspace/how\_i\_test/spec/models/user_spec.rb

E se apriamo quel file, vediamo:

 richiedere 'spechelper' descrivere Utente in attesa "aggiungi alcuni esempi (o cancella) # FILE" fine

La linea in sospeso ci dà l'output che abbiamo visto nel terminale. Sfrutteremo i matcher ActiveRecord e ActiveModel di Shoulda per garantire che il nostro modello utente corrisponda alla nostra logica aziendale.

 richiede 'spechelper' descrivi Utente fa contesto "#fields" fallo dovrebbe rispondere a (: email) esso dovrebbe rispondere a (: password) esso dovrebbe rispondere a (: firstname) it dovrebbe rispondere a (: lastname) fine context "#validations" fallo dovrebbe validate_presence_of (: email) it dovrebbe validate_presence_of (: password) it dovrebbe validate_uniqueness_of (: email) fine contesto "#associations" do it should have_many (: tasks) end descrivere "#methods" do let! (: user) FactoryGirl.create (: user) it "nome dovrebbe restituire il nome utente" do user.name.should eql "Testy McTesterson" end end end

Abbiamo impostato alcuni blocchi di contesto all'interno del nostro primo descrivere bloccare per testare cose come campi, convalide e associazioni. Mentre non ci sono differenze funzionali tra a descrivere e a contesto blocco, c'è un contestuale. Noi usiamo descrivere blocchi per impostare lo stato di ciò che stiamo testando, e contesto blocchi per raggruppare quei test. Ciò rende i nostri test più leggibili e mantenibili a lungo termine.

Il primo descrivere ci permette di provare contro il Utente modello in uno stato non modificato.

Usiamo questo stato non modificato per testare il database con i matchers Shoulda che raggruppano ciascuno per tipo. Il prossimo descrivere il blocco imposta un utente dal nostro creato in precedenza utente fabbrica. Configurare l'utente con il permettere il metodo all'interno di questo blocco ci consente di testare un'istanza del nostro modello utente rispetto ad attributi noti.

Ora, quando corriamo rspec spec / models / user_spec.rb, vediamo che tutti i nostri nuovi test falliscono.

 Errori: 1) Nome metodo utente # dovrebbe restituire il nome utente Errore / Errore: user.name.should eql "Testy McTesterson" NoMethodError: nome metodo non definito 'per # # ./spec/models/user_spec.rb:26:inblocco (3 livelli) in '2) Convalida utente # Fallimento / Errore: dovrebbe validate_unquality_of (: email) Gli errori previsti da includere "sono già stati presi" quando l'email è impostata su "arbitrario"stringa ", non ha ottenuto errori # ./spec/models/userspec.rb: 15: in 'block (3 livelli) in '3) Convalida utente # Errore / Errore: dovrebbe validate_presence_of (: password) Errori previsti da includere "non può essere vuoto" quando la password è impostata su zero, non ha ottenuto errori # ./spec/models/user_spec.rb : 14: in 'blocco (3 livelli) in '4) Convalida utente # Fallimento / Errore: dovrebbe validate_presence_of (: email) Errori previsti da includere "non può essere vuoto" quando l'e-mail è impostata su zero, non ha ottenuto errori # ./spec/models/user_spec.rb : 13: in 'blocco (3 livelli) in '5) Associazioni # utente Errore / Errore: dovrebbe averemany (: tasks) L'utente previsto ha un hasmolte associazioni chiamate task (nessuna associazione chiamata tasks) # ./spec/models/user_spec.rb:19:in 'block (3 livelli) in '6) Campi # utente Errore / Errore: dovrebbe risponderea (: ultimanome) previsto # rispondere a: ultimonome # ./spec/models/userspec.rb: 9: in 'block (3 livelli) in '7) Campi # utente Errore / Errore: dovrebbe risponderea (: primanome) previsto # rispondere a: primanome # ./spec/models/userspec.rb: 8: in blocco (3 livelli) in '

Con il fallimento di ognuno di questi test, abbiamo il framework che è necessario aggiungere migrazioni, metodi, associazioni e validazioni ai nostri modelli. Man mano che la nostra applicazione si evolve, i modelli si espandono e il nostro schema cambia, questo livello di test ci fornisce la protezione per introdurre cambiamenti improvvisi.


Conclusione

Sebbene non abbiamo trattato in modo approfondito troppi argomenti, ora dovresti avere una conoscenza di base dell'integrazione e dei test unitari con Cucumber e RSpec. TDD / BDD è una delle cose che gli sviluppatori sembrano o non fanno, ma ho scoperto che i vantaggi di TDD / BDD superano di gran lunga gli svantaggi in più di un'occasione.