Il secondo articolo di questa breve serie ti insegna come utilizzare vari abbinamenti che vengono con RSpec. Ti mostra anche come suddividere la tua suite di test tramite tagging, come funzionano i callback e come estrarre alcuni dati. Espandiamo un po 'il kit base di sopravvivenza dal primo articolo e ti dimostriamo abbastanza pericoloso da non avere troppa corda per impiccarti.
Nel primo articolo abbiamo passato parecchio tempo cercando di rispondere al "perché?" Dei test. Suggerisco di tornare subito al "come?" E di risparmiarci un altro contesto. Abbiamo già ampiamente coperto questa parte. Vediamo cos'altro RSpec ha da offrire che tu come principiante puoi gestire subito.
Quindi questo si avvicina al cuore delle cose. RSpec ti offre una tonnellata di cosiddetti matchers. Questi sono il tuo pane e burro quando scrivi le tue aspettative. Finora hai visto .per eq
e .non_a eq
. Ma c'è un arsenale molto più grande per scrivere le tue specifiche. Puoi testare gli errori, i valori di verità e falsi o anche per classi specifiche. Esaminiamo alcune opzioni per iniziare:
.per eq
.non_a eq
Questo test per l'equivalenza.
... si aspetta una 'descrizione intelligente' (agent.enemy). Eq 'Ernst Stavro Blofeld' si aspetta (agent.enemy) .non-eq 'Winnie Pooh' fine ...
Per farla breve, ho preparato due dichiarazioni di aspettativa all'interno di una esso
bloccare. È buona pratica, tuttavia, testare solo una singola cosa per test. Ciò mantiene le cose molto più focalizzate, e i tuoi test finiranno meno fragili quando cambierai le cose.
.essere_truthy
.per essere vero
... è 'una descrizione intelligente' aspettarsi (agent.hero?). Di essere_truthy expect (enemy.megalomaniac?). Essere vero fine ...
La differenza è questa be_truthy
è vero quando non lo è zero
o falso
. Così passerà se il risultato non è né di questi due tipi di "veri". .per essere vero
d'altra parte accetta solo un valore che è vero
e nient'altro.
.per essere_falsy
.essere falso
... è 'una descrizione intelligente' aspettarsi (agente.coward?). Essere-federica si aspetta (nemico.megalomane?). Essere falsa fine ...
Simile ai due esempi sopra, .per essere_falsy
si aspetta che a falso
o a zero
valore, e .essere falso
farà solo un confronto diretto su falso
.
.essere nullo
.to_not be_nil
E, ultimo ma non meno importante, questo test esattamente per zero
si. Ti risparmio l'esempio.
.abbinare ()
Spero che tu abbia già avuto il piacere di guardare nelle espressioni regolari. In caso contrario, si tratta di una sequenza di caratteri con cui è possibile definire un modello che si inserisce tra due barre in avanti per cercare stringhe. Una regex può essere molto utile se si desidera cercare pattern più ampi che si possano generalizzare in tale espressione.
... è 'una descrizione intelligente' aspettarsi (agent.number.to_i). To match (/ \ d 3 /) end ...
Supponiamo di avere a che fare con agenti come James Bond, 007, a cui sono assegnati numeri a tre cifre. Quindi potremmo testarlo in questo modo, in primis qui, ovviamente.
>
<
<=
> =
I confronti sono più utili di quanto si possa pensare. Presumo che gli esempi seguenti riguarderanno ciò che è necessario sapere.
... E 'una descrizione intelligente' ... Aspettatevi (numero dell'agente). Essere < quartermaster.number expect(agent.number).to be > m.number expect (agent.kill_count) .to be> = 25 expect (quartermaster.number_of_gadgets) .to essere <= 5 end…
Ora, stiamo diventando meno noiosi. Puoi anche provare per classi e tipi:
.essere_an_instance_of
.essere un
.essere un
... è 'una descrizione intelligente' do mission = Mission.create (nome: 'Moonraker') agent = Agent.create (nome: 'James Bond') mission.agents << agent expect(@mission.agents).not_to be_an_instance_of(Agent) expect(@mission.agents).to be_a(ActiveRecord::Associations::CollectionProxy) end…
Nell'esempio fittizio di cui sopra, puoi vedere che un elenco di agenti associati a una missione non è di classe Agente
ma di ActiveRecord :: :: Associazioni CollectionProxy
. Quello che dovresti togliere da questo è che possiamo facilmente testare le classi stesse pur restando altamente espressive. .essere un
e .essere un
fai una e la stessa cosa Avete entrambe le opzioni disponibili per mantenere le cose leggibili.
Anche la verifica degli errori è estremamente comoda in RSpec. Se sei super fresco di Rails e non sei ancora sicuro di quali errori possa scatenare il framework, potresti non sentire il bisogno di usarli, ovviamente questo è assolutamente logico. In una fase successiva del tuo sviluppo, li troverai comunque molto utili. Hai quattro modi per affrontarli:
.per raise_error
Questo è il modo più generico. Qualsiasi errore verrà generato nella tua rete.
.to raise_error (ErrorClass)
In questo modo puoi specificare esattamente da quale classe deve venire l'errore.
.to raise_error (ErrorClass, "Some error message")
Questo è ancora più fine in quanto non si menziona solo la classe dell'errore, ma un messaggio specifico che dovrebbe essere gettato con l'errore.
.to raise_error ("Qualche messaggio di errore)
O si cita solo il messaggio di errore stesso senza la classe di errore. La parte che ci si aspetta ha bisogno di essere scritta un po 'diversamente, però - abbiamo bisogno di avvolgere la parte sotto il testo in un blocco di codice stesso:
... è 'una descrizione intelligente' do agent = Agent.create (nome: 'James Bond') si aspetta agent.lady_killer?. A raise_error (NoMethodError) aspettere double_agent.name .to a raise_error (NameError) expect double_agent. name .to a raise_error ("Error: No double agents around") expect double_agent.name .to a raise_error (NameError, "Error: No double agents around") end ...
.iniziare con
.per terminare
Dato che spesso gestiamo le raccolte quando creiamo app web, è bello avere uno strumento per sbirciarle. Qui abbiamo aggiunto due agenti, Q e James Bond, e volevamo solo sapere chi è il primo e l'ultimo nella collezione di agenti per una particolare missione - qui Moonraker.
... è 'una descrizione intelligente' fai moonraker = Mission.create (nome: 'Moonraker') bond = Agent.create (nome: 'James Bond') q = Agent.create (nome: 'Q') moonraker.agents << bond moonraker.agents << q expect(moonraker.agents).to start_with(bond) expect(moonraker.agents).to end_with(q) end…
.includere
Questo è anche utile per controllare il contenuto delle collezioni.
... è 'una descrizione intelligente' do mission = Mission.create (nome: 'Moonraker') bond = Agent.create (nome: 'James Bond') mission.agents << bond expect(mission.agents).to include(bond) end…
Questi abbinamenti predicati sono una funzionalità di RSpec per creare dinamici i corrispondenti per te. Se si dispone di metodi predicati nei propri modelli, ad esempio (che terminano con un punto interrogativo), RSpec sa che è necessario creare per te corrispondenze che è possibile utilizzare nei test. Nell'esempio seguente, vogliamo verificare se un agente è James Bond:
agente di classe < ActiveRecord::Base def bond? name == 'James Bond' && number == '007' && gambler == true end… end
Ora, possiamo usare questo nelle nostre specifiche in questo modo:
... è 'una descrizione intelligente' do agent = Agent.create (nome: 'James Bond', numero: '007', giocatore d'azzardo: true) expect (agent). Be bebond end it 'some intelligent description' do agent = Agent. creare (nome: 'James Bond') aspettarsi (agente) .non-essere bebond fine ...
RSpec ci permette di usare il nome del metodo senza il punto interrogativo per formulare una sintassi migliore, suppongo. Cool, non è vero??
permettere
e permettere!
all'inizio potrebbero sembrare delle variabili, ma in realtà sono metodi di supporto. Il primo viene valutato pigramente, il che significa che viene eseguito e valutato solo quando una specifica lo usa effettivamente, e l'altro lascia con il botto (!) Viene eseguito indipendentemente dall'essere utilizzato da una specifica o meno. Entrambe le versioni sono memoized e i loro valori verranno memorizzati nella cache all'interno dello stesso ambito di esempio.
Descrivi Mission, '#prepare',: let let (: mission) Mission.create (nome: 'Moonraker') let! (: bond) Agent.create (nome: 'James Bond') it 'aggiunge agenti in missione 'do mission.prepare (bond) expect (mission.agents). includere bond end end
La versione bang che non viene ponderata può richiedere molto tempo e quindi è costosa se diventa il tuo nuovo amico di fantasia. Perché? Perché imposterà questi dati per ogni test in questione, non importa cosa, e potrebbe alla fine finire per essere una di queste cose sgradevoli che rallentano significativamente la tua suite di test.
Da allora dovresti sapere questa funzionalità di RSpec permettere
è ampiamente conosciuto e usato. Detto questo, il prossimo articolo ti mostrerà alcuni problemi che dovresti conoscere. Usa questi metodi di supporto con cautela, o almeno in piccole dosi per ora.
RSpec ti offre la possibilità di dichiarare l'argomento sottoposto a test in modo molto esplicito. Ci sono soluzioni migliori per questo, e discuteremo gli aspetti negativi di questo approccio nel prossimo articolo quando mostro alcune cose che in genere si vuole evitare. Ma per ora, diamo un'occhiata a cosa soggetto
può fare per te:
descrivere l'agente, '#status' fare oggetto Agent.create (nome: 'Bond') esso 'restituisce lo stato degli agenti' si aspetta (subject.status) .non-essere 'MIA' end end
Questo approccio può, da un lato, aiutare a ridurre la duplicazione del codice, avere un protagonista dichiarato una volta in un determinato ambito, ma può anche portare a qualcosa chiamato un ospite misterioso. Ciò significa semplicemente che potremmo finire in una situazione in cui utilizziamo i dati per uno dei nostri scenari di test, ma non abbiamo più idea da dove provenga e di cosa sia composto. Maggiori informazioni su questo nel prossimo articolo.
Nel caso in cui non sei ancora a conoscenza dei callback, lascia che ti dia una breve panoramica. Le callback vengono eseguite in determinati punti specifici del ciclo di vita del codice. In termini di Rails, ciò significherebbe che il codice è in esecuzione prima che gli oggetti vengano creati, aggiornati, distrutti, ecc.
Nel contesto di RSpec, è il ciclo di vita dei test in esecuzione. Ciò significa semplicemente che è possibile specificare ganci che devono essere eseguiti prima o dopo l'esecuzione di ogni test nel file spec, o semplicemente attorno a ciascun test. Sono disponibili alcune opzioni a grana fine, ma per il momento mi raccomando di evitare di perdersi nei dettagli. Cominciando dall'inizio:
prima (: ciascuno)
Questo callback viene eseguito prima di ogni esempio di test.
descrivi Agent, '#favorite_gadget' fai prima (: each) do @gagdet = Gadget.create (nome: 'Walther PPK') termina 'restituisce un elemento, il gadget preferito dell'agente' do agent = Agent.create (name : "James Bond") agente.favorite_gadgets << @gadget expect(agent.favorite_gadget).to eq 'Walther PPK' end… end
Supponiamo che tu abbia bisogno di un determinato gadget per ogni test eseguito in un determinato ambito. prima
ti consente di estrarlo in un blocco e preparare questo piccolo frammento per te comodamente. Quando si impostano i dati in questo modo, è necessario utilizzare variabili di istanza, ovviamente, per avere accesso ad esso tra vari ambiti.
Non farti ingannare dalla convenienza in questo esempio. Solo perché puoi fare questo genere di cose non significa che dovresti. Voglio evitare di entrare nel territorio di AntiPattern e confondere l'inferno da te, ma d'altra parte, voglio spiegare gli svantaggi di questi semplici esercizi fittizi un po 'pure.
Nell'esempio sopra, sarebbe molto più espressivo se si impostassero gli oggetti necessari su base test-by-test. Soprattutto su file spec più grandi, è possibile perdere rapidamente la vista di queste piccole connessioni e rendere più difficile per gli altri mettere insieme questi enigmi.
prima (: all)
Questo prima
il blocco viene eseguito solo una volta prima di tutti gli altri esempi in un file spec.
descrivi l'agente, fai '#enemy' prima (: all) do @main_villain = Villain.create (nome: 'Ernst Stavro Blofeld') @mission = Mission.create (nome: 'Moonraker') @ mission.villains << @main_villain end it 'returns the main enemy Bond has to face in his mission' do agent = Agent.create(name: 'James Bond') @mission.agents << agent expect(agent.enemy).to eq 'Ernst Stavro Blofeld' end… end
Quando ricordi le quattro fasi del test, prima
i blocchi a volte sono utili per impostare qualcosa per te che deve essere ripetuto su base regolare, probabilmente roba che è un po 'più meta in natura.
dopo ogni)
e Dopotutto)
hanno lo stesso comportamento ma vengono semplicemente eseguiti dopo che i test sono stati eseguiti. dopo
è spesso usato per pulire i tuoi file, per esempio. Ma penso che sia un po 'presto per affrontarlo. Quindi affidati alla memoria, sappi che è lì nel caso in cui ne avessi bisogno, e passiamo ad esplorare altre cose più basilari.
Tutti questi callback possono essere posizionati strategicamente per soddisfare le tue esigenze. Mettili in qualsiasi descrivere
blocco di cui è necessario eseguirli, non devono necessariamente essere posizionati sopra il file spec. Possono essere facilmente inseriti all'interno delle specifiche.
descrivere Agent do before (: each) do @mission = Mission.create (nome: 'Moonraker') @bond = Agent.create (nome: 'James Bond', numero: '007') termina descrivere '#enemy' fare prima (: each) do @main_villain = Villain.create (nome: 'Ernst Stavro Blofeld') @ mission.villains << @main_villain end describe 'Double 0 Agent with associated mission' do it 'returns the main enemy the agent has to face in his mission' do @mission.agents << @bond expect(@bond.enemy).to eq 'Ernst Stavro Blofeld' end end describe 'Low-level agent with associated mission' do it 'returns no info about the main villain involved' do some_schmuck = Agent.create(name: 'Some schmuck', number: '1024') @mission.agents << some_schmuck expect(some_schmuck.enemy).to eq 'That's above your paygrade!' end end… end end
Come puoi osservare, puoi posizionare blocchi di callback in qualsiasi ambito a tuo piacimento, e andare più in profondità di cui hai bisogno. Il codice nel callback verrà eseguito nell'ambito di qualsiasi ambito del blocco descrittivo. Ma un piccolo consiglio: se senti il bisogno di nidificare troppo e le cose sembrano un po 'complicate e complicate, ripensa il tuo approccio e considera come potresti semplificare i test e la loro configurazione. BACIO! Mantienilo semplice, stupido. Inoltre, fai attenzione a come questo si legge quando forziamo questi test a fallire:
Fallimenti: 1) Agente # nemico Double 0 Agente con missione associata restituisce il nemico principale che l'agente deve affrontare nella sua missione Errore / Errore: aspettarsi (@ bond.enemy). A eq 'Ernst Stavro Blofeld' atteso: "Ernst Stavro Blofeld "got:" Blofeld "2) Agente # nemico Agente di basso livello con missione associata non restituisce informazioni sul cattivo principale coinvolto Errore / Errore: aspettati (some_schmuck.enemy). su eq 'Questo è sopra il tuo stipendio!' atteso: "Questo è superiore al tuo stipendio!" ottenuto: "Blofeld"
Diamo anche una rapida occhiata a quali generatori sono forniti da RSpec per te. Ne hai già visto uno quando abbiamo usato rails genera rspec: installa
. Questo piccolo amico ha creato RSpec per noi in modo facile e veloce. Cos'altro abbiamo?
RSpec: Modello
Vuoi avere un'altra speculazione del modello fittizio?
rails genera rspec: modella another_dummy_model
crea spec / models / another_dummy_model_spec.rb
Veloce, non è vero? O una nuova specifica per un test del controller, ad esempio:
rspec: controllore
rails genera rspec: controller dummy_controller
spec / controller / dummy_controller_controller_spec.rb
RSpec: view
Le stesse opere per le viste, ovviamente. Non testeremo comunque nessuna vista del genere. Le specifiche per le viste ti danno il minimo contraccolpo, ed è assolutamente sufficiente probabilmente in quasi tutti gli scenari per testare indirettamente le tue visualizzazioni tramite test delle funzionalità.
I test di funzionalità non sono una specialità di RSpec e sono più adatti a un altro articolo. Detto questo, se sei curioso, controlla Capybara, che è uno strumento eccellente per quel genere di cose. Ti consente di testare interi flussi che esercitano più parti della tua app, testando funzionalità complete e simulando l'esperienza del browser. Ad esempio, un utente che paga per più articoli in un carrello della spesa.
RSpec: Assistente
La stessa strategia di generatore ci consente anche di piazzare un aiutante senza troppi problemi.
rails genera rspec: helper dummy_helper
crea spec / helper / dummy_helper_helper_spec.rb
Il doppio helper_helper
parte non è stata un incidente. Quando gli diamo un nome più "significativo", vedrai che RSpec si limita ad attaccare _helper
da solo.
rails genera rspec: helper important_stuff
crea spec / helper / important_stuff_helper_spec.rb
No, questa directory non è un posto dove accumulare i tuoi preziosi metodi di aiuto che emergono durante il refactoring dei tuoi test. Questi andrebbero sotto spec / supporto
, in realtà. spec / aiutanti
è per i test che dovresti scrivere per i tuoi aiutanti di visualizzazione: un aiutante come impostare la data
sarebbe un esempio comune Sì, la copertura completa del test del tuo codice dovrebbe includere anche questi metodi di supporto. Solo perché spesso sembrano piccoli e banali non significa che dovremmo ignorarli o ignorare il loro potenziale di bug che vogliamo catturare. Più l'aiutante risulta davvero complesso, più ragione si dovrebbe dover scrivere a helper_spec
per questo!
Nel caso in cui inizi a giocarci subito, tieni presente che devi eseguire i tuoi metodi di supporto su a aiutante
oggetto quando scrivi i tuoi test di supporto per lavorare. Quindi possono essere esposti solo usando questo oggetto. Qualcosa come questo:
descrivere '#set_date' do ... helper.set_date ... end ...
È possibile utilizzare lo stesso tipo di generatori per le specifiche delle caratteristiche, le specifiche di integrazione e le specifiche del mailer. Questi sono fuori dal nostro campo di applicazione per oggi, ma puoi affidarli alla memoria per un utilizzo futuro:
Le specifiche che abbiamo creato tramite il generatore qui sopra sono pronte per essere utilizzate e puoi aggiungere i tuoi test immediatamente. Diamo uno sguardo minuscolo a una differenza tra le specifiche, però:
richiede 'rails_helper' RSpec.describe DummyModel, digita:: model do in sospeso "aggiungi alcuni esempi (o cancella) # __ FILE__" fine
richiede 'rails_helper' RSpec.describe DummyControllerController, digitare:: controller do end
richiede 'rails_helper' RSpec.describe DummyHelperHelper, digita:: helper do in sospeso "aggiungi alcuni esempi (o cancella) # __ FILE__" fine
Non ha bisogno di un bambino prodigio per capire che tutti hanno diversi tipi. Questo :genere
I metadati RSpec ti offrono l'opportunità di suddividere e applicare i test su strutture di file. È possibile indirizzare questi test un po 'meglio in questo modo. Supponi di voler avere una sorta di helper caricati solo per le specifiche del controller, ad esempio. Un altro esempio potrebbe essere che si desidera utilizzare un'altra struttura di directory per le specifiche che RSpec non si aspetta. Avere questi metadati nei test rende possibile continuare a utilizzare le funzioni di supporto di RSpec e non far scattare la suite di test. Quindi sei libero di usare qualsiasi struttura di directory che funzioni per te se lo aggiungi :genere
metadati.
I tuoi test RSpec standard non dipendono da quei metadati, d'altra parte. Quando si usano questi generatori, questi verranno aggiunti gratuitamente, ma è possibile evitarli completamente anche se non ne hai bisogno.
Puoi anche usare questi metadati per filtrare nelle tue specifiche. Supponiamo che tu abbia un blocco precedente che dovrebbe essere eseguito solo sulle specifiche del modello, ad esempio. Neat! Per le suite di test più grandi, questo potrebbe essere molto utile un giorno. È possibile filtrare il gruppo di test mirato che si desidera eseguire, anziché eseguire l'intera suite, operazione che potrebbe richiedere del tempo.
Le tue opzioni si estendono oltre le tre opzioni di codifica sopra, ovviamente. Impariamo di più sull'affettare e tagliare a cubetti i test nella prossima sezione.
Quando si accumula una suite di test più grande nel tempo, non sarà sufficiente eseguire test in determinate cartelle per eseguire i test RSpec in modo rapido ed efficiente. Quello che vuoi essere in grado di fare è eseguire test che appartengono insieme ma potrebbero essere distribuiti su più directory. Tagging per il salvataggio! Non fraintendetemi, anche l'organizzazione intelligente dei test nelle vostre cartelle è fondamentale, ma il tagging lo fa un po 'più lontano.
Dai test ai metadati come simboli come ": wip", ": checkout" o qualsiasi altra cosa si adatti alle tue esigenze. Quando si eseguono questi gruppi di test mirati, si specifica semplicemente che RSpec deve ignorare l'esecuzione di altri test questa volta fornendo un flag con il nome dei tag.
descrivi l'agente,: wip fai 'è un casino adesso' aspetta (agent.favorite_gadgets). a eq 'Unknown' end end
rspec --tag wip
Errori: 1) L'agente è un guaio in questo momento Errore / Errore: aspettarsi (agente.favorite_gadgets) .per eq 'Sconosciuto' ...
Puoi anche eseguire tutti i tipi di test e ignorare un gruppo di gruppi contrassegnati in un determinato modo. Basta fornire una tilde (~) davanti al nome del tag e RSpec è felice di ignorare questi test.
rspec --tag ~ wip
L'esecuzione di più tag in modo sincrono non è un problema:
rspec --tag wip --tag checkout rspec --tag ~ wip --tag checkout
Come puoi vedere sopra, puoi combinarli a piacimento. La sintassi non si ripete perfettamente --etichetta
forse non è l'ideale, ma hey, non è neanche un problema! Sì, tutto questo è un po 'più di lavoro extra e sovraccarico mentale quando si compongono le specifiche, ma il rovescio della medaglia, in realtà fornisce una potente capacità di dividere la vostra suite di test su richiesta. Su progetti più grandi, può farti risparmiare un sacco di tempo in quel modo.
Quello che hai imparato fino ad ora dovrebbe essere dotato delle basi assolute per giocare con i test da solo: un kit di sopravvivenza per principianti. E davvero giocare e commettere errori il più possibile. Porta RSpec e tutto il gioco guidato al test per un giro e non aspettarti di scrivere subito test di qualità. Ci sono ancora un paio di pezzi mancanti prima che ti senta a tuo agio e prima di essere efficace con esso.
Per me, all'inizio era un po 'frustrante perché era difficile vedere come testare qualcosa quando non l'avevo ancora implementato e non capivo appieno come si sarebbe comportato.
I test dimostrano davvero se comprendi un framework come Rails e sai come si integrano i pezzi. Quando scrivi dei test, dovrai essere in grado di scrivere delle aspettative su come dovrebbe comportarsi un framework.
Non è facile se stai appena iniziando con tutto questo. Occuparsi di più linguaggi specifici del dominio, ad esempio RSpec e Rails, ad esempio, oltre all'apprendimento dell'API Ruby può essere fonte di confusione. Non sentirti male se la curva di apprendimento sembra scoraggiante; diventerà più facile se ti infili. Far spegnere questa lampadina non accadrà per tutta la notte, ma per me è valsa davvero la pena.