oggetto restituito

Questa miniserie in due parti è stata scritta per le persone che vogliono iniziare a lavorare con Factory Girl e andare al sodo senza scavare troppo nella documentazione. Per le persone che hanno iniziato a giocare con i test piuttosto recentemente, ho fatto del mio meglio per mantenerlo adatto ai principianti.

Ovviamente, essere stato nelle stesse scarpe a un certo punto mi ha fatto pensare che non ci vuole molto per far sentire le nuove persone più a proprio agio con i test. Prendendo un po 'più di tempo per spiegare il contesto e demistificare il gergo fa molto per ridurre i tassi di frustrazione per i principianti.

Contenuto

  • Introduzione e contesto
  • infissi
  • Configurazione
  • Definire le fabbriche
  • Usando le fabbriche
  • Eredità
  • Più record
  • sequenze

Introduzione e contesto

Iniziamo con un po 'di storia e parliamo dei bravi ragazzi di thinkbot che sono responsabili di questa famosa gemma Ruby. Nel 2007/2008, Joe Ferris, CTO di thinkbot, l'aveva avuto con i dispositivi e ha iniziato a cucinare la sua soluzione. Passare attraverso vari file per testare un singolo metodo era un punto dolente comune mentre si trattava di dispositivi. Messo diversamente e ignorando tutti i tipi di inflessibilità, questa pratica porta anche a test di scrittura che non ti dicono molto sul loro contesto che viene testato subito.

Il fatto di non essere venduto in quella pratica corrente gli ha permesso di controllare varie soluzioni per le fabbriche, ma nessuno di loro ha supportato tutto ciò che voleva. Così ha inventato Factory Girl, che ha reso i test con i dati dei test più leggibili, ASCIUTTI e anche più espliciti offrendoti il ​​contesto per ogni test. Un paio di anni dopo, Josh Clayton, direttore dello sviluppo di thoughtbot a Boston, divenne il manutentore del progetto. Nel corso del tempo questa gemma è cresciuta costantemente e diventa un "appuntamento fisso" nella comunità di Ruby.

Parliamo di più sui principali punti dolenti che Factory Girl risolve. Quando crei la tua suite di test, hai a che fare con molti record associati e con informazioni che cambiano frequentemente. Vuoi essere in grado di creare set di dati per i tuoi test di integrazione che non sono fragili, facili da gestire ed espliciti. Le tue fabbriche di dati dovrebbero essere dinamiche e in grado di fare riferimento ad altre fabbriche, qualcosa che è in parte al di là degli apparecchi YAML dei vecchi tempi.

Un'altra comodità che si desidera avere è la possibilità di sovrascrivere gli attributi per gli oggetti al volo. Factory Girl ti permette di fare tutto ciò senza sforzo, dato che è scritto in Ruby e un sacco di stregoneria metaprogrammante sta succedendo dietro le quinte e ti viene fornito un linguaggio specifico per il dominio che è facile agli occhi pure.

Costruire i dati delle tue fixture con questa gemma può essere descritto come facile, efficace e nel complesso più conveniente rispetto a giocare con i dispositivi. In questo modo puoi gestire più concetti che con le colonne effettive nel database. Ma basta parlare, prendiamoci le mani un po 'sporche.

infissi?

Persone che hanno esperienza con le applicazioni di test e che non hanno bisogno di conoscere il concetto di proiettori, sentitevi liberi di saltare direttamente alla sezione successiva. Questo è per i neofiti che hanno solo bisogno di un aggiornamento sui dati di test.

Le fixture sono dati di esempio, è davvero così! Per una buona parte della tua suite di test, vuoi essere in grado di popolare il tuo database di test con dati personalizzati per i tuoi casi di test specifici. Per un po 'di tempo, molti sviluppatori hanno utilizzato YAML per questi dati, rendendolo indipendente dal database. Col senno di poi, essere indipendenti in quel modo poteva essere la cosa migliore. Era più o meno un file per modello. Questo da solo potrebbe darti un'idea di tutti i tipi di mal di testa di cui le persone si lamentavano. La complessità è un nemico in rapida crescita che YAML non è in grado di affrontare efficacemente. Qui sotto vedrai che cosa .yml il file con i dati di test è simile.

File YAML: secret_service.yml

"plain Quartermaster: name: Q favorite_gadget: abilità di radio scopa: inventando aggeggi e hacking

00 Agente: nome: James Bond favorite_gadget: Sottomarino Lotus Esprit abilità: ottenere Bond Girls uccise e infiltrazione clandestina "

Sembra un hash, vero? È un elenco separato da due punti di coppie chiave / valore separati da uno spazio vuoto. È possibile fare riferimento ad altri nodi l'uno con l'altro se si desidera simulare associazioni dai propri modelli. Ma penso che sia giusto dire che è lì che la musica si ferma e molti dicono che inizia il loro dolore. Per i set di dati che sono un po 'più coinvolti, i dispositivi YAML sono difficili da mantenere e difficili da modificare senza influenzare altri test. Puoi farli funzionare, naturalmente - dopotutto, gli sviluppatori li usavano molto in passato - ma molte persone erano d'accordo sul fatto che il prezzo da pagare per la gestione degli infissi era solo un po 'troppo alto.

Per evitare di rompere i dati del test quando si verificano inevitabili cambiamenti, gli sviluppatori sono stati felici di adottare nuove strategie che offrivano maggiore flessibilità e comportamento dinamico. Ecco dove entra in scena Factory Girl e lascia i giorni di YAML alle spalle. Un altro problema è la forte dipendenza tra il test e il .yml file di fissaggio. Gli ospiti misteriosi sono anche un grande dolore con questo tipo di infissi. Factory Girl ti consente di evitarlo creando oggetti pertinenti ai test in linea.

Certo, i dispositivi YAML sono veloci, e ho sentito persone che sostengono che una suite di test lenta con dati di Factory Girl li ha fatti tornare sulla terra YAML. Nella mia mente, se usi Factory Girl così tanto da rallentare davvero i test, potresti sovrautilizzare inutilmente le fabbriche e potresti ignorare le strategie che non colpiscono il database. Vedrai cosa intendo quando arriveremo alle sezioni pertinenti. Ovviamente, usa tutto ciò che ti serve, ma considera se sei avvisato se sei stato masterizzato da YAML.

Penso che sarebbe giusto aggiungere che nei primi giorni di Rails e Ruby TDD, i dispositivi YAML erano lo standard di fatto per l'impostazione dei dati di test nella vostra applicazione. Hanno svolto un ruolo importante e hanno contribuito a far progredire l'industria. Al giorno d'oggi hanno una replica abbastanza cattiva. I tempi cambiano, quindi passiamo alle fabbriche, che hanno lo scopo di sostituire i dispositivi.

Configurazione

Presumo che tu abbia già Ruby e RSpec per i test installati sul tuo sistema. In caso contrario, torna dopo aver consultato Google e dovresti essere pronto. È piuttosto semplice, direi. Puoi installare manualmente la gemma nel tuo terminale tramite Conchiglia:

bash gem installa factory_girl

o aggiungilo al tuo Gemfile:

ruby gem "factory_girl", "~> 4.0" e corri installazione bundle.

Ora hai solo bisogno di richiedere Factory Girl per completare la configurazione. Qui sto usando RSpec, quindi aggiungi quanto segue in alto in /spec/spec_helper.rb:

rubino richiede 'factory_girl'

Ruby on Rails

Ovviamente sei coperto se vuoi usare Factory Girl with Rails. Il factory_girl_rails gem fornisce una comoda integrazione di Rails per operaia.

Conchiglia:

bash gem installa factory_girl_rails

Gemfile:

ruby gem "factory_girl_rails", "~> 4.0"

e richiedere ovviamente in spec / spec_helper.rb:

ruby richiede 'factory_girl_rails'

Pratica configurazione della sintassi

Se preferisci digitare qualcosa come (ovviamente lo fai)

Rubino:

ruby create (: utente)

invece di

ruby FactoryGirl.create (: utente)

ogni volta che usi uno dei tuoi stabilimenti hai solo bisogno di includere il Factorygirl :: :: Metodi Sintassi modulo nel file di configurazione del test. Se ti dimentichi di questo passaggio, devi prefigurare tutti i metodi Factory Girl con la stessa prefazione verbosa. Funziona con qualsiasi app Ruby, non solo con quelle di Rails, ovviamente.

Per RSpec trovare il spec / spec_helper.rb file e aggiungi:

"ruby richiede 'factory_girl_rails'

RSpec.configure do | config | config.include FactoryGirl :: Syntax :: Methods end "

Attenzione!

Per i neofiti tra di voi, state attenti che il RSpec.configure il blocco sarà già lì, sepolto sotto una certa quantità di commenti. Puoi anche eseguire la stessa impostazione in un file separato di tuo tipo spec / supporto / factory_girl.rb. In tal caso dovrai aggiungere tu stesso l'intero blocco di configurazione.

La stessa configurazione funziona se si stanno utilizzando altre librerie per il test:

  • Test :: Unit
  • Cetriolo
  • Spinaci
  • MINITEST
  • MINITEST :: Spec
  • MINITEST-rails

Puoi fare più fantasia con la tua configurazione lanciando DatabaseCleaner, per esempio, ma la documentazione implica che questa configurazione è sufficiente per andare avanti, quindi passerò da qui.

Definire le fabbriche

Puoi definire le tue fabbriche ovunque, ma verranno caricate automaticamente se vengono posizionate nelle seguenti posizioni:

RSpec:

bash spec / factories.rb spec / factories / *. rb

Test :: Unit:

bash test / factories.rb test / factories / *. rb

Come puoi vedere, hai la possibilità di dividerli in file separati che aderiscono a qualsiasi logica, o raggruppare le tue fabbriche in un unico grande factories.rb file. La complessità del tuo progetto sarà la migliore linea guida per quando separare logicamente le fabbriche in file separati.

Fabbriche di ossa nude

Factory Girl fornisce una sintassi ruby ​​DSL ben sviluppata per la definizione di fabbriche come utente, inviare o qualsiasi altro oggetto, non solo Record attivo oggetti. Le classi "semplici" di Ruby stanno perfettamente bene. Si inizia impostando un blocco di definizione nel proprio factories.rb file.

Rubino:

"rubino FactoryGirl.define do

fine"

Tutte le fabbriche sono definite all'interno di questo blocco. Le fabbriche hanno solo bisogno di a :simbolo nome e un set di attributi per iniziare. Questo nome deve essere il snake_cased versione del modello che si desidera emulare come SecretServiceAgent nel seguente esempio. La fabbrica sotto è nominata secret_service_agent e ha attributi chiamati nome, favorite_gadget e abilità.

Rubino:

"rubino FactoryGirl.define do

factory: secret_service_agent do name "Q" favorite_gadget "Sottomarino Lotus Esprit" abilità "Inventare aggeggi e hacking" fine

fine"

Attenzione!

Se si prende una cosa lontana da questo articolo, dovrebbe essere la seguente: Solo definire la fabbrica più snella possibile per essere valida per impostazione predefinita, valida nel senso delle convalide di Active Record, ad esempio.

Quando Factory Girl chiama salvare! in alcuni casi, le convalide verranno esercitate. Se qualcuno di loro fallisce, ActiveRecord :: RecordInvalid si alza Definire solo il minimo ti dà più flessibilità se i tuoi dati cambiano e ridurrai le possibilità di rompere test e duplicazioni, dal momento che l'accoppiamento è ridotto al nucleo. Non essere pigro quando componi le tue fabbriche, pagherà molto tempo!

Se ritieni che questo possa sembrare difficile da gestire, probabilmente sarai lieto di sapere che esistono soluzioni a portata di mano per separare gli oggetti e i loro attributi. Eredità e Tratti diventeranno grandi alleati dal momento che sono pratiche strategie per completare le tue fabbriche nude e tenerle ASCIUTTE allo stesso tempo. Scopri di più sull'ereditarietà qui sotto, e il mio secondo articolo si concentrerà un po 'su Tratti.

Usando le fabbriche

Se hai incluso il Factorygirl :: :: Metodi Sintassi nella fase di configurazione, è possibile utilizzare la sintassi abbreviata per creare le fabbriche nei test. Hai quattro opzioni, che la gente chiama costruire strategie:

creare

ruby create (: some_object) # FactoryGirl.create (: some_object)

Questo restituisce un'istanza della classe emulata dalla fabbrica. Si consiglia di usare creare solo quando hai davvero bisogno di colpire il database. Questa strategia rallenta la tua suite di test se usata inutilmente. Usalo se vuoi eseguire le convalide poiché verrà eseguito salvare! sullo sfondo. Penso che questa opzione sia per lo più appropriata quando si eseguono test di integrazione in cui si desidera coinvolgere un database per i propri scenari di test.

costruire

build ruby ​​(: some_object) # FactoryGirl.build (: some_object)

Crea un'istanza e assegna gli attributi, ma riceverai un'istanza restituita che non viene salvata nel database-costruire mantiene l'oggetto solo in memoria. Se si desidera verificare se un oggetto ha determinati attributi, questo farà il lavoro, dal momento che non è necessario l'accesso al database per quel tipo di cose. Dietro le quinte non usa salvare!, il che significa che le convalide non vengono eseguite.

Dritta!

Quando usi associazioni con esso, potresti imbatterti in un piccolo trucchetto. C'è una piccola eccezione per non salvare l'oggetto tramite costruire-esso costruisce l'oggetto ma esso crea le associazioni - che tratterò nella sezione sulle associazioni di Factory Girl. C'è una soluzione per questo nel caso in cui non è quello che stavi acquistando.

build_stubbed

ruby build_stubbed (: some_object) # FactoryGirl.build_stubbed (: some_object)

Questa opzione è stata creata per velocizzare i test e per i casi di test in cui nessuno del codice deve raggiungere il database. Inoltre, crea un'istanza e assegna gli attributi come costruire lo fa, ma rende solo gli oggetti come se fossero stati persistenti. L'oggetto restituito ha tutti gli attributi definiti dalla tua fabbrica eliminati e un falso id e zero timestamp. Anche le loro associazioni sono soppresse costruire associazioni, che stanno usando creare sugli oggetti associati. Poiché questa strategia ha a che fare con stub e non ha alcuna dipendenza dal database, questi test saranno veloci quanto loro.

attributes_for

Questo metodo restituirà un hash dei soli attributi definiti nella factory rilevante, senza associazioni, timestamp e id, ovviamente. È utile se si desidera creare un'istanza di un oggetto senza manipolare manualmente gli hash degli attributi. L'ho visto usato principalmente nelle specifiche del controller in questo modo:

Rubino:

"ruby it 'reindirizza a qualche posizione" scrivi: create, spy: attributes_for (: spy)

aspettarsi (risposta) .per redirect_to (some_location) end "

Confronto degli oggetti

Per chiudere questa sezione, vorrei darti un semplice esempio per vedere i diversi oggetti restituiti da queste quattro strategie di costruzione. Qui sotto puoi confrontare i quattro diversi oggetti che otterrai attributes_for, creare, costruire e build_stubbed:

"rubino FactoryGirl.define do

factory: spy do name "Marty McSpy" favorite_gadget "Hoverboard" abilità "Infiltrazione e spionaggio" fine

fine"

"ruby attributes_for (: spy)

oggetto restituito

"

"ruby create (: spy)

oggetto restituito

"

"ruby build (: spia)

oggetto restituito

"

"ruby build_stubbed (: spy)

oggetto restituito

"

Spero che questo sia stato utile se hai ancora lasciato qualche confusione su come funzionano e su quando utilizzare l'opzione.

Eredità

Amerai questo! Con l'ereditarietà puoi definire le fabbriche solo con gli attributi necessari di cui ciascuna classe ha bisogno per la creazione. Questa fabbrica madre può generare il numero di fabbriche "figlio" che riterrà opportuno per coprire tutti i tipi di scenari di test con serie di dati variabili. Mantenere i dati dei test ASCIUTTI è molto importante, e l'ereditarietà rende questo molto più semplice per te.

Supponiamo che tu voglia definire un paio di attributi principali in una fabbrica e che all'interno di quella stessa fabbrica abbiano fabbriche diverse per la stessa Classe con attributi diversi. Nell'esempio seguente, puoi vedere come evitare di ripetere gli attributi semplicemente annidando le tue fabbriche. Emuliamo un Spiare classe che deve adattarsi a tre diversi scenari di test.

factories.rb

"rubino FactoryGirl.define do

factory: spy do name 'Marty McSpy' licence_to_kill false skills 'Spionaggio e intelligenza'

factory: quartermaster do name "Q" skills "Inventing gizmos and hacking" end factory: bond do name "James Bond" licence_to_kill true end end 

fine"

some_spec.rb

"ruby bond = create (: bond) quartermaster = create (: quartermaster)

quartermaster.name # => 'Q' quartermaster.skills # => 'Inventare aggeggi e hacking' quartermaster.licence_to_kill # => false

bond.name # => 'James Bond' bond.skills # => 'Spionaggio e intelligenza' bond.licence_to_kill # => true "

Come puoi osservare, il :legame e : furiere le fabbriche ereditano gli attributi dai loro genitori :spiare. Allo stesso tempo è possibile sovrascrivere facilmente gli attributi in base alle esigenze ed essere molto espressivi su di essi tramite i loro nomi di fabbrica. Immagina tutte le righe di codice salvate perché non devi ripetere la stessa impostazione di base se vuoi testare stati diversi o oggetti correlati. Quella caratteristica vale la pena usare Factory Girl e rende difficile tornare ai dispositivi YAML.

Se si desidera evitare la nidificazione delle definizioni di fabbrica, è anche possibile collegare esplicitamente le fabbriche al genitore fornendo a genitore hash:

factories.rb

"fabbrica di rubini: la spia chiama" Marty McSpy "licence_to_kill false skills" Spionaggio e intelligenza "fine

factory: bond, parent:: spy do name "James Bond" licence_to_kill true end "

È la stessa funzionalità e quasi come ASCIUTTA.

Record multipli

Ecco un'aggiunta piccola ma comunque gradita a Factory Girl che rende facile gestire le liste come una torta:

  • build_list
  • create_list

Ogni tanto, in situazioni in cui si desidera avere più istanze di una fabbrica senza molta confusione, queste saranno utili. Entrambi i metodi restituiranno un array con la quantità di articoli di fabbrica specificati. Abbastanza pulito giusto?

Rubino:

"ruby spy_clones = create_list (: spy, 3)

fake_spies = build_list (: spy, 3)

spy_clones # => [#, #, #]

fake_spies # => [#, #, #]"

Ci sono sottili differenze tra le due opzioni, ma sono sicuro che le capisci ormai. Vorrei anche ricordare che è possibile fornire entrambi i metodi con un hash di attributi se si desidera sovrascrivere gli attributi di fabbrica al volo per qualche motivo. Le sovrascritture mangeranno un po 'della tua velocità di test se crei molti dati di test con sovrascritture. Probabilmente avere una fabbrica separata con questi attributi modificati sarà un'opzione migliore per la creazione di elenchi.

"ruby smug_spies = create_list (: spy, 3, skills: 'Smug jokes')

double_agents = build_list (: spy, 3, nome: 'Vesper Lynd', abilità: 'Seduzione e contabilità')

smug_spies # => [#, #, #]

double_agents # => [#, #, #]"

Spesso avrai bisogno di un paio di oggetti, quindi Factory Girl ti offre altre due opzioni:

  • build_pair
  • create_pair

È la stessa idea di cui sopra, ma l'array restituito contiene solo due record alla volta.

"ruby create_pair (: spy) # => [#, #]

build_pair (: spy) # => [#, #]"

sequenze

Se pensavi che nominare le spie potesse essere meno statico, hai assolutamente ragione. In questa sezione finale vedremo la creazione di valori univoci sequenziali come dati di test per gli attributi di fabbrica.

Quando potrebbe essere utile? Per quanto riguarda gli indirizzi email univoci per testare l'autenticazione o nomi utente univoci, ad esempio? Ecco dove brillano le sequenze. Diamo un'occhiata a un paio di modi diversi che puoi usare sequenza:

Sequenza "globale"

"rubino FactoryGirl.define do

sequenza: spy_email do | n | "00#[email protected]" fine

factory: spy do name 'Marty McSpy' email '[email protected]' skills 'Spionaggio e infiltrazione' license_to_kill false

factory: elite_spy do name 'Edward Donne' license_to_kill true end end 

fine

top_spy = create (: elite_spy) top_spy.email = generate (: spy_email)

top_spy.email # => "[email protected]" "

Poiché questa sequenza è definita "globalmente" per tutte le tue fabbriche, non appartiene a una fabbrica specifica, puoi utilizzare questo generatore di sequenze per tutte le tue fabbriche ovunque ti serva : spy_email. Tutto quello che serve è creare e il nome della sequenza.

attributi

Come una piccola variazione che è super conveniente, ho intenzione di mostrarvi come assegnare direttamente le sequenze come attributi alle vostre fabbriche. Utilizzare la stessa condizione di cui sopra, in cui la sequenza è definita "globalmente". Nel caso di una definizione di fabbrica puoi lasciare fuori creare chiamata al metodo e Factory Girl assegnerà il valore restituito dalla sequenza direttamente all'attributo con lo stesso nome. pulito!

"rubino FactoryGirl.define do

sequenza: email do | n | "00#[email protected]" fine

factory: secret_service_agent do name E-mail 'Edwad Donne' Spionaggio e infiltrazione 'license_to_kill true end

fine"

Attributi pigri

Puoi anche usare a sequenza sopra attributi pigri. Tratterò questo argomento nel mio secondo articolo, ma per completezza volevo menzionarlo anche qui. Nel caso in cui sia necessario un valore univoco assegnato nel momento in cui l'istanza viene creata, viene chiamata pigro perché questo attributo attende con l'assegnazione del valore fino a quando le sequenze di istanziazione degli oggetti richiedono solo un blocco per funzionare.

"rubino

FactoryGirl.define do

sequenza: mission_deployment do | number | "Missione # numero in # DateTime.now.to_formatted_s (: short)" fine

factory: spy do name 'deployment Marty McSpy' generate (: mission_deployment) fine

fine

some_spy = create (: spy) some_spy.deployment # => "Missione 1 alle 19 ott 21:13" "

Il blocco per l'attributo factory viene valutato quando l'oggetto viene istanziato. Nel nostro caso, otterrai una stringa composta da un numero di missione univoco e un nuovo Appuntamento oggetto come valori per ogni :spiare che viene schierato.

Sequenza in linea

Questa opzione è la migliore quando una sequenza di valori univoci è necessaria solo su un attributo per una singola fabbrica. Non avrebbe senso definirlo al di fuori di quella fabbrica e forse dovresti cercarlo altrove se hai bisogno di modificarlo. Nell'esempio in basso stiamo cercando di generare id unici per ogni auto spia.

"rubino FactoryGirl.define do

factory: aston_martin do sequence (: vehicle_id_number) | n | "A_M _ # n" fine

fine

spycar_01 = create (: aston_martin) spycar_01.vehicle_id_number # => "A_M_1"

spycar_02 = create (: aston_martin) spycar_02.vehicle_id_number # => "A_M_2" "

Bene, forse dovremmo fornire il vehicle_id_number attribuisci un altro valore iniziale di 1? Diciamo che vogliamo rendere conto di un paio di prototipi prima che l'auto fosse pronta per la produzione. È possibile fornire un secondo argomento come valore iniziale per la sequenza. Andiamo con 9 questa volta.

"rubino FactoryGirl.define do

factory: aston_martin do sequence (: vehicle_id_number, 9) | n | "A_M _ # n" fine

fine

spycar_01 = create (: aston_martin) spycar_01.vehicle_id_number # => "A_M_9"

spycar_02 = create (: aston_martin) spycar_02.vehicle_id_number # => "A_M_10"

...

"

Pensieri di chiusura

Come hai già visto, Factory Girl offre un Ruby DSL ben bilanciato che crea oggetti anziché record di database per i tuoi dati di test. Aiuta a mantenere i test concentrati, ASCIUTTI e leggibili quando gestisci dati fittizi. Questo è un risultato abbastanza solido nel mio libro.

Ricorda che le definizioni di fabbrica bare-bones sono fondamentali per la tua sanità mentale futura. Maggiore è il numero di dati di fabbrica che metti nel tuo spazio di test globale, maggiore è la probabilità che tu possa sperimentare una sorta di dolore di manutenzione.

Per i tuoi test unitari, Factory Girl non sarà necessaria e rallenterà solo la tua suite di test. Josh Clayton sarebbe il primo ad attestarlo e raccomanderebbe la sua migliore pratica per utilizzare Factory Girl in modo selettivo e il meno possibile.