returnert objekt

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.

innhold

  • Intro og kontekst
  • inventar
  • konfigurasjon
  • Definere fabrikker
  • Bruke fabrikker
  • Arv
  • Flere poster
  • sekvenser

Intro og kontekst

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.

inventar?

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.

konfigurasjon

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'

Ruby on Rails

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'

Praktisk syntaksoppsett

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 »

Oppmerksomhet!

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:

  • Test :: Unit
  • Agurk
  • Spinat
  • MiniTest
  • MiniTest :: Spec
  • minitest-skinnene

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.

Definere fabrikker

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.

Bare-Bones Fabrikker

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"

Oppmerksomhet!

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.

Bruke fabrikker

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:

skape

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.

bygge

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.

Heads Up!

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.

build_stubbed

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.

attributes_for

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 "

Objektjämförelse

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)

returnert objekt

"

"Ruby create (: spy)

returnert objekt

"

"ruby build (: spion)

returnert objekt

"

"ruby build_stubbed (: spion)

returnert objekt

"

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.

Arv

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.

Flere poster

Her er en liten, men likevel velkommen tillegg til Factory Girl som gjør håndteringen lister lett som kake:

  • build_list
  • create_list

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:

  • build_pair
  • create_pair

Det er den samme ideen som ovenfor, men den returnerte gruppen inneholder bare to poster om gangen.

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

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

sekvenser

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:

"Global" 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.

Egenskaper

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"

Lazy Attributes

Du kan også bruke a sekvenslat 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.

In-line sekvens

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"

...

"

Avsluttende tanker

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.