Denne todelt mini-serien ble skrevet for folk som ønsker å hoppe inn i arbeid med Factory Girl og kutte til jakten uten å grave gjennom dokumentasjonen for seg selv for mye. For folk som begynte å spille med tester ganske nylig, gjorde jeg mitt beste for å holde det nybegynnervennlig.
Åpenbart, etter å ha vært i samme sko på et tidspunkt, fikk jeg tro på at det ikke tar mye for å få nye mennesker til å føle seg mer trygge med testing. Tar litt mer tid til å forklare konteksten og demystifisere lingo går langt i å kutte ned frustrasjonsraten for nybegynnere.
La oss starte med litt historie og snakke om de fine folkene på thoughtbot som er ansvarlige for denne populære Ruby-perlen. Tilbake i 2007/2008 hadde Joe Ferris, CTO på thoughtbot, hatt det med inventar og begynte å lage sin egen løsning. Å gå gjennom ulike filer for å teste en enkelt metode var et vanlig smertepunkt når man håndterte armaturer. Sett annerledes og ignorerer alle slags inflexibilities, den øvelsen fører også til skrive tester som ikke forteller deg mye om at konteksten blir testet med en gang.
Ikke blir solgt på den nåværende øvelsen gjorde ham til å sjekke ut ulike løsninger for fabrikker, men ingen av dem støttet alt han ønsket. Så han kom opp med Factory Girl, som gjorde testing med testdata mer lesbar, tørr og også mer eksplisitt ved å gi deg konteksten for hver test. Et par år senere overtok Josh Clayton, utviklingsdirektør ved tankegangen i Boston, som vedlikeholder av prosjektet. Over tid har denne perlen vokst jevnt og blitt en "fixture" i Ruby-samfunnet.
La oss snakke mer om hovedpinepunktene Factory Girl løser. Når du bygger testpakken, har du å gjøre med mange tilknyttede poster og med informasjon som endrer seg ofte. Du vil kunne bygge datasett for integreringstester som ikke er sprø, lett å håndtere og eksplisitte. Datafabrikkene dine bør være dynamiske og kunne referere til andre fabrikker - noe som delvis er utenfor YAML-inventar fra gamle dager.
En annen bekvemmelighet du vil ha, er muligheten til å overskrive attributter for objekter på fluen. Factory Girl lar deg gjøre alt det uanstrengt - gitt at det er skrevet i Ruby, og mye av metaprogrammering hekseri foregår bak kulissene - og du er utstyrt med et stort domenespesifikt språk som er lett på øynene også.
Oppbygging av fixture data med denne perlen kan beskrives som enkelt, effektivt og generelt mer praktisk enn å feste med armaturer. På den måten kan du håndtere mer med begreper enn med de faktiske kolonnene i databasen. Men nok til å snakke snakket, la oss få hendene litt skitne.
Folk som har erfaring med testing av applikasjoner og som ikke trenger å lære om konseptet med inventar, vær så snill å hoppe rett fram til neste avsnitt. Denne er for nybegynnere som bare trenger en oppdatering om testing av data.
Fixtures er prøve data-det er det egentlig! For en god del av testpakken din, vil du kunne fylle testdatabasen med data som er skreddersydd for dine spesifikke testtilfeller. For en stund brukte mange devs YAML for disse dataene, noe som gjorde det uavhengig av databasen. Etterpå, å være uavhengig på den måten, kan ha vært det beste med det. Det var mer eller mindre en fil per modell. Det alene kan gi deg en ide om alle slags hodepine folk klagde over. Kompleksitet er en raskt voksende fiende som YAML er knapt rustet til å ta på seg effektivt. Nedenfor ser du hva en slik .YML
filen med testdata ser ut.
YAML-fil: secret_service.yml
"vanlig Quartermaster: navn: Q favorite_gadget: Broom radio ferdigheter: Oppfinne gizmoer og hacking
00 Agent: navn: James Bond favorite_gadget: Submarine Lotus Esprit ferdigheter: Få Bond Girls dræpt og skjult infiltrering "
Det ser ut som en hash, ikke sant? Det er en kolon-separert liste over nøkkel / verdi-par som er adskilt av et tomt mellomrom. Du kan referere til andre noder i hverandre hvis du vil simulere foreninger fra modellene dine. Men jeg synes det er rimelig å si at det er der musikken stopper og mange sier at deres smerte begynner. For datasett som er litt mer involvert, er YAML-armaturer vanskelige å vedlikeholde og vanskelig å forandre uten å påvirke andre tester. Du kan selvfølgelig få dem til å fungere, selvfølgelig, utviklere brukte dem mye i fortiden - men mange var enige om at prisen for å betale for å håndtere inventar var litt for høyt.
For å unngå å bryte testdataene dine når de uunngåelige endringene oppstår, var utviklerne glade for å vedta nyere strategier som ga mer fleksibilitet og dynamisk oppførsel. Det var der Factory Girl kom inn og forlot YAML-dagene bak. Et annet problem er den sterke avhengigheten mellom testen og .YML
fixturfil. Mystiske gjester er også en stor smerte med disse typer inventar. Factory Girl lar deg unngå det ved å lage objekter som er relevante for testene inline.
Jo, YAML fixtures er raske, og jeg har hørt folk hevder at en treg test suite med Factory Girl data gjorde at de gikk tilbake til YAML land. For meg, hvis du bruker Factory Girl så mye at det virkelig senker tester, kan du overutnyttelse av fabrikker unødvendig, og du kan ignorere strategier som ikke rammer databasen. Du vil se hva jeg mener når vi kommer til den relevante delen (e). Bruk selvsagt hva du trenger, men vær så snill å advare om du blir brent av YAML.
Jeg synes det ville være rimelig å legge til at i de tidlige dagene av Rails og Ruby TDD var YAML fixtures de facto-standarden for å sette opp testdata i søknaden din. De spilte en viktig rolle og bidro til å flytte bransjen fremover. I dag har de en rimelig dårlig representant. Tider endres, så la oss gå videre til fabrikker, som er ment å erstatte inventar.
Jeg antar at du allerede har Ruby og RSpec for testing installert på systemet ditt. Hvis ikke, kom tilbake etter å ha konsultert Google, og du bør være god til å gå. Det er ganske greit, jeg vil si. Du kan installere perlen manuelt i terminalen via Shell:
bash gem install factory_girl
eller legg det til din Gemfile:
ruby perle "factory_girl", "~> 4.0"
og løp bunt installasjon
.
Nå trenger du bare å krever
Factory Girl for å fullføre oppsettet ditt. Her bruker jeg RSpec, så legg til følgende øverst i /spec/spec_helper.rb
:
rubin krever 'factory_girl'
Du er selvfølgelig dekket hvis du vil bruke Factory Girl med Rails. De factory_girl_rails
perle gir en praktisk Rails integrasjon for factory_girl
.
Shell:
bash gem install factory_girl_rails
Gemfile:
ruby perle "factory_girl_rails", "~> 4.0"
og krever
det selvfølgelig i spec / spec_helper.rb
:
rubin krever 'factory_girl_rails'
Hvis du foretrekker å skrive noe som (selvfølgelig gjør du det)
Rubin:
rubin lage (: bruker)
i stedet for
rubin FactoryGirl.create (: bruker)
hver gang du bruker en av fabrikkene dine, trenger du bare å inkludere FactoryGirl :: Syntax :: Metoder
modul i testkonfigurasjonsfilen din. Hvis du glemmer det trinnet, må du forord alle Factory Girl-metoder med det samme verbose forordet. Dette fungerer med noen Ruby app, ikke bare med Rails selvfølgelig.
For RSpec finner du spec / spec_helper.rb
fil og legg til:
"Ruby krever 'factory_girl_rails'
RSpec.configure do | config | config.include FactoryGirl :: Syntaks :: Metoder slutt »
For nybegynnere blant dere, vær oppmerksom på at RSpec.configure
blokk vil allerede være der-begravet under noen mengder kommentarer. Du kan også gjøre det samme oppsettet i en egen fil av din egen spec / støtte / factory_girl.rb
. I så fall må du selv legge til hele konfigurasjonsblokken.
Samme konfigurasjon fungerer hvis du bruker andre biblioteker for testing:
Du kan gå mer fancy med konfigurasjonen ved å kaste inn DatabaseCleaner
, for eksempel, men dokumentasjonen innebærer at dette oppsettet er tilstrekkelig for å komme i gang, så jeg fortsetter herfra.
Du kan definere fabrikkene dine hvor som helst, men de lastes automatisk hvis de plasseres på følgende steder:
RSpec:
bash spec / factories.rb spec / fabrikker / * .rb
Test :: Enhet:
bash test / factories.rb test / fabrikker / * .rb
Som du kan se, har du muligheten til å dele dem i separate filer som overholder logikken, eller bunte fabrikkene sammen til en stor factories.rb
fil. Kompleksiteten i prosjektet ditt vil være den beste retningslinjen for når du logisk skal skille fabrikker i egne filer.
Factory Girl tilbyr en velutviklet ruby DSL syntaks for å definere fabrikker som bruker, post eller noe annet objekt - ikke bare Aktiv post objekter. "Vanlige" Ruby klasser er helt greit. Du starter med å sette opp en definerblokk i din factories.rb
fil.
Rubin:
"Ruby FactoryGirl.define gjør
slutt"
Alle fabrikkene er definert i denne blokken. Fabrikkene trenger bare en : symbol
navn og et sett med attributter for å komme i gang. Dette navnet må være snake_cased versjon av modellen du vil etterligne - som SecretServiceAgent i følgende eksempel. Fabrikken nedenfor er oppkalt secret_service_agent
og har attributter kalt Navn
, favorite_gadget
og ferdigheter
.
Rubin:
"Ruby FactoryGirl.define gjør
fabrikk: secret_service_agent gjør navnet "Q" favorite_gadget "Submarine Lotus Esprit" ferdigheter "Oppfinne gizmoer og hacking" slutten
slutt"
Hvis du tar en ting bort fra denne artikkelen, bør det være følgende: Definer bare den mest bare-ben-fabrikken som er mulig for å være gyldig som standard, gyldig i henhold til Active Record-valideringer, for eksempel.
Når Factory Girl ringer lagre!
På tilfeller vil dine valideringer bli utøvet. Hvis noen av dem feiler, Active :: RecordInvalid
blir hevet. Definere bare det minste minimumet gir deg mer fleksibilitet hvis dataene endres og vil redusere sjansene for å bryte tester og duplisering - siden koblingen er redusert til kjernen. Ikke vær lat når du komponerer fabrikkene dine - det vil betale deg for stor tid!
Hvis du tror dette høres vanskelig å administrere, vil du mest sannsynlig være glad for å høre at det finnes praktiske løsninger på plass for å segregere objekter og deres attributter. Arv og trekk vil bli store allierte siden de er praktiske strategier for å utfylle dine bare-beinfabrikker og holde dem tørre samtidig. Lær mer om arv nedenfor, og min andre artikkel vil fokusere litt på egenskaper.
Hvis du har tatt med FactoryGirl :: Syntax :: Metoder
I konfigurasjonstrinnet kan du bruke stenografisyntaxen til å lage fabrikker i testene dine. Du har fire alternativer, hvilke folk ringer bygge strategier:
Ruby create (: some_object) # FactoryGirl.create (: some_object)
Denne returnerer en forekomst av klassen som fabrikken emulerer. Det anbefales å bruke skape
bare når du virkelig trenger å treffe databasen. Denne strategien bremser testpakken din hvis den blir overnyttet unødvendig. Bruk den hvis du vil kjøre dine valideringer siden den vil løpe lagre!
i bakgrunnen. Jeg tror dette alternativet er mest hensiktsmessig når du gjør integreringstester der du vil involvere en database for testscenarier.
ruby build (: some_object) # FactoryGirl.build (: some_object)
Det instantierer og tilordner attributter, men du får tilbake en forekomst som ikke er lagret i databasen-bygge
holder objektet bare i minnet. Hvis du vil kontrollere om et objekt har bestemte attributter, vil dette gjøre jobben, siden du ikke trenger databasetilgang for den typen ting. Bak kulissene bruker den ikke lagre!
, som betyr at valideringene dine ikke kjøres.
Når du bruker foreninger med det, kan du komme inn i en liten gotcha. Det er et lite unntak når det gjelder å ikke lagre objektet via bygge
-den bygger objektet, men det skaper foreningene som jeg vil dekke i avsnittet om Factory Girl-foreninger. Det er en løsning for det dersom det ikke er det du handlet for.
ruby build_stubbed (: some_object) # FactoryGirl.build_stubbed (: some_object)
Dette alternativet ble opprettet for å øke forsøkene dine og for testtilfeller hvor ingen av kodene måtte treffe databasen. Det instanterer og tilordner også attributter som bygge
gjør det, men det gjør bare gjenstander som om de har vært vedvarende. Objektet som returneres har alle de definerte egenskapene fra fabrikken stubbet ut pluss en falsk id
og nil
tidsstempler. Deres foreninger er stubbed ut så godt i motsetning til bygge
foreninger, som bruker skape
på tilknyttede objekter. Siden denne strategien omhandler stubber og har ingen avhengighet av databasen, vil disse testene bli så fort som de får.
Denne metoden vil returnere en hash av bare de attributter som er definert i den aktuelle fabrikken uten foreninger, tidsstempler og id selvfølgelig. Det er praktisk hvis du vil bygge en forekomst av en gjenstand uten å fitte rundt med attributten hashes manuelt. Jeg har sett det mest brukt i Controller-spesifikasjoner som ligner på dette:
Rubin:
"ruby det" omadresserer til noe sted "gjør innlegg: opprett, spion: attributes_for (: spy)
forventer (respons) .for omdirigering til (some_location) avslutte "
For å lukke denne delen, la meg gi deg et enkelt eksempel for å se de forskjellige objektene som er returnert fra disse fire byggestrategiene. Nedenfor kan du sammenligne de fire forskjellige objektene du får fra attributes_for
, skape
, bygge
og build_stubbed
:
"Ruby FactoryGirl.define gjør
Fabrikk: Spion gjør navnet "Marty McSpy" favorite_gadget "Hoverboard" ferdigheter "Infiltrering og spionasje" slutten
slutt"
"ruby attributes_for (: spion)
"
"Ruby create (: spy)
"ruby build (: spion)
"ruby build_stubbed (: spion)
Jeg håper dette var nyttig hvis du fortsatt hadde litt forvirring igjen om hvordan de jobber og når du skal bruke hvilket alternativ.
Du kommer til å elske denne! Med arv kan du bare definere fabrikker med de nødvendige egenskapene som hver klasse trenger for opprettelsen. Denne overordnede fabrikken kan gyte så mange "barn" fabrikker som du ser hensiktsmessig til å dekke alle slags testscenarier med varierende datasett. Å holde testdataene dine DRY er veldig viktig, og arv gjør dette mye lettere for deg.
Si at du vil definere et par kjernegenskaper på en fabrikk og innenfor samme fabrikk har forskjellige fabrikker for det samme Klasse med forskjellige attributter. I eksemplet nedenfor kan du se hvordan du kan unngå å gjenta attributter ved å bare neste fabrikkene dine. La oss etterligne en Spion klasse som trenger å tilpasse seg tre forskjellige testscenarier.
factories.rb
"Ruby FactoryGirl.define gjør
Fabrikk: Spion gjør navn 'Marty McSpy' lisens til å falle ferdigheter 'Spionasje og intelligens'
fabrikk: quartermaster gjør navn 'Q' ferdigheter 'Oppfinning av gizmoer og hacking' sluttfabrikk: Obligasjon gjør navn 'James Bond' lisens_til_kill sann ende
slutt"
some_spec.rb
"ruby bond = create (: bond) quartermaster = opprett (: quartermaster)
quartermaster.name # => 'Q' quartermaster.skills # => 'Oppfinne gizmoer og hacking' quartermaster.licence_to_kill # => false
bond.name # => 'James Bond' bond.skills # => 'Spionasje og intelligens' bond.licence_to_kill # => true "
Som du kan observere, :knytte bånd
og : kvartermester
Fabrikkene arver attributter fra foreldrene sine :spion
. Samtidig kan du enkelt overskrive attributter etter behov og være veldig uttrykksfulle om det via deres fabrikknavn. Tenk på alle kodelinjene som er lagret fordi du ikke trenger å gjenta det samme grunnleggende oppsettet hvis du vil teste forskjellige stater eller relaterte objekter. Denne funksjonen alene er verdt å bruke Factory Girl og gjør det vanskelig å gå tilbake til YAML-armaturer.
Hvis du vil unngå nesting av fabrikkdefinisjoner, kan du også koble fabrikker til foreldrene deres eksplisitt ved å gi en forelder
hash:
factories.rb
"Ruby Fabrikk: Spion Navn" Marty McSpy 'lisens_to_kill Falske ferdigheter' Spionasje og intelligens 'slutt
fabrikk: obligasjon, foreldre:: spion navn 'James Bond' lisens_to_kill sann ende "
Det er samme funksjonalitet og nesten like tørt.
Her er en liten, men likevel velkommen tillegg til Factory Girl som gjør håndteringen lister lett som kake:
Hver gang en gang, i situasjoner der du vil ha flere forekomster av noen fabrikker uten mye fuzz, vil disse komme til nytte. Begge metodene returnerer en matrise med mengden av fabrikkelementene som er angitt. Ganske pent akkurat?
Rubin:
"ruby spy_clones = create_list (: spion, 3)
fake_spies = build_list (: spion, 3)
spy_clones # => [#
fake_spies # => [#
Det er subtile forskjeller mellom de to alternativene, men jeg er sikker på at du forstår dem nå. Jeg bør også nevne at du kan gi begge metodene en hash av attributter hvis du vil overskrive fabrikkattributter på fluen av en eller annen grunn. Overskrivelsene vil spise litt av testhastigheten din hvis du lager masse testdata med overskrifter. Sannsynligvis å ha en egen fabrikk med disse attributene endret, vil være et bedre alternativ for å lage lister.
"ruby smug_spies = create_list (: spion, 3, ferdigheter: 'Smug vitser')
double_agents = build_list (: spion, 3, navn: 'Vesper Lynd', ferdigheter: 'Seduction and bookkeeping')
smug_spies # => [#
double_agents # => [#
Du trenger ofte et par objekter, så Factory Girl gir deg ytterligere to alternativer:
Det er den samme ideen som ovenfor, men den returnerte gruppen inneholder bare to poster om gangen.
"ruby create_pair (: spion) # => [#
build_pair (: spion) # => [#
Hvis du trodde navngi spioner kan være mindre statisk, har du helt rett. I denne siste delen ser vi på å skape sekventielle unike verdier som testdata for fabrikkattributtene dine.
Når kan dette være nyttig? Hva med unike e-postadresser for å teste autentisering eller unike brukernavn for eksempel? Det er her sekvenser skinner. La oss se på et par forskjellige måter du kan bruke på sekvens
:
"Ruby FactoryGirl.define gjør
sekvens: spy_email do | n | "00 #[email protected]" slutten
Fabrikk: Spion gjør navn 'Marty McSpy' email '[email protected]' ferdigheter 'Spionasje og infiltrering' lisens_til_kill falsk
Fabrikk: elite_spy gjør navnet 'Edward Donne' license_to_kill true end-end
slutt
top_spy = opprett (: elite_spy) top_spy.email = generer (: spy_email)
top_spy.email # => "[email protected]" "
Siden denne sekvensen er definert globalt for alle fabrikkene, tilhører den ikke en bestemt fabrikk. Du kan bruke denne sekvensgeneratoren til alle fabrikkene dine hvor som helst du trenger en : spy_email
. Alt du trenger er generere
og navnet på sekvensen din.
Som en liten variant som er super praktisk, skal jeg vise deg hvordan du direkte tilordner sekvenser som attributter til fabrikkene dine. Bruk samme betingelse som ovenfor, der sekvensen din er definert "globalt". I tilfelle av en fabrikkdefinisjon kan du gå av generere
Metode samt Factory Girl vil tildele den returnerte verdien fra sekvensen direkte til attributten med samme navn. Ryddig!
"Ruby FactoryGirl.define gjør
sekvens: email do | n | "00 #[email protected]" slutten
fabrikk: secret_service_agent gjør navnet 'Edwad Donne' e-postkompetanse 'Spionasje og infiltrering' lisens_til_kill sann slutt
slutt"
Du kan også bruke a sekvens
på lat attributter. Jeg vil dekke dette emnet i min andre artikkel, men for fullstendig skyld ønsket jeg å nevne det her også. Hvis du trenger en unik verdi tilordnet når forekomsten blir opprettet, kalles den lat fordi dette attributtet venter med verdioppgave til objektobservasjonssekvenser bare trenger en blokk for å fungere også.
"rubin
FactoryGirl.define gjør
sekvens: mission_deployment do | number | "Mission # number at # DateTime.now.to_formatted_s (: short)" avslutte
Fabrikk: Spion gjør navnet 'Marty McSpy' distribusjon generere (: mission_deployment) slutten
slutt
some_spy = create (: spy) some_spy.deployment # => "Mission # 1 på 19 okt 21:13" "
Blokken for fabrikkattributtet blir evaluert når objektet blir instantiated. I vårt tilfelle får du en streng bestående av et unikt oppdragsnummer og en ny Dato tid
objekt som verdier for hver :spion
det blir deployert.
Dette alternativet er best når en sekvens av unike verdier kun er nødvendig på et attributt for en enkelt fabrikk. Det ville ikke være lurt å definere det utenfor fabrikken og muligens måtte lete etter det andre steder hvis du trenger å finjustere det. I eksemplet nedenfor ser vi til å generere unike ids for hver spionbil.
"Ruby FactoryGirl.define gjør
fabrikk: aston_martin gjøre rekkefølge (: vehicle_id_number) | n | "A_M _ # n" slutten
slutt
spycar_01 = create (: aston_martin) spycar_01.vehicle_id_number # => "A_M_1"
spycar_02 = opprett (: aston_martin) spycar_02.vehicle_id_number # => "A_M_2" "
Vel, kanskje vi burde gi vehicle_id_number
Tilordne en annen startverdi enn 1
? La oss si at vi ønsker å redegjøre for et par prototyper før bilen var klar for produksjon. Du kan gi et andre argument som startverdien for sekvensen. La oss gå med 9
denne gangen.
"Ruby FactoryGirl.define gjør
fabrikk: aston_martin gjør rekkefølge (: vehicle_id_number, 9) | n | "A_M _ # n" slutten
slutt
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"
"
Som du har sett nå, tilbyr Factory Girl en velbalansert Ruby DSL som bygger objekter i stedet for Database Records for testdataene dine. Det bidrar til å holde testene dine fokusert, tørre og lesbare når du håndterer dummy data. Det er en ganske solid prestasjon i boken min.
Husk at bare-bein-fabrikkdefinisjoner er nøkkelen til fremtidig sunnhet. Jo flere fabrikkdata du legger inn i ditt globale testrom, desto mer sannsynlig vil du oppleve noen form for vedlikeholdssmerter.
For enhetstester, vil Factory Girl være unødvendig og bare bremser testpakken din. Josh Clayton ville være den første som attesterer dette og vil anbefale det beste å bruke Factory Girl selektivt og så lite som mulig.