Prova il tuo codice Ruby con Guard, RSpec e Pry

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.

introduzione

Questo progetto utilizza alcuni servizi Amazon diversi come:

  • SQS (Simple Queue Service)
  • DynamoDB (key / value store)
  • S3 (servizio di archiviazione semplice)

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:

  • RSpec (framework di test)
  • Guardia (task runner)
  • Pry (REPL e debugging)

Cosa devo sapere in anticipo?

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.

Guardia

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

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.

Pry

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.

TDD (Test-Driven Development)

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.

Creare un progetto di esempio

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.

File primari

Ci sono tre file principali necessari per il funzionamento dell'applicazione di esempio, questi sono:

  1. Gemfile
  2. Guardfile
  3. Rakefile

Tra breve esamineremo il contenuto di ciascun file, ma la prima cosa che dobbiamo fare è ottenere la struttura della nostra directory.

Struttura della 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.

Installazione

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).

Gemfile

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

Rakefile

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.

Guardfile

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.

Codice di prova

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.

Il nostro primo test

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.

Scrittura del codice di prova prima del codice dell'applicazione

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.

Creare un aiuto per il nostro test

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:

  • di 'a Ruby dove si trova il nostro codice applicativo principale
  • carica il nostro codice dell'applicazione (per eseguire i test contro)
  • caricare il 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.

Codice dell'applicazione

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:

  • scrivi un test fallito
  • scrivi la quantità minima di codice per farla passare
  • refactoring del codice

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.

Conclusione

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.