returnerat objekt

Den här tvådelade mini-serien skrevs för personer som vill hoppa in i arbetet med Factory Girl och skära i jakten utan att gräva igenom själva dokumentationen för mycket. För folk som började spela med test ganska nyligen gjorde jag mitt bästa för att hålla det nybörjevänliga.

Självklart har jag på samma gång varit i samma skor och jag tror att det inte tar mycket för att få nya människor att känna sig mer bekväma med testningen. Att ta lite mer tid att förklara kontextet och demystifiera lingo går långt i att minska frustrationshastigheten för nybörjare.

Innehåll

  • Intro och kontext
  • fixturer
  • Konfiguration
  • Definiera fabriker
  • Använda fabriker
  • Arv
  • Flera poster
  • sekvenser

Intro och kontext

Låt oss börja med lite historia och prata om de fina folket på thoughtbot som är ansvariga för den här populära Ruby-pärlan. Tillbaka 2007/2008 Joe Ferris, CTO på thoughtbot, hade haft det med armaturer och började laga sin egen lösning. Att gå igenom olika filer för att testa en enda metod var en vanlig smärtpunkt vid hanteringen av armaturer. Sätt annorlunda och ignorera alla typer av inflexibiliteter, den övningen leder också till skrivprov som inte berättar mycket om att deras sammanhang testas direkt.

Att inte säljas på den nuvarande praxisen fick honom att kolla olika lösningar för fabriker, men ingen av dem stödde allt han ville ha. Så han kom fram med Factory Girl, som gjorde testning med testdata mer läsbar, DRY och också mer explicit genom att ge dig sammanhanget för varje test. Några år senare tog Josh Clayton, utvecklingsdirektör på thoughtbot i Boston, över som projektansvarig. Med tiden har denna pärla ökat stadigt och blivit en "fixtur" i Ruby-samhället.

Låt oss prata mer om de viktigaste smärtpunkterna Factory Girl löser. När du bygger din testpaket handlar du om många tillhörande poster och med information som ändras ofta. Du vill kunna bygga dataset för dina integrationsprov som inte är sköra, lätt att hantera och tydliga. Dina datafabriker ska vara dynamiska och kunna hänvisa till andra fabriker - något som delvis ligger utanför YAML-armaturer från gamla tider.

En annan bekvämlighet du vill ha är förmågan att skriva över attribut för objekt i fluga. Factory Girl gör det möjligt för dig att göra allt så enkelt - med tanke på att det är skrivet i Ruby och en hel del metaprogrammering häxverk pågår bakom kulisserna - och du har ett bra domänspecifikt språk som är lätt på ögonen för.

Uppbyggnaden av din fixturdata med denna pärla kan beskrivas som enkel, effektiv och övergripande bekvämare än att fitta med fixturer. På det sättet kan du hantera mer med begrepp än med de faktiska kolumnerna i databasen. Men nog att prata pratningen, låt oss få händerna lite smutsiga.

fixturer?

Folk som har erfarenhet av att testa applikationer och som inte behöver lära sig begreppet fixturer, var snäll och hoppa direkt fram till nästa avsnitt. Den här är för nybörjare som bara behöver en uppdatering om testning av data.

Fixtures är provdata-det är det verkligen! För en bra bit av din testpaket vill du kunna fylla din testdatabas med data som är skräddarsydd för dina specifika testfall. För ett tag använde många devs YAML för denna data, vilket gjorde det databasoberoende. I efterhand kan det vara det bästa med det att vara självständigt på det sättet. Det var mer eller mindre en fil per modell. Det ensamma kan ge dig en uppfattning om alla typer av huvudvärk som folk klagade över. Komplexiteten är en snabbt växande fiende som YAML knappast är utrustad med att ta på sig effektivt. Nedan ser du vad en sådan .YML filen med testdata ser ut.

YAML-fil: secret_service.yml

"vanlig Quartermaster: namn: Q favorite_gadget: Broom radio färdigheter: Uppfinning gizmos och hacking

00 Agent: namn: James Bond favorite_gadget: Submarine Lotus Esprit-färdigheter: Att få Bond Girls dödade och dolda infiltration "

Det ser ut som en hash, eller hur? Det är en kolon-separerad lista över nyckel / värdepar som är åtskilda av ett tomt utrymme. Du kan referera till andra noder inom varandra om du vill simulera föreningar från dina modeller. Men jag tycker att det är rättvist att säga att det är där musiken slutar och många säger att deras smärta börjar. För dataset som är lite mer involverade är YAML-armaturer svåra att underhålla och svår att förändra utan att påverka andra test. Du kan ju få dem att fungera, förstås, utvecklare använde dem gott i det förflutna, men många kom överens om att priset för att betala för att hantera armaturer var bara lite för högt.

För att undvika att bryta dina testdata när de oundvikliga förändringarna inträffar var utvecklarna glada att anta nya strategier som gav mer flexibilitet och dynamiskt beteende. Det var där Factory Girl kom in och lämnade YAML-dagarna bakom. En annan fråga är det stora beroendet mellan testet och .YML fixturfilen. Mystiska gäster är också en stor smärta med dessa typer av armaturer. Factory Girl låter dig undvika det genom att skapa objekt som är relevanta för testen inline.

Visst, YAML-armaturer är snabba, och jag har hört folk argumentera för att en långsam testpaket med Factory Girl-data gjorde att de gick tillbaka till YAML-land. Om du använder Factory Girl så mycket att det verkligen saktar ner dina tester, kanske du överdriver fabriker i onödan och du kanske ignorerar strategier som inte slår på databasen. Du ser vad jag menar när vi kommer till relevanta avsnitt. Naturligtvis, använd vad du behöver, men överväga dig själv om du blir bränd av YAML.

Jag tror att det skulle vara rättvist att lägga till i de tidiga dagarna av Rails och Ruby TDD var YAML fixtures den faktiska standarden för att ställa in testdata i din ansökan. De spelade en viktig roll och bidrog till att flytta branschen framåt. Numera har de en ganska dålig rep. Tiderna förändras, så låt oss gå vidare till fabriker som är avsedda att ersätta armaturer.

Konfiguration

Jag antar att du redan har Ruby och RSpec för testning installerad på ditt system. Om inte, kom tillbaka efter att ha hört Google och du borde vara bra att gå. Det är ganska enkelt, skulle jag säga. Du kan installera juvelen manuellt i din terminal via Skal:

bash gem installation factory_girl

eller lägg till den till din Gemfile:

rubin pärla "factory_girl", "~> 4.0" och springa buntinstallation.

Nu behöver du bara fordra Factory Girl för att slutföra din installation. Här använder jag RSpec, så lägg till följande på toppen in /spec/spec_helper.rb:

rubin kräver 'factory_girl'

Ruby on Rails

Du är naturligtvis täckt om du vill använda Factory Girl med Rails. De factory_girl_rails pärla ger en praktisk Rails integration för factory_girl.

Skal:

bash gem installation factory_girl_rails

Gemfile:

rubin pärla "factory_girl_rails", "~> 4.0"

och fordra det självklart i spec / spec_helper.rb:

rubin kräver "factory_girl_rails"

Praktisk syntaxinställning

Om du föredrar att skriva något som (självklart gör du)

Rubin:

rubin skapa (: användare)

istället för

rubin FactoryGirl.create (: användare)

varje gång du använder en av dina fabriker behöver du bara inkludera FactoryGirl :: Syntax :: Metoder modul i din testkonfigurationsfil. Om du glömmer det steget måste du förorda alla Factory Girl-metoder med samma verbala förord. Det här fungerar med alla Ruby app, inte bara med Rails.

För RSpec hitta spec / spec_helper.rb fil och lägg till:

"rubin kräver" factory_girl_rails "

RSpec.configure do | config | config.include FactoryGirl :: Syntax :: Metoder slutar "

Uppmärksamhet!

För nybörjare bland er, akta dig för att RSpec.configure block kommer redan att vara där-begravd under en del kommentarer. Du kan också göra samma inställning i en separat fil av din egen spec / support / factory_girl.rb. I det fallet måste du självklart lägga till hela konfigurationsblocket.

Samma konfiguration fungerar om du använder andra bibliotek för testning:

  • Test :: Unit
  • Gurka
  • Spenat
  • MINITEST
  • MINITEST :: Spec
  • MINITEST-skenor

Du kan gå mer fint med din konfiguration genom att kasta in DatabaseCleaner, till exempel, men dokumentationen innebär att denna inställning är tillräcklig för att komma igång, så jag går vidare härifrån.

Definiera fabriker

Du kan definiera dina fabriker var som helst, men de laddas automatiskt om de placeras på följande platser:

RSpec:

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

Test :: Enhet:

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

Som du kan se har du möjlighet att dela upp dem i separata filer som följer vad som helst logik eller buntar dina fabriker tillsammans till en stor factories.rb fil. Komplexiteten i ditt projekt kommer att vara den bästa riktlinjen för när man logiskt separerar fabriker i sina egna separata filer.

Barebenfabriker

Factory Girl tillhandahåller en välutvecklad ruby ​​DSL-syntax för att definiera fabriker som användare, posta eller något annat objekt - inte bara Aktiv inspelning objekt. "Plain" Ruby klasser är helt bra. Du börjar med att skapa ett definieringsblock i din factories.rb fil.

Rubin:

"Ruby FactoryGirl.define gör

slutet"

Alla fabriker är definierade inom detta block. Fabriker behöver bara en :symbol namn och en uppsättning attribut för att komma igång. Detta namn måste vara snake_cased version av modellen du vill emulera - som SecretServiceAgent i följande exempel. Fabriken nedan heter secret_service_agent och har attribut som heter namn, favorite_gadget och Kompetens.

Rubin:

"Ruby FactoryGirl.define gör

fabriks: secret_service_agent namn "Q" favorite_gadget "Submarine Lotus Esprit" färdigheter "Uppfinna gizmos och hacking" slutet

slutet"

Uppmärksamhet!

Om du tar en sak bort från den här artikeln bör den vara följande: Definiera bara den mest nakna ben-fabriken som är möjlig för att vara giltig som standard - giltig i enlighet med Active Record-valideringar, till exempel.

När Factory Girl ringer spara! I vissa fall kommer dina valideringar att utövas. Om någon av dem misslyckas, Active :: RecordInvalid blir uppvuxen. Att definiera endast det minsta läget ger dig större flexibilitet om dina data ändras och minskar risken att bryta test och dubbelarbete eftersom kopplingen sänks till kärnan. Var inte lat när du komponerar dina fabriker - den kommer att betala av stor tid!

Om du tror att det här låter svårt att hantera, kommer du med största sannolikhet att vara glad att höra att det finns praktiska lösningar på plats för att segregera objekt och deras attribut. Arv och egenskaper kommer att bli stora allierade eftersom de är praktiska strategier för att komplettera dina barebenfabriker och hålla dem torka samtidigt. Läs mer om Arv nedan, och min andra artikel kommer att fokusera ganska lite på Egenskaper.

Använda fabriker

Om du har inkluderat FactoryGirl :: Syntax :: Metoder I konfigurationssteget kan du använda korttonsyntaxen för att skapa fabriker i dina test. Du har fyra alternativ, vilka personer som ringer bygga strategier:

skapa

rubin skapa (: some_object) # FactoryGirl.create (: some_object)

Den här återkommer en förekomst av klassen som fabriken emulerar. Det rekommenderas att använda skapa bara när du verkligen behöver träffa databasen. Denna strategi saktar ner din testpaket om det inte behövs för mycket. Använd det om du vill köra dina valideringar eftersom det kommer att köras spara! i bakgrunden. Jag tycker att det här alternativet är mest lämpligt när du gör integrationstester där du vill involvera en databas för dina testscenarier.

bygga

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

Det instantiates och tilldelar attribut, men du får en instans som inte sparas i databasen-bygga håller objektet bara i minnet. Om du vill kontrollera om ett objekt har vissa attribut, kommer det att göra jobbet, eftersom du inte behöver databasåtkomst för den typen av saker. Bakom kulisserna använder den inte spara!, vilket innebär att dina valideringar inte körs.

Se upp!

När du använder föreningar med det kan du komma in i en liten gotcha. Det finns ett litet undantag när det gäller att inte spara objektet via bygga-Det bygger objektet men det skapar de föreningar som jag kommer att täcka i avsnittet om Factory Girl-föreningar. Det finns en lösning för det om det inte är vad du handlade för.

build_stubbed

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

Det här alternativet skapades för att påskynda dina test och för testfall där ingen kod behöver träffa databasen. Det instantierar och tilldelar också attribut som bygga gör det, men det gör bara saker som ser ut som om de har varit kvar. Objektet som returneras har alla de definierade attributen från din fabrik stubbed out-plus en falsk id och noll tidsstämplar. Deras föreningar stubbas ut lika bra till skillnad från varandra bygga föreningar, som använder skapa på associerade objekt. Eftersom denna strategi handlar om stubbar och inte har något beroende av databasen, kommer dessa tester att bli så snabba som de får.

attributes_for

Denna metod kommer att returnera en hash av endast de attribut som definieras i den relevanta fabriken utan associationer, tidsstämplar och id naturligtvis. Det är praktiskt om du vill bygga en förekomst av ett objekt utan att fitta runt med attributkassar manuellt. Jag har sett det mest använda i Controller-specifikationer som liknar detta:

Rubin:

"ruby it" omdirigerar till någon plats "gör post: skapa, spion: attributes_for (: spy)

förvänta (svar) .om omdirigering till (some_location) avsluta "

Objektjämförelse

För att stänga det här avsnittet, låt mig ge dig ett enkelt exempel för att se de olika objekten som returneras från dessa fyra byggstrategier. Nedan kan du jämföra de fyra olika objekten som du kommer att få från attributes_for, skapa, bygga och build_stubbed:

"Ruby FactoryGirl.define gör

fabriks: spionera namnet "Marty McSpy" favorite_gadget "Hoverboard" färdigheter "Infiltration och spionage" slutet

slutet"

"ruby attributes_for (: spy)

returnerat objekt

"

"rubin skapa (: spion)

returnerat objekt

"

"ruby build (: spy)

returnerat objekt

"

"ruby build_stubbed (: spy)

returnerat objekt

"

Jag hoppas att det var till hjälp om du fortfarande hade någon förvirring kvar om hur de fungerar och när du ska använda vilket alternativ.

Arv

Du kommer att älska den här! Med arv kan du bara definiera fabriker med de nödvändiga attribut som varje klass behöver för skapandet. Denna förälderfabrik kan koka så många "barn" fabriker som du tycker är lämplig för att täcka alla slags testscenarier med olika dataset. Att hålla dina testdata DRY är mycket viktigt, och arv gör det mycket lättare för dig.

Säg att du vill definiera några kärnattribut på en fabrik och inom samma fabrik har olika fabriker för samma Klass med olika attribut. I exemplet nedan kan du se hur du kan undvika att upprepa attribut genom att bara nesta dina fabriker. Låt oss efterlikna en Spionera klass som behöver anpassas till tre olika testscenarier.

factories.rb

"Ruby FactoryGirl.define gör

Fabrik: Spionera namnet "Marty McSpy" Licence_to_Kill Falska färdigheter "Spionage och intelligens"

fabrik: kvartmästare gör namn "Q" färdigheter "Uppfinning av gizmoer och hacking" slutfabrik: Obligation gör namn "James Bond" licens_to_kill sanna ändänden 

slutet"

some_spec.rb

ruby bond = skapa (: bond) quartermaster = skapa (: quartermaster)

quartermaster.name # => 'Q' quartermaster.skills # => 'Uppfinning av gizmos och hacking' quartermaster.licence_to_kill # => false

bond.name # => 'James Bond' bond.skills # => 'Spionage och intelligens' bond.licence_to_kill # => true "

Som du kan observera, :obligation och :styrman fabrikerna ärver attribut från deras förälder :spionera. Samtidigt kan du enkelt skriva över attribut efter behov och vara mycket uttrycksfull över det via deras fabriksnamn. Föreställ dig alla rader med kod sparad eftersom du inte behöver upprepa samma grundläggande inställning om du vill testa olika stater eller relaterade objekt. Den egenskapen ensam är värt att använda Factory Girl och gör det svårt att gå tillbaka till YAML fixtures.

Om du vill undvika att fästa fabriksdefinitioner kan du också länka fabriker till deras förälder uttryckligen genom att tillhandahålla en förälder hash:

factories.rb

"Ruby Fabrik: Spionera namnet" Marty McSpy "Licence_to_Kill Falska färdigheter" Spionage och intelligens "slutet

fabrik: obligation, förälder:: spionera namnet "James Bond" licence_to_kill true end "

Det är samma funktionalitet och nästan lika torr.

Flera poster

Här är ett litet men ändå välkommet tillägg till Factory Girl som gör att man enkelt hanterar listor som paj:

  • build_list
  • create_list

Varje gång ibland, i situationer där du vill ha flera instanser av någon fabrik utan mycket fuzz kommer dessa att vara till nytta. Båda metoderna kommer att returnera en array med mängden fabriksobjekt som anges. Ganska snyggt rätt?

Rubin:

"ruby spy_clones = create_list (: spy, 3)

fake_spies = build_list (: spy, 3)

spy_clones # => [#, #, #]

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

Det finns subtila skillnader mellan de två alternativen, men jag är säker på att du förstår dem nu. Jag bör också nämna att du kan ge båda metoderna en hack av attribut om du vill skriva över fabriksattribut i flygningen av någon anledning. Överskrifterna kommer att äta lite av din testhastighet om du skapar mycket testdata med överskrifter. Förmodligen att ha en separat fabrik med dessa attribut ändras är ett bättre alternativ för att skapa listor.

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

double_agents = build_list (: spy, 3, namn: 'Vesper Lynd', färdigheter: 'Seduction and bookkeeping')

smug_spies # => [#, #, #]

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

Du behöver ofta ett par objekt, så Factory Girl ger dig ytterligare två alternativ:

  • build_pair
  • create_pair

Det är samma idé som ovan, men den returnerade matrisen rymmer bara två poster i taget.

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

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

sekvenser

Om du trodde namnet spioner kan vara mindre statisk har du helt rätt. I det här sista avsnittet ser vi på att skapa sekventiella unika värden som testdata för dina fabriksattribut.

När kan det vara användbart? Vad sägs om unika e-postadresser för att testa autentisering eller unika användarnamn till exempel? Det är där sekvenser lyser. Låt oss titta på ett par olika sätt du kan använda sekvens:

"Global" -sekvensen

"Ruby FactoryGirl.define gör

sekvens: spy_email do | n | "00 #[email protected]" slut

fabrik: spionera namnet "Marty McSpy" email "[email protected]" färdigheter "Spionage och infiltration" license_to_kill false

fabriks: elite_spy heter "Edward Donne" licens_to_kill true end-end 

slutet

top_spy = skapa (: elite_spy) top_spy.email = generera (: spy_email)

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

Eftersom denna sekvens är definierad globalt för alla dina fabriker, hör den inte till en specifik fabrik. Du kan använda denna sekvensgenerator för alla dina fabriker var du än behöver en : spy_email. Allt du behöver är generera och namnet på din sekvens.

attribut

Som en liten variant som är super bekvämt, kommer jag att visa dig hur du direkt tilldelar sekvenser som attribut till dina fabriker. Använd samma villkor som ovan, där din sekvens är definierad "globalt". Vid fabriksdefinition kan du lämna av generera Metodsamtal och Factory Girl kommer att tilldela det returnerade värdet från sekvensen direkt till attributet med samma namn. Propert!

"Ruby FactoryGirl.define gör

sekvens: email do | n | "00 #[email protected]" slut

fabriks: secret_service_agent namn "Edwad Donne" e-mail kompetens "Spionage och infiltration" licens_to_kill true end

slutet"

Lata attribut

Du kan också använda a sekvenslat egenskaper. Jag kommer att täcka detta ämne i min andra artikel, men för fullständighetens skull vill jag också nämna det här också. Om du behöver ett unikt värde som är tilldelat vid den tid som förekomsten skapas, kallas den lat eftersom det här attributet väntar med värdetilldelning tills objektinställnings-sekvenser bara behöver ett block att fungera också.

"rubin

FactoryGirl.define gör

sekvens: mission_deployment do | number | "Uppdrag # nummer vid # DateTime.now.to_formatted_s (: short)" avsluta

fabriks: spionera namnet "Marty McSpy" -utplacering generera (: mission_deployment) slutet

slutet

some_spy = create (: spy) some_spy.deployment # => "Uppdrag # 1 vid 19 okt 21:13" "

Blocket för fabriksattributet blir utvärderat när objektet blir instantierat. I vårt fall får du en sträng som består av ett unikt uppdragsnummer och ett nytt Datum Tid objekt som värden för varje :spionera det blir deployerat.

In-line-sekvens

Det här alternativet är bäst när en sekvens av unika värden bara behövs på ett attribut för en enda fabrik. Det skulle ingen mening att definiera det utanför den fabriken och kanske behöva leta efter det på andra håll om du behöver tweak det. I exemplet nedan söker vi att skapa unika ids för varje spionbil.

"Ruby FactoryGirl.define gör

fabriks: aston_martin gör sekvens (: vehicle_id_number) | n | "A_M _ # n" slutet

slutet

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

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

Tja, kanske borde vi ge vehicle_id_number ange ett annat startvärde än 1? Låt oss säga att vi vill redogöra för ett par prototyper innan bilen var klar för produktion. Du kan ge ett andra argument som startvärde för din sekvens. Låt oss gå med 9 den här gången.

"Ruby FactoryGirl.define gör

fabriks: aston_martin gör sekvens (: vehicle_id_number, 9) | n | "A_M _ # n" slutet

slutet

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

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

...

"

Slutsatser

Som du har sett nu erbjuder Factory Girl en välbalanserad Ruby DSL som bygger objekt istället för databasrekord för dina testdata. Det hjälper till att hålla dina test inriktade, torka och läsliga när du hanterar dummy data. Det är en ganska solid prestation i min bok.

Kom ihåg att barebenets fabriksdefinitioner är nyckeln till din framtida sanity. Ju mer fabriksdata du lägger i ditt globala testutrymme, desto mer sannolikt kommer du att uppleva någon form av underhållssmärta.

För din enhetstester kommer Factory Girl att vara onödigt och saktar bara ner din testpaket. Josh Clayton skulle vara den första som intygar detta och skulle rekommendera det bästa sättet att använda Factory Girl selektivt och så lite som möjligt.