Ben tornato! Se hai perso la prima parte del nostro viaggio finora, allora potresti voler tornare indietro e recuperare il ritardo.
Finora, abbiamo applicato un processo di sviluppo basato sui test per creare la nostra applicazione, oltre a utilizzare il famoso framework di test RSpec. Da qui, esamineremo alcune altre funzionalità di RSpec e analizzeremo l'uso della gemma Pry per aiutarti a eseguire il debug e scrivere il tuo codice.
Prendiamo un momento per esaminare alcune altre funzionalità di RSpec che non sono necessarie in questa semplice applicazione di esempio, ma potresti trovare utile lavorare sul tuo progetto.
Immagina di scrivere un test ma sei interrotto o devi partire per una riunione e non hai ancora completato il codice richiesto per far passare il test.
Potresti cancellare il test e riscriverlo più tardi quando sarai in grado di tornare al tuo lavoro. In alternativa, puoi semplicemente commentare il codice, ma questo è abbastanza brutto e decisamente inutile quando si utilizza un sistema di controllo della versione.
La cosa migliore da fare in questa situazione è definire il nostro test come "in sospeso", quindi ogni volta che vengono eseguiti i test, il framework di test ignorerà il test. Per fare questo è necessario utilizzare il in attesa di
parola chiave:
descrivi "qualche metodo" fallo "dovrebbe fare qualcosa" in attesa di fine
Tutti i buoni framework di test consentono di eseguire il codice prima e dopo l'esecuzione di ogni test. RSpec non è diverso.
Ci fornisce prima
e dopo
metodi che ci permettono di impostare uno stato specifico per il nostro test da eseguire, e di pulire poi quello stato dopo che il test è stato eseguito (questo è così che lo stato non perde ed influisce sul risultato dei test successivi).
descrivi "qualche metodo" fai prima (: ognuno) fai # qualche codice di set-up dopo (: each) fai # qualche codice di decompressione e termina "dovrebbe fare qualcosa" in attesa di fine-fine
Abbiamo già visto il descrivere
bloccare; ma c'è un altro blocco chiamato funzionalmente equivalente contesto
. Puoi usarlo ovunque tu usi descrivere
.
La differenza tra loro è sottile ma importante: contesto
ci consente di definire uno stato per il nostro test. Non esplicitamente però (non impostiamo realmente lo stato definendo a contesto
block - invece è a scopo di leggibilità quindi l'intento del seguente codice è più chiaro).
Ecco un esempio:
descrivi "Qualche metodo" fa il contesto "il blocco fornito" lo fa "restituisce il blocco" fa il contesto di fine ultimo in sospeso "nessun blocco fornito" lo fa "chiama un metodo di fallback" fa il fine ultimo in sospeso
Possiamo usare il mozzicone
metodo per creare una versione falsa di un oggetto esistente e per farlo restituire un valore predeterminato.
Ciò è utile per evitare che i nostri test tocchino le API di Live Service e che guidino i nostri test fornendo risultati prevedibili da determinate chiamate.
Immagina di avere una classe chiamata Persona
e che questa classe ha un parlare
metodo. Vogliamo testare che il metodo funziona come ci aspettiamo che lo faccia. Per fare ciò, mozzeremo il parlare
metodo usando il seguente codice:
descrivi Persona fallo "speak ()" fai bob = stub () bob.stub (: speak) .and_return ('hello') Person.any_instance.stub (: initialize) .and_return (bob) instance = Person.new expect ( instance.speak) .to eq ('hello') end end
In questo esempio, diciamo che "qualsiasi istanza" di Persona
la classe dovrebbe avere il suo inizializzare
metodo stubato in modo da restituire l'oggetto peso
.
Lo noterai peso
è esso stesso uno stub che è impostato in modo tale che qualsiasi time code provi ad eseguire il parlare
metodo tornerà "ciao".
Quindi procediamo a creare un nuovo Persona
istanza e passare la chiamata di instance.speak
in RSpec aspettarsi
sintassi.
Diciamo a RSpec che ci aspettiamo che quella chiamata porti alla stringa "ciao".
Negli esempi precedenti abbiamo utilizzato la funzionalità RSpec e ritorno
per indicare quale dovrebbe essere il nostro stub quando viene chiamato.
Possiamo indicare un diverso valore di ritorno ogni volta che lo stub viene chiamato specificando più argomenti a e ritorno
metodo:
obj = stub () obj.stub (: foo) .and_return (1, 2, 3) expect (obj.foo ()). a eq (1) expect (obj.foo ()). a eq (2) expect (obj.foo ()). a eq (3)
I mock sono simili a Stubs nel senso che stiamo creando versioni false dei nostri oggetti ma invece di restituire un valore predefinito stiamo guidando in modo più specifico i percorsi dei nostri oggetti dovere fare in modo che il test sia valido.
Per farlo usiamo il finto
metodo:
Descrivi Obj esegui "testing ()" do bob = mock () bob.should_receive (: testing) .with ('content') Obj.any_instance.stub (: initialize) .and_return (bob) instance = Obj.new instance. testare ('qualche valore') end-end
Nell'esempio precedente creiamo un nuovo Oggetto
istanza e poi chiama il suo analisi
metodo.
Dietro le quinte di quel codice ci aspettiamo il analisi
metodo da chiamare con il valore 'soddisfare'
. Se non viene chiamato con quel valore (che nell'esempio precedente non lo è), allora sappiamo che parte del nostro codice non ha funzionato correttamente.
Il soggetto
la parola chiave può essere utilizzata in un paio di modi diversi. Tutto ciò è progettato per ridurre la duplicazione del codice.
Puoi usarlo implicitamente (nota la nostra esso
il blocco non fa riferimento soggetto
affatto):
descrivi l'array descrivi "con 3 elementi" fai oggetto [1,2,3] it should_not be_empty end end
Potete usarlo esplicitamente (notate il nostro esso
il blocco si riferisce a soggetto
direttamente):
descrivi MyClass fai descrivere "inizializzazione" fai oggetto MyClass "crea una nuova istanza" fai instance = subject.new expect (instance) .to be_a (MyClass) end end end
Piuttosto che fare costantemente riferimento a un argomento all'interno del codice e trasmettere valori diversi per l'istanziazione, ad esempio:
descrivi "Foo" fai il contesto "A" fallo "Bar" do baz = Baz.new ('a') expect (baz.type) .to eq ('a') contesto finale "B" fallo "Bar" do baz = Baz.new ('b') expect (baz.type) .to eq ('b') end end context "C" fai "Bar" do baz = Baz.new ('c') expect (baz .type) .to eq ('c') end end end
Puoi invece usare soggetto
insieme a permettere
per ridurre la duplicazione:
descrivi "Persona" fai oggetto Person.new (nome) # La persona ha un contesto metodo get_name "Bob" do let (: name) 'Bob' its (: get_name) should == 'Bob' fine contesto "Joe" let (: name) 'Joe' its (: get_name) should == 'Joe' contesto finale "Smith" do let (: name) 'Smith' suo (: get_name) dovrebbe == 'Smith' fine
Ci sono molte altre funzionalità disponibili per RSpec, ma abbiamo esaminato le più importanti che ti troverai ad usare molto quando scrivi i test usando RSpec.
È possibile configurare RSpec per eseguire i test in un ordine casuale. Ciò ti consente di assicurarti che nessuno dei tuoi test abbia dipendenza o dipendenza dagli altri test intorno ad esso.
RSpec.configure do | config | config.order = fine 'casuale'
È possibile impostare questo tramite il comando utilizzando il --ordine
flag / opzione. Per esempio: rspec - ordine casuale
.
Quando usi il --ordine casuale
l'opzione RSpec mostrerà il numero casuale usato per seminare l'algoritmo. Puoi usare di nuovo questo valore "seme" quando pensi di aver scoperto un problema di dipendenza all'interno dei tuoi test. Dopo aver risolto ciò che pensi sia il problema, puoi passare il valore seme in RSpec (ad es. Se il seme era 1234
quindi eseguire --ordine casuale: 1234
) e userà lo stesso seme randomizzato per vedere se può replicare il bug di dipendenza originale.
Hai visto che abbiamo aggiunto un set specifico di progetto di oggetti di configurazione all'interno del nostro Rakefile
. Ma puoi impostare le opzioni di configurazione a livello globale aggiungendole a a .RSpec
file nella tua home directory.
Per esempio, dentro .RSpec
:
--colore - formato annidato
Ora siamo pronti per iniziare a esaminare come possiamo eseguire il debug della nostra applicazione e del nostro codice di test utilizzando la gemma Pry.
È importante capire che, sebbene Pry sia veramente utile per il debug del tuo codice, in realtà è inteso come uno strumento Ruby REPL migliorato (da sostituire IRB
) e non a scopi strettamente di debug; per esempio non ci sono funzioni integrate come: step in, step over o step out etc che normalmente si trovano in uno strumento progettato per il debug.
Ma come strumento di debug, Pry è molto concentrato e snello.
Torneremo al debug in un momento, ma prima analizziamo come utilizzeremo inizialmente Pry.
Allo scopo di dimostrare Pry, aggiungerò altro codice alla mia applicazione di esempio (questo codice extra non influisce in alcun modo sul nostro test)
class RSpecGreeter attr_accessor: test @@ class_property = "Sono una proprietà di classe" def greet binding.pry @instance_property = "Sono una proprietà di istanza" pubs privs "Hello RSpec!" end def pubs test_var = "Sono una variabile di prova" test_var end private def privs mette "I'm private" end end
Noterai che abbiamo aggiunto alcuni metodi aggiuntivi, proprietà di istanza e classe. Facciamo anche chiamate a due dei nuovi metodi che abbiamo aggiunto dal nostro salutare
metodo.
Infine, noterai l'uso di binding.pry
.
binding.pry
Un break-point è un posto all'interno del tuo codice dove l'esecuzione si fermerà.
È possibile impostare più punti di interruzione all'interno del codice e crearli utilizzando binding.pry
.
Quando esegui il tuo codice, noterai che il terminale si fermerà e ti posizionerà all'interno del codice dell'applicazione nel punto esatto in cui è stato inserito il binding.pry.
Di seguito è riportato un esempio di come potrebbe apparire ...
8: def greet => 9: binding.pry 10: pubs 11: privs 12: "Ciao RSpec!" 13: fine
Da questo momento Pry ha accesso all'ambito locale in modo da poter usare Pry molto come faresti IRB
e inizia a digitare per esempio le variabili per vedere quali valori tengono.
È possibile eseguire il Uscita
comando per uscire da Pry e il codice per continuare l'esecuzione.
dove sono
Quando si utilizza un sacco di binding.pry
punti di rottura può essere difficile capire dove si trova l'applicazione.
Per avere un contesto migliore di dove ti trovi in qualsiasi momento, puoi usare il dove sono
comando.
Se eseguito da solo, vedrai qualcosa di simile a quando hai usato binding.pry
(vedrai la linea su cui è stato impostato il break-point e un paio di linee sopra e sotto). La differenza è se si passa un argomento numerico in più come whereami 5
vedrai cinque linee aggiuntive sopra dove il binding.pry
è stato posto. Ad esempio, potresti richiedere di vedere 100 linee attorno al punto di interruzione corrente.
Questo comando può aiutarti ad orientarti all'interno del file corrente.
WTF
Il WTF
comando sta per "what the f ***" e fornisce una traccia stack completa per l'eccezione più recente che è stata lanciata. Può aiutarti a capire i passaggi che portano all'errore che si è verificato.
ls
Il ls
comando visualizza quali metodi e proprietà sono disponibili per Pry.
Quando eseguito ti mostrerà qualcosa come ...
Metodi di RSpecGreeter #: greet pubs test test = variabili di classe: @@ class_property locals: _ __ _dir_ _ex_ _file_ _in_ _out_ _pry_
Nell'esempio precedente possiamo vedere che abbiamo quattro metodi pubblici (ricorda che abbiamo aggiornato il nostro codice per includere alcuni metodi aggiuntivi e poi test
e test =
sono stati creati quando si utilizza Ruby attr_accessor
abbreviazione).
Mostra anche altre variabili locali e di classe a cui è possibile accedere.
Un'altra cosa utile che puoi fare è grep (cercare) i risultati solo per ciò che ti interessa. Avrai bisogno di capire le espressioni regolari, ma può essere una tecnica utile. Ecco un esempio ...
ls -p -G ^ p => Metodi di RSpecGreeter #: privilegi
Nell'esempio sopra stiamo usando il -p
e -sol
opzioni / flag che indicano a Pry che vogliamo solo vedere i metodi pubblici e privati e che usiamo la regex ^ p
(che significa abbinare qualsiasi cosa a partire da p
) come modello di ricerca per filtrare i risultati.
In esecuzione ls --help
mostrerà anche tutte le opzioni disponibili.
CD
È possibile modificare l'ambito corrente utilizzando il CD
comando.
Nel nostro esempio se corriamo cd ... / pubs
ci porterà al risultato di quella chiamata al metodo.
Se ora corriamo dove sono
vedrai che verrà visualizzato All'interno "I'm a test variable"
.
Se corriamo se stesso
allora vedrai che arriviamo "Sono una variabile di prova"
tornato.
Se corriamo self.class
vedremo Stringa
tornato.
È possibile spostare la catena di ambito utilizzando CD…
oppure puoi tornare al livello più alto dell'ambito usando cd /
.
Nota: potremmo aggiungere un altro binding.pry
dentro il pub
metodo e quindi il nostro ambito sarebbe all'interno di quel metodo piuttosto che il risultato del metodo.
annidamento
Considera il precedente esempio di esecuzione cd pub
. Se eseguiamo il annidamento
comando otterremo un aspetto di primo livello sul numero di contesti / livelli che Pry ha attualmente:
Stato di nidificazione: - 0. # (leva di livello superiore) 1. "Sono una variabile di prova"
Da lì possiamo correre Uscita
per tornare al contesto precedente (ad esempio, all'interno di salutare
metodo)
In esecuzione Uscita
di nuovo significa che stiamo chiudendo l'ultimo contesto che Pry ha e così Pry finisce e il nostro codice continua a funzionare.
find-metodo
Se non sei sicuro di dove trovare un particolare metodo, puoi utilizzare il find-metodo
comando per mostrare tutti i file all'interno del tuo codice base che ha un metodo che corrisponde a quello che stai cercando:
find-method priv => Kernel Kernel # private_methods Modulo Module # private_instance_methods Modulo # private_constant Modulo # private_method_defined? Modulo # private_class_method Modulo # private RSpecGreeter RSpecGreeter # privs
Puoi anche usare il -c
opzione / flag per cercare il contenuto dei file invece:
find-method -c greet => RSpecGreeter RSpecGreeter: def greet RSpecGreeter # privs: greet
Il prossimo
, passo
, Continua
Anche se le tecniche di cui sopra sono utili, non sono in realtà "debug" nello stesso senso in cui probabilmente sei abituato.
Per la maggior parte degli sviluppatori, il loro editor o browser fornirà loro uno strumento di debug integrato che consente loro di passare effettivamente attraverso il loro codice riga per riga e seguire il percorso che il codice impiega fino al completamento.
Poiché Pry è stato sviluppato per essere usato come REPL che non vuol dire che non sia utile per il debug.
Una soluzione ingenua sarebbe quella di impostare più binding.pry
dichiarazioni attraverso un metodo e l'uso CTRL-D per passare attraverso ogni set di break-point. Ma questo non è ancora abbastanza buono.
Per il debugging passo dopo passo puoi caricare la gemma pry-nav ...
source "https://rubygems.org" gem 'rspec' group: sviluppo do gem 'guard' gemma 'guard-rspec' gem 'pry' # Aggiunge passi di debug a Pry # continua, passo, prossimo gem 'pry-remote' gemma 'pry-nav' fine
Questo gioiello estende Pry in modo che comprenda i seguenti comandi:
Il prossimo
(vai alla riga successiva)Passo
(passare alla riga successiva e se si tratta di un metodo, quindi passare a tale metodo)Continua
(Ignora qualsiasi ulteriore break-point in questo file)Come bonus aggiuntivo integriamo i nostri test con il servizio online CI (integrazione continua) Travis-CI.
Il principio dell'IC è di impegnarsi / spingere presto e spesso per evitare conflitti tra il codice e il ramo principale. Quando lo fai (in questo caso ci stiamo impegnando a GitHub), allora dovresti dare il via a una "build" sul tuo server CI che esegue i test rilevanti per garantire che tutto funzioni come dovrebbe.
Ora con TDD come metodologia di sviluppo primaria è meno probabile che si verifichino bug ogni volta che si spinge perché i test sono parte integrante del flusso di lavoro dello sviluppo e quindi prima di spingerti sarai già a conoscenza di bug o regressioni. Ma questo non ti protegge necessariamente dai bug che si verificano dai test di integrazione (dove tutto il codice su più sistemi viene eseguito insieme per assicurare che il sistema 'nel suo complesso' funzioni correttamente).
Indipendentemente da ciò, il codice non dovrebbe mai essere spinto direttamente al server di produzione live in alcun modo; dovrebbe sempre essere spinto prima su un server CI per aiutare a cogliere eventuali potenziali errori derivanti dalle differenze tra l'ambiente di sviluppo e l'ambiente di produzione.
Molte aziende hanno ancora più ambienti in cui il loro codice deve passare prima che raggiunga il server di produzione live.
Ad esempio, alla BBC News abbiamo:
Sebbene ogni ambiente debba essere identico nel set-up, lo scopo è quello di implementare diversi tipi di test per garantire che tutti i bug vengano intercettati e risolti prima che il codice raggiunga "live".
Travis CI è un servizio di integrazione continua ospitato per la comunità open source. È integrato con GitHub e offre supporto di prima classe per più lingue
Ciò significa che Travis-CI offre servizi CI gratuiti per progetti open-source e dispone anche di un modello a pagamento per aziende e organizzazioni che desiderano mantenere privata l'integrazione con gli elementi della configurazione..
Useremo il modello open-source gratuito sul nostro repository GitHub di esempio.
Il processo è questo:
.travis.yml
file nella directory principale del progetto e trasferirlo nel repository GitHubIl passo finale è il più importante (creare a .travis.yml
file) in quanto determina le impostazioni di configurazione per Travis-CI in modo che sappia come gestire i test per il proprio progetto.
Diamo un'occhiata al .travis.yml
file che stiamo usando per il nostro repository GitHub di esempio:
lingua: ruby cache: bundler rvm: - 2.0.0 - 1.9.3 script: 'bundle exec rake spec' bundler_args: - senza rami di sviluppo: solo: - notifiche principali: email: - [email protected]
Rompiamo questo pezzo per pezzo ...
Per prima cosa specifichiamo quale lingua stiamo usando nel nostro progetto. In questo caso stiamo usando Ruby: lingua: rubino
.
Perché l'esecuzione di Bundler può essere un po 'lenta e sappiamo che le nostre dipendenze non cambieranno che spesso possiamo scegliere di mettere in cache le dipendenze, quindi abbiamo impostato cache: bundler
.
Travis-CI usa RVM (Ruby Version Manager) per installare Rubies sui loro server. Quindi dobbiamo specificare a quale versione di Ruby vogliamo sottoporre i nostri test. In questo caso abbiamo scelto 2.0
e 1.9.3
che sono due popolari versioni di Ruby (tecnicamente la nostra applicazione utilizza Ruby 2 ma è bene sapere che il nostro codice passa anche in altre versioni di Ruby):
rvm: - 2.0.0 - 1.9.3
Per eseguire i nostri test sappiamo che possiamo usare il comando rastrello
o rake spec
. Travis-CI di default esegue il comando rastrello
ma a causa di come le gemme sono installate su Travis-CI usando Bundler, è necessario modificare il comando predefinito: script: 'bundle exec rake spec'
. Se non lo facessimo, allora Travis-CI avrebbe avuto un problema nel localizzare il rspec / core / rake_task
file che è specificato all'interno del nostro Rakefile
.
Nota: in caso di problemi relativi a Travis-CI, è possibile partecipare al canale #travis su IRC freenode per ottenere assistenza in caso di domande. È lì che ho scoperto la soluzione al mio problema con Travis-CI che non era in grado di eseguire i miei test usando il suo valore predefinito rastrello
comando e il suggerimento di sovrascrivere il default con pacchetto rake exec
risolto questo problema.
Successivamente, poiché siamo interessati solo a eseguire i nostri test, possiamo passare ulteriori argomenti a Travis-CI per filtrare le gemme che non vogliamo disturbare nell'installazione. Quindi per noi vogliamo escludere l'installazione delle gemme raggruppate come sviluppo: bundler_args: - senza sviluppo
(questo significa che stiamo escludendo gemme che sono utilizzate solo per lo sviluppo e il debugging come Pry e Guard).
È importante notare che originariamente stavo caricando Pry all'interno del nostro spec_helper.rb
file. Questo ha causato un problema durante l'esecuzione del codice su Travis-CI, ora che escludevo le gemme "di sviluppo". Quindi ho dovuto modificare il codice in questo modo:
richiede 'pry' se ENV ['APP_ENV'] == 'debug'
Potete vedere che ora la gemma Pry è solo richiedere
'ed se una variabile di ambiente di APP_ENV
è impostato per il debug. In questo modo possiamo evitare che Travis-CI generi errori. Ciò significa che quando si esegue il codice in locale è necessario impostare la variabile di ambiente se si desidera eseguire il debug del codice utilizzando Pry. Quanto segue mostra come ciò potrebbe essere fatto in una riga:
APP_ENV = debug && ruby lib / example.rb
C'erano altri due cambiamenti che ho fatto e questo era per il nostro Gemfile
. Uno era per rendere più chiaro quali gemme erano necessarie per i test e quali erano necessarie per lo sviluppo, e l'altro era esplicitamente richiesto da Travis-CI:
fonte "https://rubygems.org" gruppo: test do gem 'rake' gem 'rspec' fine gruppo: sviluppo do gemma 'guardia' gemma 'guardia-rspec' gemma 'pria' # Aggiunge passi di debug a Pry # continua, passo, la prossima gemma 'pry-remote' gemma 'pry-nav' fine
Guardando quanto sopra aggiornato Gemfile
possiamo vedere che abbiamo spostato la gemma RSpec in una nuova test
gruppo, quindi ora dovrebbe essere più chiaro quale sia lo scopo di ogni gemma. Abbiamo anche aggiunto un nuovo gemma "rastrello"
. La documentazione di Travis-CI afferma che questo deve essere specificato esplicitamente.
La sezione successiva è facoltativa e ti consente di elencare alcune liste bianche (o lista nera) sul tuo repository. Quindi, per impostazione predefinita, Travis-CI eseguirà test su tutti i rami a meno che non lo dici diversamente. In questo esempio diciamo che vogliamo solo che vada contro il nostro maestro
ramo:
rami: solo: - maestro
Potremmo dire che per eseguire ogni ramo 'tranne' un ramo particolare, in questo modo:
rami: tranne: - some_branch_I_dont_want_run
La sezione finale indica a Travis-CI dove inviare le notifiche quando una compilazione fallisce o ha esito positivo:
notifiche: email: - [email protected]
Puoi specificare più indirizzi e-mail se lo desideri:
notifiche: email: - [email protected] - [email protected] - [email protected]
Puoi essere più specifico e specificare cosa vuoi che succeda in caso di errore o di successo (ad esempio, più persone saranno interessate solo se i test falliscono piuttosto che ricevere un'e-mail ogni volta che passano):
notifiche: email: destinatari: - [email protected] on_failure: cambia on_success: mai
L'esempio sopra mostra che il destinatario non riceverà mai un'e-mail se i test passano ma verrà notificato se lo stato di errore cambia (il valore predefinito per entrambi è sempre
il che significa che sarai sempre informato indipendentemente dal risultato dello stato).
Nota: quando si specifica esplicitamente a on_failure
o on_success
è necessario spostare l'indirizzo e-mail all'interno di destinatari
chiave.
Questa è la fine del nostro sguardo in due parti su RSpec, TDD e Pry.
Nella prima parte siamo riusciti a scrivere la nostra applicazione usando il processo TDD e il framework di test di RSpec. In questa seconda parte abbiamo anche utilizzato Pry per mostrare come possiamo eseguire il debug più facilmente di un'applicazione Ruby in esecuzione. Finalmente siamo stati in grado di ottenere la configurazione dei test da eseguire come parte di un server di integrazione continua utilizzando il popolare servizio Travis-CI.
Spero che questo ti abbia dato abbastanza assaggio, quindi sei curioso di approfondire ognuna di queste tecniche.