Test RSpec per principianti, parte 3

In questo articolo finale sulle basi di RSpec, copriamo alcune parti incerte che puoi e dovresti evitare, come dovresti comporre i test, perché dovresti evitare il database il più possibile e come accelerare la tua suite di test.  

Temi

  • Test di velocità
  • Bottlenecks del database
  • Preloader di primavera
  • Iffy RSpec Conveniences
  • Ospiti misteriosi
  • Codice in linea
  • Metodi di estrazione

Ora che hai le nozioni di base, dovremmo prendere il tempo per discutere alcune parti incerte di RSpec e TDD: alcuni problemi che possono essere facilmente abusati e alcuni aspetti negativi dell'uso di parti del DSL di RSpec non riflettute. Voglio evitare di riempire un sacco di concetti avanzati nei cervelli TDD appena schiusi, ma sento che alcuni punti devono essere fatti prima di iniziare il tuo primo test di follia. Inoltre, creare una suite di test lenta a causa di cattive abitudini che sono facilmente evitabili è qualcosa che puoi migliorare subito come principiante.

Certo, ci sono alcune cose di cui hai bisogno per avere più esperienza prima che ti sentirai a tuo agio ed efficace con i test, ma scommetto che ti sentirai anche meglio fin dall'inizio se togli alcune delle migliori pratiche che miglioreranno il tuo specifiche molteplici senza estendere troppo le tue capacità in questo momento. È anche una piccola finestra su concetti più avanzati che dovrai raccogliere nel corso del tempo per eseguire i test "master". Sento che non dovrei disturbarti troppo all'inizio con questi perché potrebbe sembrare complicato e confuso prima di aver sviluppato l'immagine più grande che lega tutto perfettamente.

Test di velocità

Iniziamo con velocità. Una suite veloce non è nulla che accada per caso; è una questione di manutenzione "costante". Ascoltare i test molto spesso è molto importante, almeno se si è a bordo con TDD e si è bevuto il Kool-Aid per un po ', e le suite di test veloci rendono molto più ragionevole prestare attenzione a dove i test stanno guidando tu.

La velocità del test è qualcosa di cui dovresti prenderti cura. È fondamentale fare test di un'abitudine regolare e tenerlo divertente. Vuoi essere in grado di eseguire rapidamente i test in modo da ottenere un feedback rapido durante lo sviluppo. Più tempo ci vuole per esercitare la suite di test, più è probabile che tu salti di test sempre di più fino a quando non lo fai alla fine prima di voler spedire una nuova funzione. 

All'inizio potrebbe non sembrare poi così male, ma questo non è un problema banale. Uno dei principali vantaggi di una suite di test è che guida il design della tua applicazione: questa è probabilmente la più grande vittoria di TDD. Le esecuzioni di test più lunghe rendono questa parte praticamente impossibile perché è molto probabile che non le eseguirai per non interrompere il flusso. Test rapidi garantiscono che non hai motivo per non eseguire i tuoi test.

Puoi vedere questo processo come un dialogo tra te e la suite di test. Se questa conversazione diventa troppo lenta, è davvero doloroso continuare. Quando il tuo editor di codice offre la possibilità di eseguire anche i tuoi test, devi assolutamente utilizzare questa funzione. Ciò aumenterà notevolmente la velocità e migliorerà il tuo flusso di lavoro. Passare ogni volta tra l'editor e una shell per eseguire i test invecchia molto rapidamente. Ma dal momento che questi articoli sono rivolti ai programmatori principianti, non mi aspetto che tu imposti subito i tuoi strumenti in questo modo. Esistono altri modi per migliorare questo processo senza dover armeggiare subito con il tuo editor. È bene sapere, però, e consiglio di rendere tali strumenti parte del tuo flusso di lavoro.

Inoltre, sii consapevole del fatto che hai già imparato come suddividere i tuoi test e che non hai bisogno di eseguire la suite di test completa per tutto il tempo. È possibile eseguire facilmente singoli file o anche singoli esso blocca tutto in un editor di codice capace senza mai lasciarlo per il terminale. Puoi mettere a fuoco il test sulla linea sotto test, per esempio. Sembra una magia, per essere sinceri, non diventa mai noioso.

Bottlenecks del database

Scrivere troppo sul database, spesso anche molto inutilmente, è un modo sicuro per rallentare rapidamente la suite di test in modo significativo. In molti scenari di test, è possibile simulare i dati necessari per impostare un test e concentrarsi solo sui dati direttamente testati. Non è necessario eseguire il backup del database per la maggior parte del tempo, specialmente per le parti non sottoposte a test direttamente e supportare solo il test in qualche modo: un utente che ha effettuato l'accesso mentre si sta verificando l'importo da pagare in un checkout, per esempio. L'utente è come un extra che può essere simulato. 

Dovresti cercare di evitare di colpire il database il più possibile perché questo morde una grossa fetta di una suite di test lenta. Inoltre, prova a non impostare troppi dati se non ne hai affatto bisogno. Questo può essere molto facile da dimenticare soprattutto con i test di integrazione. I test unitari sono spesso molto più focalizzati per definizione. Questa strategia si rivelerà molto efficace per evitare il rallentamento delle suite di test nel tempo. Scegli le tue dipendenze con molta cura e scopri qual è la quantità minima di dati che i tuoi test devono superare.

Per ora non voglio entrare in nessun'altra specifica-è probabilmente un po 'troppo presto nella tua traiettoria per parlare di matrici, spie, falsi e cose del genere. Confondervi in ​​questo modo con concetti così avanzati sembra controproducente e vi imbatterete in questi abbastanza presto. Esistono molte strategie per test rapidi che coinvolgono anche altri strumenti oltre a RSpec. Per ora, prova ad avvolgere la tua testa intorno all'immagine più grande con RSpec e test in generale.

Vuoi anche mirare a testare tutto solo una volta, se possibile. Non riesaminare la stessa cosa più e più volte, è solo uno spreco. Ciò avviene principalmente per incidente e / o cattive decisioni di progettazione. Se hai iniziato ad avere dei test lenti, questo è un posto facile da refactoring per ottenere un aumento di velocità.

La maggior parte dei test dovrebbe essere anche a livello di unità, testando i modelli. Questo non solo manterrà le cose veloci, ma fornirà anche il più grande successo per il dollaro. Test di integrazione che testano interi flussi di lavoro, imitando in un certo modo il comportamento dell'utente riunendo un gruppo di componenti e verificandoli in modo sincrono, dovrebbero essere la parte più piccola della piramide di test. Questi sono piuttosto lenti e "costosi". Forse il 10% dei test complessivi non è irrealistico per cui sparare, ma questo dipende, immagino.

Esercitare il database il meno possibile può essere difficile perché è necessario imparare un po 'più di strumenti e tecniche per conseguirlo in modo efficace, ma è essenziale sviluppare suite di test che siano abbastanza veloci abbastanza velocemente da eseguire davvero i test frequentemente.

Preloader di primavera

Il server Spring è una funzionalità di Rails e precarica la tua applicazione. Questa è un'altra strategia semplice per aumentare significativamente la velocità del test, subito dopo l'aggiunta, dovrei aggiungere. Quello che fa è semplicemente mantenere l'applicazione in esecuzione in background senza doverla avviare con ogni singola esecuzione di test. Lo stesso vale per le attività e le migrazioni di Rake; anche questi funzioneranno più velocemente.

Dal momento che Rails 4.1, Spring è stato incluso in Rails-aggiunto al Gemfile automaticamente e non è necessario fare molto per avviare o fermare questo preloader. In passato dovevamo collegare i nostri strumenti di scelta per questo, cosa che puoi ancora fare se hai altre preferenze. La cosa veramente bella e premurosa è che si riavvierà automaticamente se cambi alcune gemme, inizializzatori o file di configurazione, un tocco piacevole e pratico perché è facile dimenticare di prenderti cura di te stesso.

Di default è configurato per funzionare rotaie e rastrello solo comandi. Quindi abbiamo bisogno di configurarlo per funzionare anche con RSpec comando per eseguire i nostri test. Puoi chiedere lo stato di primavera in questo modo:

terminale

stato di primavera

Produzione

La primavera non sta funzionando.

Poiché l'output ci ha detto che Spring non è in esecuzione, è sufficiente avviarlo con server di primavera. Quando ora corri stato di primavera, dovresti vedere qualcosa di simile a questo:

Produzione

La primavera è in corso: 3738 spring server | rspec-dummy | iniziato 21 secondi fa

Ora dovremmo controllare quale Spring è impostato per il precaricamento.

terminale

molla binstub --all

Produzione

* bin / rake: molla già presente * bin / rails: molla già presente

Questo ci dice che Spring sta pre-caricando Rails per rastrello e rotaie comandi, e nient'altro finora. Di cui dobbiamo occuparci. Dobbiamo aggiungere la gemma spring-comandi-rspec, e i nostri test sono quindi pronti per essere precaricati.

Gemfile

gem 'spring-commands-rspec', gruppo:: sviluppo

terminale

pacchetto di installazione bundle exec spring binstub rspec

Ti risparmio l'uscita da installazione bundle; Sono sicuro che hai già visto più della tua buona parte di esso. In esecuzione bundle exec spring binstub rspec, d'altra parte, genera a bin / RSpec file che in sostanza lo aggiunge per essere precaricato da Spring. Vediamo se questo ha funzionato:

terminale

molla binstub --all

Questo ha creato qualcosa chiamato un binstub, un wrapper per eseguibili come rotaie, rastrello, fascio, RSpec e così-così che quando usi il RSpec comando che userà Spring. Per inciso, tali binstub assicurano che si stiano eseguendo questi eseguibili nell'ambiente giusto. Ti permettono anche di eseguire questi comandi da ogni directory della tua app, non solo dalla radice. L'altro vantaggio dei binstub è che non devi anteporre pacchetto exec con tutto.

Produzione

* bin / rake: molla già presente * bin / rspec: molla già presente * bin / rails: molla già presente

Sembra A-OK! Fermiamo e riavviamo il server Spring prima di procedere:

terminale

server spring spring spring

Quindi ora esegui il server Spring in una finestra terminale dedicata e esegui i test con una sintassi leggermente diversa in un'altra. Abbiamo semplicemente bisogno di prefissare ogni esecuzione di test con primavera comando:

terminale

spring specspec

Questo esegue tutti i tuoi file spec, ovviamente. Ma non c'è bisogno di fermarsi lì. Puoi anche eseguire singoli file o test con tag tramite Spring: nessun problema! E saranno tutti fulminei adesso; su piccoli gruppi di test sembrano davvero quasi istantanei. Oltre a questo, puoi usare la stessa sintassi per il tuo rotaie e rastrello comandi. Bello, eh?

terminale

rastrelliera primavera rotaie a molla g modello BondGirl nome: stringa rastrello molla db: migrare ... 

Quindi, apriamo Spring per migliorare le cose in Rails, ma non dobbiamo dimenticare di aggiungere questo gioiellino per far sapere a Spring come giocare a palla con RSpec.

Iffy RSpec Conveniences

Le cose menzionate in questa sezione sono probabilmente buone da evitare fintanto che puoi trovare un'altra soluzione per loro. L'uso eccessivo di alcune delle convenienze di RSpec può portare a sviluppare abitudini di test inadeguate, almeno se non proprio. Quello che discuteremo qui è comodo in superficie ma potrebbe morderti un po 'più tardi lungo la strada.

Non dovrebbero essere considerati AntiPattern - cose da evitare immediatamente - ma piuttosto visti come "odori", cose su cui dovresti stare attento e che potrebbero introdurre un costo significativo che spesso non vuoi pagare. Il ragionamento per questo implica un po 'più di idee e concetti che tu, come principiante, probabilmente non conoscono ancora - e francamente potrebbe essere un po' troppo alto a questo punto - ma dovrei almeno mandarti a casa con alcuni bandiere rosse a cui pensare e impegnarsi a memoria per ora.

  • permettere

Avere un sacco di permettere i riferimenti possono sembrare molto convenienti all'inizio, soprattutto perché ASCIUGANO le cose un po '. Ad esempio, all'inizio sembra abbastanza buono da averli nella parte superiore dei file. D'altro canto, possono facilmente complicare la comprensione del proprio codice, se si visitano test specifici una quantità significativa di tempo dopo. Non disponendo i dati all'interno del tuo permettere i blocchi non aiutano troppo la comprensione dei test. Non è così banale come potrebbe sembrare all'inizio, specialmente se sono coinvolti altri sviluppatori che hanno bisogno di leggere anche il tuo lavoro.

Questo tipo di confusione diventa molto più costoso più gli sviluppatori sono coinvolti. Non è solo una perdita di tempo se devi dare la caccia permettere riferimenti più e più volte, è anche stupido perché sarebbe stato evitabile con pochissimo sforzo. La chiarezza è re, non c'è dubbio. Un altro argomento per mantenere in linea questi dati è che la tua suite di test sarà meno fragile. Non vuoi costruire un castello di carte che diventa più instabile con tutti permettere questo è nascondere i dettagli di ogni test. Probabilmente hai imparato che usare le variabili globali non è una buona idea. In tal senso, permettere è semi-globale all'interno dei file delle specifiche.

Un altro problema è che dovrai testare molte variazioni diverse, stati diversi per scenari simili. Presto sarai a corto di nome ragionevole permettere dichiarazioni per coprire tutte le diverse versioni di cui potreste avere bisogno o finire con un mucchio di tonnellate di variazioni dello stato con nomi simili. Quando si impostano i dati direttamente in ogni test, non si ha questo problema. Le variabili locali sono economiche, altamente leggibili e non creano confusione con altri ambiti. In realtà, possono essere anche più espressivi perché non è necessario prendere in considerazione tonnellate di altri test che potrebbero avere un problema con un nome particolare. Si desidera evitare di creare un altro DSL in cima al framework che tutti devono decifrare per ogni test che si sta utilizzando permettere. Spero che mi senta molto come uno spreco di tempo per tutti.

  • prima & dopo

Salva cose come prima, dopo e le sue variazioni per le occasioni speciali e non la usano sempre, dappertutto. Guardalo come uno dei grandi cannoni che tiri fuori per le metamorfosi. La pulizia dei dati è un buon esempio che è troppo meta per ogni singolo test da affrontare. Vuoi estrarlo, ovviamente.

Ospiti misteriosi

Spesso metti il permettere roba nella parte superiore di un file e nascondere questi dettagli da altri test che li usano andando giù per il file. Desiderate avere le informazioni e i dati pertinenti il ​​più vicino possibile alla parte in cui effettivamente eseguite il test, non a miglia di distanza, rendendo i test individuali più oscuri. 

Alla fine, sembra troppo corda per impiccarsi, perché permettere introduce dispositivi condivisi ampiamente condivisi. In pratica, si riducono a dati di prova fittizi il cui ambito non è abbastanza stretto. 

Questo porta facilmente a un odore importante chiamato "ospite misterioso". Ciò significa che hai dati di test che appaiono dal nulla o che vengono semplicemente assunti. Spesso dovrai cercare prima di tutto per capire un test, specialmente se è trascorso un po 'di tempo da quando hai scritto il codice o se sei nuovo in una base di codice. È molto più efficace definire i dati di test in linea esattamente dove ne hai bisogno, nella configurazione di un particolare test e non in un ambito molto più ampio.

Agente Spec

... descrivi Agent, '#print_favorite_gadget' do it 'stampa il nome dell'agente, il rank e il gadget preferito' si aspetta (agent.print_favorite_gadget) .to eq ('Commander Bond ha una cosa per Aston Martin') end-end

Quando guardi questo, si legge molto bene, giusto? È succinta, a una sola riga, piuttosto pulita, no? Non prendiamoci in giro. Questo test non ci dice molto sul agente in questione, e non ci dice tutta la storia. I dettagli dell'implementazione sono importanti, ma non ne vediamo nulla. L'agente sembra essere stato creato da qualche altra parte, e dovremmo prima scovarlo per capire appieno cosa sta succedendo qui. Quindi forse sembra elegante in superficie, ma ha un prezzo pesante.

Sì, i tuoi test potrebbero non essere sempre super DRY a tale riguardo, ma questo è un piccolo prezzo da pagare per essere più espressivo e più facile da capire, penso. Certo, ci sono delle eccezioni, ma dovrebbero essere semplicemente applicate a circostanze eccezionali dopo che hai esaurito le opzioni che Ruby offre subito. 

Con un ospite misterioso, devi scoprire da dove provengono i dati, perché è importante e quali sono le sue caratteristiche specifiche. Non vedere i dettagli di implementazione in un particolare test in sé rende la tua vita più difficile di quanto debba essere. Voglio dire, fai come ti senti se lavori sui tuoi progetti, ma quando altri sviluppatori sono coinvolti, sarebbe meglio pensare di rendere la loro esperienza con il tuo codice il più agevole possibile. 

Come con molte cose, ovviamente, la sostanza essenziale sta nei dettagli, e tu non vuoi tenere te stesso e gli altri nell'oscurità su quelli. Leggibilità, concisione e praticità di permettere non dovrebbe venire a costo di perdere la chiarezza sui dettagli di implementazione e sulla direzione errata. Vuoi che ogni singolo test racconti tutta la storia e fornisca tutto il contesto per capirlo subito.

Codice in linea

Per farla breve, si desidera avere test facili da leggere e più facili da ragionare, su base test-by-test. Cerca di specificare tutto ciò di cui hai bisogno nel test effettivo e non di più. Questo tipo di rifiuti inizia ad "annusare" proprio come qualsiasi altro tipo di spazzatura. Ciò significa anche che è necessario aggiungere i dettagli necessari per test specifici il più tardi possibile, quando si creano i dati di test in generale, all'interno dello scenario effettivo e non in qualche luogo remoto. L'uso suggerito di permettere offre un diverso tipo di comodità che sembra opporsi a questa idea.

Facciamo un altro andare con l'esempio precedente e implementarlo senza il problema dell'ospite misterioso. Nella soluzione seguente, troveremo tutte le informazioni rilevanti per il test in linea. Possiamo stare bene in questa specifica se fallisce e non ha bisogno di cercare ulteriori informazioni in qualche altro posto.

Agente Spec

... descrivi Agent, '#print_favorite_gadget' do it 'stampa il nome dell'agente, il rank e il gadget preferito' do agent = Agent.new (nome: 'James Bond', rank: 'Commander', preferito_gadget: 'Aston Martin') aspettati (agent.print_favorite_gadget) .to eq ('Commander Bond ha una cosa per Aston Martin') end-end

Sarebbe bello se permettere consente di impostare dati di test barebone che è possibile migliorare in base al bisogno di sapere in ogni test specifico, ma questo non è il modo permettere sta rotolando. È così che oggigiorno usiamo le fabbriche tramite Factory Girl. 

Ti risparmierò i dettagli, soprattutto perché ne ho già scritto alcuni pezzi. Ecco i miei articoli su misura per principianti 101 e 201 su ciò che Factory Girl ha da offrire, se sei già curioso di farlo. È scritto per sviluppatori senza tanta esperienza.

Diamo un'occhiata a un altro semplice esempio che fa un buon uso dei dati di test di supporto impostati in linea:

Agente Spec

descrivi l'agente, '#current_mission' lo fa 'stampa lo stato attuale della missione dell'agente e il suo obiettivo' fai mission_octopussy = Mission.new (nome: 'Octopussy', obiettivo: 'Stop bad white dude') bond = Agent.new (nome : 'James Bond', stato: 'Operazione sotto copertura', sezione: '00', licence_to_kill: true) bond.missions << mission_octopussy expect(bond.current_mission).to eq ('Agent Bond is currently engaged in an undercover operation for mission Octopussy which aims to stop bad white dude') end end

Come puoi vedere, abbiamo tutte le informazioni di cui questo test ha bisogno in un unico posto e non c'è bisogno di dare la caccia a nessun altro posto. Racconta una storia e non è oscura. Come già detto, questa non è la migliore strategia per il codice DRY. Il guadagno è buono, però. Chiarezza e leggibilità superano questo piccolo pezzetto di codice ripetitivo da molto tempo, specialmente in codebase di grandi dimensioni.

Ad esempio, supponiamo che tu scriva qualche nuova caratteristica apparentemente non correlata, e improvvisamente questo test inizia a fallire come danno collaterale e non hai toccato questo file spec in età. 

Pensi di essere felice se hai bisogno di decifrare i componenti di configurazione prima di capire e correggere questo test negativo prima di poter continuare con una funzionalità completamente diversa su cui stai lavorando? Penso di no! Vuoi uscire da questa specifica "non correlata" il più presto possibile e tornare a finire l'altra funzione. 

Quando trovi tutti i dati del test proprio lì dove i tuoi test ti dicono dove fallisce, aumenti le tue possibilità di sistemarlo velocemente senza "scaricare" una parte completamente diversa dell'app nel tuo cervello.

Metodi di estrazione

Puoi pulire e ASCIUGARE significativamente il tuo codice scrivendo i tuoi metodi di supporto. Non è necessario utilizzare RSPec DSL per qualcosa di economico come un metodo Ruby. 

Diciamo che hai trovato un paio di dispositivi ripetitivi che stanno iniziando a sentirsi un po 'sporchi. Invece di andare con a permettere o a soggetto, Definisci un metodo nella parte inferiore di un blocco descrittivo, una convenzione, ed estrai gli elementi comuni in esso. Se viene utilizzato un po 'più ampiamente all'interno di un file, è possibile inserirlo anche nella parte inferiore del file. 

Un buon effetto collaterale è che non stai affrontando alcuna variabile semi-globale in questo modo. Inoltre, ti risparmerai di apportare un sacco di modifiche ovunque, se hai bisogno di modificare leggermente i dati. Ora puoi andare in un punto centrale in cui è definito il metodo e influire su tutti i punti in cui viene utilizzato contemporaneamente. Non male!

Agente Spec

descrivi l'agente, '#current_status' fai 'specula sulla scelta della destinazione dell'agente se lo stato è vacanza' do bond = Agent.new (nome: 'James Bond', stato: 'On vacation', sezione: '00', licence_to_kill : true) expect (bond.current_status) .to eq ('Commander Bond è in vacanza, probabilmente alle Bahamas') fine 'specula sulla scelta della destinazione del quartermaster se lo stato è vacation' do q = Agent.new (name: 'Q', stato: 'In vacanza', sezione: '00', licence_to_kill: true) expect (q.current_status) .to eq ('Il quartermaster è in vacanza, probabilmente al DEF CON') end end

Come puoi vedere, c'è un po 'di codice di configurazione ripetitivo, e vogliamo evitare di scriverlo più e più volte. Invece, vogliamo vedere solo gli elementi essenziali per questo test e avere un metodo per costruire il resto dell'oggetto per noi.

Agente Spec

descrivi l'agente, '#current_status' fai 'specula sulla scelta della destinazione dell'agente se lo stato è vacanza' do bond = build_agent_on_vacation ('James Bond', 'On vacation') si aspetta (bond.current_status) .to eq ('Commander Bond è in vacanza, probabilmente alle Bahamas ') fine' specula sulla scelta della destinazione del quartermaster se lo stato è vacanza 'do q = build_agent_on_vacation (' Q ',' On Vacation ') aspettati (q.current_status) .to eq (' Il quartermaster è in vacanza, probabilmente al DEF CON ') end def build_agent_on_vacation (nome, stato) Agent.new (nome: nome, stato: status, sezione:' 00 ', licence_to_kill: true) end end

Ora il nostro metodo estratto si prende cura di sezione e licence_to_kill roba e quindi non ci distrae dall'essenziale del test. Naturalmente, questo è un esempio fittizio, ma è possibile ridimensionarne la complessità tanto quanto è necessario. La strategia non cambia. È una tecnica di refactoring molto semplice, ecco perché la introduco così presto, ma una delle più efficaci. Inoltre, rende quasi inutile evitare gli strumenti di estrazione offerti da RSpecs.

Quello su cui dovresti prestare attenzione è quanto espressivi possano essere questi metodi di aiuto senza pagare alcun prezzo aggiuntivo.

Pensieri finali

Evitare alcune parti del DSL di RSpec e fare buon uso dei buoni principi di programmazione Ruby e Object-Oriented Programming è un buon modo per avvicinarsi alla scrittura dei test. Puoi usare liberamente l'essenziale, descrivere, contesto e esso, ovviamente. 

Trova una buona ragione per usare altre parti di RSpec ed evitarle il più a lungo possibile. Solo perché le cose possono sembrare convenienti e la fantasia non è un buon motivo per usarle, è meglio mantenere le cose più semplici. 

Semplice è buono; mantiene i tuoi test sani e veloci.