Il mio recente lavoro è stato su un progetto Ruby basato su cloud per la BBC News, le prossime elezioni del 2014. Richiede I / O veloce, scalabilità e deve essere ben testato. Il requisito "essere ben collaudato" è ciò su cui voglio concentrarmi in questo tutorial.
Questo progetto utilizza alcuni servizi Amazon diversi come:
Dobbiamo essere in grado di scrivere test rapidi e darci un riscontro immediato sui problemi con il nostro codice.
Anche se, in questo tutorial, non useremo i servizi Amazon, li accludo perché per noi avere test rapidi, ci richiede di fingere questi oggetti esterni (ad esempio, non dovremmo avere bisogno di una connessione di rete per eseguire i nostri test, perché quella dipendenza può portare a test di esecuzione lenti).
Insieme al leader tecnologico Robert Kenny (che è molto abile nello scrivere applicazioni TDD (sviluppo basato sui test) basate su Ruby) abbiamo utilizzato diversi strumenti che hanno reso questo processo e la nostra programmazione molto più semplice.
Voglio condividere alcune informazioni su questi strumenti con voi.
Gli strumenti che coprirò sono:
Immaginerò che tu abbia familiarità con il codice Ruby e l'ecosistema Ruby. Ad esempio, non avrei bisogno di spiegarti quali sono le "gemme" o il modo in cui funzionano certi concetti / sintassi di Ruby.
Se non sei sicuro, prima di trasferirti, consiglierei di leggere uno dei miei altri post su Ruby per metterti subito alla prova.
Potresti non avere familiarità con Guard, ma in sostanza si tratta di uno strumento a riga di comando che utilizza Ruby per gestire eventi diversi.
Ad esempio, Guard può avvisarti ogni volta che sono stati modificati file specifici ed è possibile eseguire alcune azioni in base al tipo di file o evento sparato.
Questo è noto come "task runner", potresti aver già sentito la frase prima, dato che stanno ricevendo un sacco di utilizzo nel mondo front-end / lato client al momento (Grunt e Gulp sono due esempi popolari).
Il motivo per cui useremo Guard è che aiuta a rendere il ciclo di feedback (quando si fa TDD) molto più stretto. Ci permette di modificare i nostri file di test, vedere un test fallito, aggiornare e salvare il nostro codice e vedere immediatamente se passa o fallisce (a seconda di ciò che abbiamo scritto).
Potresti invece usare qualcosa come Grunt o Gulp, ma preferiamo usare quei tipi di task runner per gestire le cose front-end / lato client. Per il codice back-end / lato server, utilizziamo Rake and Guard.
RSpec, se non lo sapessi già, è uno strumento di test per il linguaggio di programmazione Ruby.
Esegui i tuoi test (usando RSpec) tramite la riga di comando e dimostrerò come puoi rendere questo processo più semplice tramite l'uso del programma di build di Ruby, Rake.
Infine, useremo un'altra gemma Ruby chiamata Pry che è uno strumento di debug di Ruby estremamente potente che si inietta nella tua applicazione, mentre è in esecuzione, per permetterti di ispezionare il tuo codice e capire perché qualcosa non funziona.
Anche se non è necessario per dimostrare l'uso di RSpec e Guard, vale la pena notare che approvo pienamente l'uso di TDD come mezzo per garantire che ogni linea di codice che scrivi abbia uno scopo ed è stata progettata in modo testabile e affidabile.
Descriverò in dettaglio come faremo TDD con una semplice applicazione, quindi almeno avrai un'idea di come funziona il processo.
Ho creato un esempio di base su GitHub per salvarti dal dover scrivere tutto da solo. Sentiti libero di scaricare il codice.
Andiamo ora e rivediamo questo progetto, passo dopo passo.
Ci sono tre file principali necessari per il funzionamento dell'applicazione di esempio, questi sono:
Gemfile
Guardfile
Rakefile
Tra breve esamineremo il contenuto di ciascun file, ma la prima cosa che dobbiamo fare è ottenere la struttura della nostra directory.
Per il nostro esempio di progetto, avremo bisogno di due cartelle create:
lib
(questo terrà il nostro codice dell'applicazione)spec
(questo terrà il nostro codice di prova)Questo non è un requisito per la tua applicazione, puoi facilmente modificare il codice all'interno dei nostri altri file per lavorare con qualsiasi struttura che ti si addice.
Apri il tuo terminale ed esegui il seguente comando:
gem install bundler
Bundler è uno strumento che semplifica l'installazione di altre gemme.
Dopo aver eseguito quel comando, crea i tre file sopra (Gemfile
, Guardfile
e Rakefile
).
Il Gemfile
è responsabile della definizione di un elenco di dipendenze per la nostra applicazione.
Ecco come appare:
source "https://rubygems.org" gem 'rspec' group: sviluppo do gemma 'guardia' gemma 'guard-rspec' gemma 'leva' fine
Una volta salvato questo file, esegui il comando installazione bundle
.
Questo installerà tutte le nostre gemme per noi (incluse quelle gemme specificate all'interno di sviluppo
gruppo).
Lo scopo del sviluppo
gruppo (che è una funzionalità specifica del bundler) è così quando si distribuisce l'applicazione, è possibile indicare al proprio ambiente di produzione di installare solo le gemme necessarie per il corretto funzionamento dell'applicazione.
Quindi, per esempio, tutte le gemme all'interno del sviluppo
gruppo, non sono richiesti per il corretto funzionamento dell'applicazione. Sono utilizzati esclusivamente per aiutarci mentre stiamo sviluppando e testando il nostro codice.
Per installare le gemme appropriate sul tuo server di produzione, devi eseguire qualcosa del tipo:
installazione bundle - senza sviluppo
Il Rakefile
ci permetterà di eseguire i nostri test RSpec dalla riga di comando.
Ecco come appare:
richiede 'rspec / core / rake_task' RSpec :: Core :: RakeTask.new do | task | task.rspec_opts = ['--color', '--format', 'doc'] end
Nota: non hai bisogno di Guard per essere in grado di eseguire i test di RSpec. Usiamo Guard per rendere più facile fare TDD.
Quando installi RSpec, ti dà accesso a un'attività Rake integrata e questo è ciò che stiamo usando qui.
Creiamo una nuova istanza di RakeTask
che per impostazione predefinita crea un'attività chiamata spec
che cercherà una cartella chiamata spec
e eseguirà tutti i file di test all'interno di quella cartella, utilizzando le opzioni di configurazione che abbiamo definito.
In questo caso, vogliamo che l'output della shell abbia colore e vogliamo formattare l'output in doc
stile (è possibile modificare il formato per essere nidificato
come esempio).
Puoi configurare l'attività Rake in modo che funzioni come vuoi e guardare in diverse directory, se è quello che hai. Ma le impostazioni di default funzionano benissimo per la nostra applicazione e quindi è quello che useremo.
Ora se voglio eseguire i test nel mio repository GitHub di esempio, allora ho bisogno di aprire il mio terminale ed eseguire il comando:
rake spec
Questo ci dà il seguente risultato:
rake spec / bin / ruby -S rspec ./spec/example_spec.rb --color --format doc RSpecGreeter RSpecGreeter # greet () Finito in 0,0006 secondi 1 esempio, 0 fallimenti
Come puoi vedere ci sono zero fallimenti. Questo perché sebbene non abbiamo scritto alcun codice applicativo, non abbiamo ancora scritto alcun codice di test.
Il contenuto di questo file dice a Guardia cosa fare quando eseguiamo il guardia
comando:
guardia 'rspec' fa # watch / lib / files watch (% r ^ lib /(.+). rb $) do | m | "spec / # m [1] _ spec.rb" end # watch / spec / files watch (% r ^ spec /(.+). rb $) do | m | "spec / # m [1]. rb" end end
Avrai notato nel nostro Gemfile
abbiamo specificato la gemma: guard-RSpec
. Abbiamo bisogno di questa gemma per permettere a Guard di capire come gestire le modifiche ai file relativi a RSpec.
Se guardiamo di nuovo il contenuto, possiamo vedere che se corriamo guardia rspec
quindi Guard avrebbe guardato i file specificati ed eseguito i comandi specificati una volta che si fossero verificate eventuali modifiche a quei file.
Nota: perché abbiamo solo un compito di guardia, RSpec
, quindi viene eseguito di default se eseguiamo il comando guardia
.
Puoi vedere che la Guardia ci fornisce un orologio
funzione che passiamo ad un'espressione regolare per permetterci di definire quali file siamo interessati a Guard Guardia.
In questo caso, stiamo dicendo alla Guardia di guardare tutti i file all'interno del nostro lib
e spec
cartelle e se eventuali modifiche si verificano a uno di quei file quindi per eseguire i file di test all'interno del nostro spec
cartella per assicurarsi che nessuna modifica apportata abbia rotto i nostri test (e successivamente non ha infranto il nostro codice).
Se hai tutti i file scaricati dal repository GitHub, puoi provare il comando da solo.
Correre guardia
e quindi salvare uno dei file per vederlo eseguire i test.
Prima di iniziare a guardare alcuni test e il codice dell'applicazione, lascia che ti spieghi cosa farà la nostra applicazione. La nostra applicazione è una singola classe che restituirà un messaggio di benvenuto a chiunque stia eseguendo il codice.
I nostri requisiti sono volutamente semplificati, poiché renderà più facile capire il processo che stiamo per intraprendere.
Diamo ora un'occhiata alle specifiche di un esempio (ad esempio il nostro file di test) che descriverà i nostri requisiti. Successivamente, inizieremo a esaminare il codice definito nelle specifiche e vediamo come possiamo usare TDD per aiutarci a scrivere la nostra applicazione.
Creeremo un file intitolato example_spec.rb
. Lo scopo di questo file è diventare il nostro file di specifiche (in altre parole, questo sarà il nostro codice di test e rappresenterà la funzionalità prevista).
Il motivo per cui scriviamo il nostro codice di test prima di scrivere il nostro codice di applicazione reale è perché in definitiva significa che qualsiasi codice applicativo che produciamo esisterà perché è stato effettivamente utilizzato.
Questo è un punto importante che sto facendo e quindi lasciami prendere un momento per chiarirlo in modo più dettagliato.
Tipicamente, se scrivi il codice dell'applicazione per primo (quindi non stai facendo TDD), allora ti troverai a scrivere un codice che a un certo punto nel futuro è sovradimensionato e potenzialmente obsoleto. Attraverso il processo di refactoring o modifica dei requisiti, è possibile che alcune funzioni non vengano mai chiamate.
Questo è il motivo per cui TDD è considerato la migliore pratica e il metodo di sviluppo preferito da utilizzare, perché ogni riga di codice prodotta è stata prodotta per un motivo: per ottenere una specifica non soddisfacente (il tuo requisito aziendale effettivo) da superare. Questa è una cosa molto potente da tenere a mente.
Ecco il nostro codice di prova:
richiede 'spec_helper' Descrivi 'RSpecGreeter' fallo 'RSpecGreeter # greet ()' do greeter = RSpecGreeter.new # Dato greeting = greeter.greet # Quando saluto.should eq ('Hello RSpec!') # End end
Potresti notare i commenti del codice alla fine di ogni riga:
Dato
quando
Poi
Queste sono una forma di terminologia BDD (Behaviour Driven Development). Li ho inclusi per lettori che hanno più familiarità con BDD (Behaviour Driven Development) e che erano interessati a come possono equiparare queste dichiarazioni con TDD.
La prima cosa che facciamo all'interno di questo file è il caricamento spec_helper.rb
(che si trova nella stessa directory del nostro file spec). Torneremo indietro e guarderemo il contenuto di quel file tra un momento.
Successivamente abbiamo due blocchi di codice specifici per RSpec:
descrivere 'x' fare
E 'fatto
Il primo descrivere
il blocco dovrebbe descrivere adeguatamente la classe / modulo specifico su cui stiamo lavorando e fornire i test per. Si potrebbe benissimo avere più descrivere
blocchi all'interno di un singolo file di specifiche.
Ci sono molte teorie differenti su come usare descrivere
e esso
blocchi di descrizione. Personalmente preferisco la semplicità e quindi userò gli identificatori per la classe / i moduli / metodi che testeremo. Ma spesso trovi persone che preferiscono usare frasi complete per le loro descrizioni. Né è giusto o sbagliato, solo preferenza personale.
Il esso
il blocco è diverso e deve essere sempre inserito all'interno di a descrivere
bloccare. Dovrebbe spiegare Come vogliamo che la nostra applicazione funzioni.
Ancora una volta, potresti usare una frase normale per descrivere i requisiti, ma ho scoperto che a volte fare ciò può rendere le descrizioni troppo esplicite, quando dovrebbero essere davvero di più implicito. Essere meno esplicito riduce le possibilità di modifiche alle funzionalità, rendendo la descrizione obsoleta (dovendo aggiornare la descrizione ogni volta che si verificano minori cambiamenti di funzionalità è più un onere che un aiuto). Utilizzando l'identificatore del metodo che stiamo testando (ad esempio, il nome del metodo che stiamo eseguendo) possiamo evitare questo problema.
Il contenuto del esso
block è il codice che testeremo.
Nell'esempio sopra, creiamo una nuova istanza della classe RSpecGreeter
(che non esiste ancora). Inviamo il messaggio salutare
(che anche non esiste ancora) all'oggetto istanziato creato (Nota: queste due linee sono codice Ruby standard a questo punto).
Infine, diciamo al framework di test che ci aspettiamo che il risultato di chiamare il salutare
metodo per essere il testo "Ciao RSpec!
", usando la sintassi di RSpec: eq (qualcosa)
.
Si noti come la sintassi gli consenta di essere facilmente letto (anche da una persona non tecnica). Questi sono noti come asserzioni.
Ci sono molte diverse asserzioni RSpec e non entreremo nei dettagli, ma sentitevi liberi di rivedere la documentazione per vedere tutte le funzionalità che RSpec fornisce.
C'è un certo numero di piastre necessarie per i nostri test da eseguire. In questo progetto, abbiamo solo un file di specifiche ma in un progetto reale probabilmente ne avrai a dozzine (a seconda della dimensione della tua applicazione).
Per aiutarci a ridurre il codice boilerplate, lo inseriremo all'interno di uno speciale file helper che verrà caricato dai nostri file di specifiche. Questo file sarà intitolato spec_helper.rb
.
Questo file farà un paio di cose:
leva
gemma (ci aiuta a eseguire il debug del nostro codice, se necessario).Ecco il codice:
$ << File.join(File.dirname(FILE), '… ', 'lib') require 'pry' require 'example'
Nota: la prima riga potrebbe sembrare un po 'criptica, quindi lascia che ti spieghi come funziona. Qui stiamo dicendo che vogliamo aggiungere il / Lib /
cartella in Ruby's $ LOAD_PATH
variabile di sistema. Ogni volta che Ruby valuta richiede 'some_file'
ha una lista di directory che proverà e individuerà quel file. In questo caso, ci stiamo assicurando che se avremo il codice richiedere 'esempio'
che Ruby sarà in grado di individuarlo perché controllerà il nostro / Lib /
directory e lì, troverà il file specificato. Questo è un trucco intelligente che vedrai usato in molte gemme di Ruby, ma può essere abbastanza confuso se non lo hai mai visto prima.
Il nostro codice applicativo sarà all'interno di un file intitolato example.rb
.
Prima di iniziare a scrivere qualsiasi codice di applicazione, ricorda che stiamo facendo questo progetto TDD. Quindi lasceremo che i test nel nostro file delle specifiche ci guidino su cosa fare prima.
Iniziamo assumendo che tu stia usando guardia
per eseguire i test (quindi ogni volta che apportiamo una modifica example.rb
, La guardia noterà il cambiamento e procederà a correre example_spec.rb
per assicurarsi che i nostri test passino).
Per noi di fare correttamente TDD, il nostro example.rb
il file sarà vuoto e quindi se apriamo il file e lo "salviamo" nel suo stato attuale, Guard verrà eseguito e scopriremo (ovviamente) che il nostro test fallirà:
Errori: 1) RSpecGreeter RSpecGreeter # greet () Failure / Error: greeter = RSpecGreeter.new # Given NameError: costante non inizializzata RSpecGreeter # ./spec/example_spec.rb:5:inblocco (2 livelli) in 'Finito in 0,00059 secondi 1 esempio, 1 errore Esempi non riusciti: rspec ./spec/example_spec.rb:4 # RSpecGreeter RSpecGreeter # greet ()
Ora, prima di andare oltre, permettetemi di chiarire nuovamente che TDD è basato sulla premessa che ogni linea di codice ha una ragione per esistere, quindi non inizia a correre avanti e scrivi più codice di quello che ti serve. Scrivi solo la quantità minima di codice richiesta per il test da superare. Anche se il codice è brutto o non soddisfa la piena funzionalità.
Il punto di TDD è avere una stretta ciclo di feedback, anche conosciuto come "red, green, refactor"). Ciò significa in pratica:
Vedrai in un momento che, poiché le nostre esigenze sono così semplici, non abbiamo bisogno di noi per il refactoring. Ma in un progetto reale con requisiti molto più complessi, probabilmente dovrai fare il terzo passo e rifattorizzare il codice che hai inserito, per far passare il test.
Tornando al nostro test fallimentare, come puoi vedere nell'errore, non c'è RSpecGreeter
classe definita. Risolviamolo e aggiungiamo il seguente codice e salviamo il file in modo che i nostri test vengano eseguiti:
il codice di classe RSpecGreeter # andrà alla fine qui
Ciò comporterà il seguente errore:
Fallimenti: 1) RSpecGreeter RSpecGreeter # greet () Failure / Error: greeter = greeter.greet # When NoMethodError: undefined methodgreet 'per # # ./spec/example_spec.rb:6:in' block (2 livelli) in 'Finito in 0,00036 secondi 1 esempio, 1 errore
Ora possiamo vedere che questo errore ci sta dicendo il metodo salutare
non esiste, quindi aggiungiamolo e poi di nuovo salvare il nostro file per eseguire i nostri test:
il codice # di saluto di classe RSpecGreeter def infine finirà qui
OK, siamo quasi arrivati. L'errore che otteniamo ora è:
Errori: 1) RSpecGreeter RSpecGreeter # greet () Failure / Error: greeter = greeting.should eq ('Hello RSpec!') # Quindi previsto: "Hello RSpec!" got: nil (confrontato usando ==) # ./spec/example_spec.rb:7:in 'block (2 livelli) in' Finito in 0,00067 secondi 1 esempio, 1 fallimento
RSpec ci sta dicendo che si aspettava di vedere Ciao RSpec!
ma invece ha ottenuto zero
(perché abbiamo definito il salutare
metodo, ma in realtà non ha definito nulla all'interno del metodo e quindi restituisce zero
).
Aggiungeremo il pezzo di codice rimanente che dovrebbe far passare il test:
classe RSpecGreeter def saluto "Ciao RSpec!" fine fine
Eccoci qua, un test di passaggio:
Finito in 0,00061 secondi 1 esempio, 0 fallimenti
Abbiamo finito qui Il nostro test è scritto e il codice sta passando.
Finora abbiamo applicato un processo di sviluppo basato sui test per creare la nostra applicazione, oltre a utilizzare il famoso framework di test RSpec.
Lo lasceremo qui per ora. Torna e unisciti a noi per la seconda parte, dove vedremo più funzionalità specifiche di RSpec e utilizzeremo la gemma Pry per aiutarti a eseguire il debug e scrivere il tuo codice.