RSpec-test for nybegynnere, del 2

Den andre artikkelen i denne korte serien lærer deg hvordan du bruker forskjellige matchere som følger med RSpec. Det viser deg også hvordan du skal skille testpakken din gjennom tagging, hvordan tilbakeringinger fungerer, og hvordan du trekker ut noen data. Vi utvider litt på det grunnleggende overlevelsessettet fra den første artikkelen, og viser deg nok til å være farlig uten for mye tau for å henge deg selv.

emner

  • matchers
  • La
  • Temaer
  • callbacks
  • generatorer
  • Tags

I den første artikkelen brukte vi ganske mye tid på å prøve å svare på "hvorfor?" Av testing. Jeg foreslår at vi kommer tilbake til "hvordan?" Og sparer oss selv for mer kontekst. Vi dekket den delen allerede mye. La oss se hva annet RSpec har å tilby at du som nybegynner kan håndtere med en gang.

matchers

Så dette kommer til å nærme seg hjertet av ting. RSpec gir deg massevis av såkalte matchere. Dette er ditt brød og smør når du skriver dine forventninger. Så langt har du sett .å eq og .ikke til ekv. Men det er et mye større arsenal å skrive dine spesifikasjoner. Du kan teste for å heve feil, for truthy og falske verdier, eller til og med for bestemte klasser. La oss kjøre ned noen få muligheter for å komme i gang:

  • .å eq
  • .ikke til ekv 

Dette tester for ekvivalens.

Noen Spec

... det er litt smart beskrivelse 'forventer (agent.enemy). Til eq' Ernst Stavro Blofeld 'forventer (agent.enemy) .not_to eq' Winnie Pooh 'end ... 

Oppmerksomhet!

For å holde ting kort, pakker jeg to forutsetninger innen en den blokkere. Det er imidlertid god praksis å teste bare en ting per test. Dette holder ting mye mer fokusert, og testene dine blir mindre sprø når du bytter ting.

  • .å være sjenerøs
  • .å være sant

Noen Spec

... det er litt smart beskrivelse 'forventer (agent.hero?). Å være trofaste forventer (enemy.megalomaniac?). Å være sann ende ... 

Forskjellen er det be_truthy er sant når det ikke er det nil eller falsk. Så det vil passere hvis resultatet ikke er noen av disse to slags "sanne". .å være sant På den annen side aksepterer bare en verdi som er ekte og ingenting annet.

  • .å være falsk
  • .å være falsk

Noen Spec

... det er litt smart beskrivelse 'forventer (agent.coward?). Å være faltig forventer (enemy.megalomaniac?). Å være falsk ende ... 

Ligner på de to eksemplene ovenfor, .å være falsk forventer enten a falsk eller a nil verdi og .å være falsk vil bare gjøre en direkte sammenligning på falsk.

  • .å være
  • .til ikke å være

Og sist men ikke minst, tester dette nøyaktig for nil seg selv. Jeg sparer deg for eksemplet.

  • .å passe()

Jeg håper du allerede hadde gleden av å se på regulære uttrykk. Hvis ikke, er dette en sekvens av tegn som du kan definere et mønster som du legger mellom to fremoverstreker for å søke strenger. En regex kan være veldig nyttig hvis du vil se etter bredere mønstre som du kan generalisere i et slikt uttrykk.

Noen Spec

... det er litt smart beskrivelse 'forventer (agent.number.to_i) .for å matche (/ \ d 3 /) slutt ... 

Anta at vi har å gjøre med agenter som James Bond, 007, som er tildelt tresifrede tall. Da kunne vi selvfølgelig teste for det på denne måten.

  • >
  • <
  • <=
  • > =

Sammenligninger kommer til nytte oftere enn man kanskje tror. Jeg antar at eksemplene nedenfor vil dekke det du trenger å vite.

Noen Spec

... det er litt smart beskrivelse 'gjør ... forventer (agent.number) .for å være < quartermaster.number expect(agent.number).to be > m.number forvente (agent.kill_count) .for å være> = 25 forvente (quartermaster.number_of_gadgets) .for å være <= 5 end… 

Nå får vi et sted mindre kjedelig. Du kan også teste for klasser og typer:

  • .å være_an_instans_of
  • .å være en
  • .å være_an

Noen Spec

... det er litt smart beskrivelse 'do mission = Mission.create (navn:' Moonraker ') agent = Agent.create (navn:' James Bond ') mission.agents << agent expect(@mission.agents).not_to be_an_instance_of(Agent) expect(@mission.agents).to be_a(ActiveRecord::Associations::CollectionProxy) end… 

I dummyeksemplet ovenfor kan du se at en liste over agenter som er tilknyttet et oppdrag, ikke er av klassen Middel men av Active :: foreninger :: CollectionProxy. Det du bør ta bort fra denne er at vi enkelt kan teste for klasser selv mens du holder deg svært uttrykksfulle. .å være en og .å være_an gjør en og samme ting. Du har begge alternativene tilgjengelige for å holde ting lesbare.

Testing for feil er også massivt praktisk i RSpec. Hvis du er super frisk til Rails og ikke sikker på hvilke feil rammen kan kaste på deg, kan du kanskje ikke føle behovet for å bruke disse - selvfølgelig, det gir full mening. På et senere tidspunkt i utviklingen din finner du dem veldig hendig, skjønt. Du har fire måter å håndtere dem:

  • .å raise_error

Dette er den mest generiske måten. Uansett hvilken feil som blir hevet, vil det bli kastet i nettet.

  • .å raise_error (ErrorClass)

På den måten kan du spesifisere nøyaktig hvilken klasse feilen skal komme fra.

  • .å raise_error (ErrorClass, "Noen feilmelding")

Dette er enda mer fint kornet siden du ikke bare nevner feilklassen, men en bestemt melding som skal kastes med feilen.

  • .å raise_error ("Noen feilmelding)

Eller du nevner bare feilmeldingen selv uten feilklassen. Forvent delene må skrives litt annerledes, skjønt - vi må pakke delen under tekst i en kodeblokk selv:

Noen Spec

... det er litt smart beskrivelse 'do agent = Agent.create (navn:' James Bond ') forventer agent.lady_killer?. Å raise_error (NoMethodError) forventer double_agent.name .to raise_error (NameError) forventer double_agent. navn .to raise_error ("Feil: Ingen doble agenter rundt") forventer double_agent.name .to raise_error (NameError, "Error: No double agents around") slutter ... 
  • .til å begynne med
  • .å ende med

Siden vi ofte håndterer samlinger når vi bygger webapps, er det fint å ha et verktøy for å se på dem. Her la vi til to agenter, Q og James Bond, og ville bare vite hvem som kommer først og sist i samlingen av agenter for et bestemt oppdrag - her Moonraker.

Noen Agent Spec

... det er litt smart beskrivelse 'do moonraker = Mission.create (navn:' Moonraker ') bond = Agent.create (navn:' James Bond ') q = Agent.create (navn:' Q ') moonraker.agents << bond moonraker.agents << q expect(moonraker.agents).to start_with(bond) expect(moonraker.agents).to end_with(q) end… 
  • .å inkludere

Denne er også nyttig for å sjekke innholdet i samlinger.

Noen Agent Spec

... det er litt smart beskrivelse 'do mission = Mission.create (navn:' Moonraker ') bond = Agent.create (navn:' James Bond ') mission.agents << bond expect(mission.agents).to include(bond) end… 
  • predikatmatchere

Disse predikatmatchene er en funksjon av RSpec for å dynamisk skape matchere for deg. Hvis du har predikatemetoder i modellene dine, for eksempel (slutter med et spørsmålstegn), vet RSpec at det skal bygge matchere for deg som du kan bruke i tester. I eksemplet nedenfor vil vi teste om en agent er James Bond:

Agent modell

klasse agent < ActiveRecord::Base def bond? name == 'James Bond' && number == '007' && gambler == true end… end

Nå kan vi bruke dette i våre spesifikasjoner som slik:

Noen Agent Spec

... det er litt smart beskrivelse 'do agent = Agent.create (navn:' James Bond ', nummer:' 007 ', gambler: true) expect (agent) .to be_bond avslutte det' litt smart beskrivelse 'gjør agent = Agent. lage (navn: 'James Bond') forvente (agent) .not_to be_bond end ... 

RSpec lar oss bruke metodenavnet uten spørsmålstegn-for å danne en bedre syntaks, antar jeg. Kult, er det ikke?

La

la og la! kan se ut som variabler først, men de er faktisk hjelpemetoder. Den første blir lurt evaluert, noe som betyr at den bare kjøres og evalueres når en spesifikasjon faktisk bruker den, og den andre lar med bang (!) Kjøres uavhengig av bruk av en spesifikasjon eller ikke. Begge versjonene blir memoized, og deres verdier blir cached i samme eksempelområde.

Noen spesifikke filer

beskrive Mission, '#prepare',: la la være (: oppdrag) Mission.create (navn: 'Moonraker') la! (: bond) Agent.create (navn: 'James Bond') agenter til et oppdrag ", gjør mission.prepare (obligasjon) forventer (oppdrag. agenter) .for å inkludere obligasjonsendets ende

Bremseversjonen som ikke vurderes lurt, kan være tidkrevende og derfor kostbar hvis det blir din fancy nye venn. Hvorfor? Fordi det vil sette opp disse dataene for hver test i spørsmålet, uansett hva, og kan til slutt ende opp med å bli en av disse ekkel ting som senker testserien din betydelig.

Du bør vite denne funksjonen til RSpec siden la er allment kjent og brukt. Når det blir sagt, vil neste artikkel vise deg noen problemer med det du bør være oppmerksom på. Bruk disse hjelpemidlene med forsiktighet, eller i det minste i små doser for nå.

Temaer

RSpec gir deg muligheten til å erklære motivet under test veldig eksplisitt. Det finnes bedre løsninger for dette, og vi vil diskutere nedgangene i denne tilnærmingen i neste artikkel når jeg viser noen ting du vanligvis vil unngå. Men for nå, la oss ta en titt på hva Emne kan gjøre for deg:

Noen spesifikke filer

Beskriv agent, '#status' gjør emne Agent.create (navn: 'Bond') det returnerer agenten status 'forventer (subject.status) .not_to være' MIA 'ende

Denne tilnærmingen kan på den ene side hjelpe deg med å redusere kod duplisering, ha en hovedpersonen erklært en gang i et bestemt omfang, men det kan også føre til noe som kalles en mystisk gjest. Dette betyr ganske enkelt at vi kan ende opp i en situasjon der vi bruker data for en av våre testscenarier, men har ingen anelse om hvor den faktisk kommer fra og hva den består av. Mer om det i neste artikkel.

callbacks

Hvis du ikke er klar over tilbakeringinger ennå, la meg gi deg en kort oppgave. Tilbakeringinger kjøres på bestemte punkter i kodeksens livssyklus. Når det gjelder Rails, vil dette bety at du har kode som kjøres før objekter blir opprettet, oppdatert, ødelagt osv. 

I forbindelse med RSpec, er det livssyklusen for testene som kjøres. Det betyr ganske enkelt at du kan spesifisere kroker som skal kjøres før eller etter hver test blir kjørt i spesifikasjonsfilen, for eksempel - eller bare rundt hver test. Det er noen få finkornede alternativer tilgjengelig, men jeg anbefaler at vi unngår å gå seg vill i detaljene for nå. Første ting først:

  • før (: hver)

Denne tilbakeringingen kjøres før hvert testeksempel.

Noen spesifikke filer

Beskriv agent, '#favorite_gadget' gjør før (: hver) gjør @gagdet = Gadget.create (navn: 'Walther PPK') avslutter det 'returnerer ett element, favorittgjenkjenningen til agenten' do agent = Agent.create (navn : 'James Bond') agent.favorite_gadgets << @gadget expect(agent.favorite_gadget).to eq 'Walther PPK' end… end

La oss si at du vil trenge en bestemt gadget for hver test du kjører i et bestemt omfang. før lar deg pakke ut dette i en blokk og forbereder denne lille utdragsenheten for deg. Når du konfigurerer data på den måten, må du selvfølgelig bruke forekomstvariabler for å få tilgang til det blant ulike mål.

Oppmerksomhet!

Ikke bli lurt av bekvemmelighet i dette eksemplet. Bare fordi du kan gjøre slike ting, betyr ikke at du burde. Jeg vil unngå å gå inn i Antipatternes territorium og forvirre helvete ut av deg, men på den annen side vil jeg forklare nackdelene til disse enkle dummyøvelsene litt også. 

I eksemplet ovenfor vil det være mye mer uttrykksfulle hvis du konfigurerer de nødvendige objektene på en test-for-test-basis. Spesielt på større spec-filer kan du raskt miste synet av disse små tilkoblingene og gjøre det vanskeligere for andre å binde sammen disse oppgavene.

  • før (: alle)

Dette før blokkerer bare én gang før alle de andre eksemplene i en spesifikasjonsfil.

Noen spesifikke filer

Beskriv agent, '#emy' gjør før (: alle) gjør @main_villain = Villain.create (navn: 'Ernst Stavro Blofeld') @mission = Mission.create (navn: 'Moonraker') @ mission.villains << @main_villain end it 'returns the main enemy Bond has to face in his mission' do agent = Agent.create(name: 'James Bond') @mission.agents << agent expect(agent.enemy).to eq 'Ernst Stavro Blofeld' end… end

Når du husker de fire faser av testen, før Blokker er noen ganger nyttige når du setter opp noe for deg som må gjentas regelmessig - sannsynligvis ting som er litt mer meta i naturen.

etter (: hver) og tross alt) har samme oppførsel, men kjører bare etter at testene dine er blitt utført. etter brukes ofte til å rydde opp filene dine, for eksempel. Men jeg synes det er litt tidlig å ta opp det. Så begå det til minne, vet at det er der i tilfelle du begynner å trenge det, og la oss gå videre for å utforske andre, mer grunnleggende ting.

Alle disse tilbakekallingene kan plasseres strategisk for å dekke dine behov. Plasser dem i noen beskrive blokkere omfanget du trenger for å kjøre dem - de trenger ikke nødvendigvis å bli plassert på toppen av spesifikasjonsfilen din. De kan lett være nestet inn i dine spesifikasjoner. 

Noen spesifikke filer

Beskriv agent gjør før (: hver) do @mission = Mission.create (navn: 'Moonraker') @bond = Agent.create (navn: 'James Bond', nummer: '007') End beskriver '#ememy' (: hver) gjør @main_villain = Villain.create (navn: 'Ernst Stavro Blofeld') @ mission.villains << @main_villain end describe 'Double 0 Agent with associated mission' do it 'returns the main enemy the agent has to face in his mission' do @mission.agents << @bond expect(@bond.enemy).to eq 'Ernst Stavro Blofeld' end end describe 'Low-level agent with associated mission' do it 'returns no info about the main villain involved' do some_schmuck = Agent.create(name: 'Some schmuck', number: '1024') @mission.agents << some_schmuck expect(some_schmuck.enemy).to eq 'That's above your paygrade!' end end… end end

 Som du kan observere, kan du plassere tilbakekallingsblokker til enhver smak, og gå så dypt som du trenger. Koden i tilbakeringingen vil bli utført innenfor omfanget av et hvilket som helst beskrivende blokkomfang. Men litt råd: Hvis du føler behov for å hekke for mye, og det ser ut til å bli litt rotete og kompliserte, bør du tenke på hvordan du kan forenkle testene og deres oppsett. KYSSE! Gjør det enkelt dummen. Vær også oppmerksom på hvor fint dette leser når vi tvinger disse tester til å mislykkes:

Produksjon

Feil: 1) Agent # fiende Dobbel 0 Agent med tilhørende oppdrag returnerer den største fienden agenten har å møte i sitt oppdrag Feil / Feil: Forvent (@ bond.enemy). Til eq 'Ernst Stavro Blofeld' forventet: "Ernst Stavro Blofeld "fikk:" Blofeld "2) Agent # fiendtlig Lavnivå agent med tilhørende misjonsavkastning ingen informasjon om den største skurken som er involvert Feil / Feil: Forvent (some_schmuck.enemy) .to eq 'Det er over din paygrade!' forventet: "Det er over din paygrade!" fikk: "Blofeld"

generatorer

La oss også få en rask titt på hvilke generatorer som leveres av RSpec for deg. Du har allerede sett en når vi brukte skinner generere rspec: installere. Denne lille fella gjorde det raskt og enkelt å sette opp RSpec. Hva har vi mer?

  • rspec: modell

Vil du ha en annen dummy modell spec?

Terminal

skinner generere rspec: modell another_dummy_model

Produksjon

opprett spec / models / another_dummy_model_spec.rb

Rask, ikke sant? Eller en ny spesifikasjon for en kontrollertest, for eksempel:

  • rspec: kontrolleren

Terminal

skinner genererer rspec: controller dummy_controller

Produksjon

spec / kontrollere / dummy_controller_controller_spec.rb
  • rspec: view

Det samme fungerer for visninger, selvfølgelig. Vi vil ikke teste noen visninger som det skjønt. Spesifikasjoner for visninger gir deg minst bang for pengene, og det er helt tilstrekkelig i sannsynligvis nesten alle scenarier for å indirekte teste dine synspunkter via funksjonstester. 

Feature tester er ikke en spesialitet av RSpec per se og passer bedre til en annen artikkel. Når det er sagt, hvis du er nysgjerrig, sjekk Capybara, som er et utmerket verktøy for den slags ting. Det lar deg teste hele strømmen som utøve flere deler av appen din, som kommer sammen, og tester komplette funksjoner mens du simulerer nettleseropplevelsen. For eksempel, en bruker som betaler for flere elementer i en handlekurv.

  • rspec: helper

Den samme generatorstrategien lar oss også plassere en hjelper uten mye oppstyr.

Terminal

skinner genererer rspec: helper dummy_helper

Produksjon

opprett spec / helpers / dummy_helper_helper_spec.rb

Det dobbelte helper_helper del var ikke en ulykke. Når vi gir det et mer "meningsfylt" navn, vil du se at RSpec bare legges til _hjelper på egen hånd.

Terminal

skinner genererer rspec: helper important_stuff

Produksjon

lage spec / helpers / important_stuff_helper_spec.rb

Et ord om hjelpere

Nei, denne katalogen er ikke et sted å skaffe dine dyrebare hjelpemetoder som kommer opp mens du refiterer testene dine. Disse ville gå under spec / support, faktisk. spec / hjelpere er for testene du burde skrive for visningshjelpene dine - en hjelper som set_date ville være et vanlig eksempel. Ja, fullstendig testdekning av koden din bør også inkludere disse hjelpemetodene. Bare fordi de ofte virker små og trivielle, betyr det ikke at vi skal overse dem eller ignorere deres potensial for bugs vi ønsker å fange. Jo mer komplekse hjelperen faktisk viser seg, desto større grunn burde du ha å skrive en helper_spec for det!

Bare hvis du begynner å leke med det med en gang, husk at du må kjøre hjelpemetoder på en hjelper objekt når du skriver hjelpetester for å kunne fungere. Så de kan bare bli utsatt ved hjelp av dette objektet. Noe sånt som dette:

Noen hjelpespesifikasjoner

beskriv '#set_date' do ... helper.set_date ... end ... 

Du kan bruke samme type generatorer for funksjonsspesifikasjoner, integreringsspesifikasjoner og e-postspesifikasjoner. Disse er ute av vårt omfang for i dag, men du kan forplikte dem til minne for fremtidig bruk:

  • rspec: mailer
  • rspec: funksjon
  • rspec: integrering

En titt på genererte spesifikasjoner

Spesifikasjonene vi opprettet via generatoren ovenfor er klare til å gå, og du kan legge til testene dine der med en gang. La oss få et lite blikk på en forskjell mellom spesifikasjoner, skjønt:

spec / modeller / dummy_model_spec.rb

krever 'rails_helper' RSpec.describe DummyModel, type:: modell venter "legg til noen eksempler på (eller slett) # __ FILE__" slutt

spec / kontrollere / dummy_controller_controller_spec.rb

krever 'rails_helper' RSpec.describe DummyControllerController, type:: kontrolleren slutter

spec / hjelpere / dummy_helper_helper_spec.rb

krever 'rails_helper' RSpec.describe DummyHelperHelper, type:: hjelper ikke lenger "legg til noen eksempler på (eller slett) # __ FILE__" avslutte

Det trenger ikke en wunderkind å finne ut at de alle har forskjellige typer. Dette :type RSpec-metadata gir deg mulighet til å skjære og titte tester over filstrukturer. Du kan målrette disse testene litt bedre på den måten. Si at du vil ha noen slags hjelpere som bare er lastet for kontroller-spesifikasjoner, for eksempel. Et annet eksempel er at du vil bruke en annen katalogstruktur for spesifikasjoner som RSpec ikke forventer. Å ha denne metadataen i testene gjør det mulig å fortsette å bruke RSpec-støttefunksjoner og ikke gå opp i testpakken. Så du er fri til å bruke hvilken som helst katalogstruktur som fungerer for deg hvis du legger til dette :type metadata.

Standard RSpec-tester er ikke avhengig av metadataene derimot. Når du bruker disse generatorene, blir de lagt til gratis, men du kan helt unngå dem også hvis du ikke trenger dem. 

Du kan også bruke denne metadata for filtrering i spesifikasjonene dine. Si at du har en tidligere blokk som bare skal kjøre på modellspecifikasjoner, for eksempel. Ryddig! For større testpakker kan dette komme veldig praktisk en dag. Du kan filtrere hvilken fokusert gruppe tester du vil kjøre i stedet for å utføre hele pakken, noe som kan ta en stund. 

Alternativene dine strekker seg utover de tre merkingsalternativene ovenfor, selvfølgelig. La oss lære mer om snitting og dicing dine tester i neste avsnitt.

Tags

Når du samler en større testpakke over tid, vil det ikke bare være nok til å kjøre tester i bestemte mapper for å kjøre RSpec-tester raskt og effektivt. Det du vil kunne gjøre, er å kjøre tester som hører sammen, men kan spres over flere kataloger. Merking til redning! Ikke ta feil, å organisere tester smart i mappene dine er også viktig, men tagging tar dette litt lenger.

Du gir testerne noen metadata som symboler som "wip", ": checkout", eller hva som passer dine behov. Når du kjører disse fokuserte testgruppene, angir du bare at RSpec burde ignorere å kjøre andre tester denne gangen ved å gi et flagg med navnet på kodene.

Noen spesifikke filer

beskriv agent,: wip gjør det "er et rot, akkurat nå" forvente (agent.favorite_gadgets) .til eq 'Unknown' end-end

Terminal

rspec - tag vift

Produksjon

Feil: 1) Agent er et rotete nå Failure / Error: expect (agent.favorite_gadgets) .to eq 'Unknown' ... 

Du kan også kjøre alle typer tester og ignorere en gruppe med grupper som er merket på en bestemt måte. Du gir bare en tilde (~) foran taggenavnet, og RSpec er glad for å ignorere disse testene.

Terminal

rspec - tag ~ wip

Kjører flere koder synkront, er heller ikke et problem:

Terminal

rspec - tag wip - tag kassa rspec - tag ~ wip - tag kassa

Som du kan se over, kan du blande og matche dem etter ønske. Syntaxen er ikke perfekt-gjentatt --stikkord er kanskje ikke ideell-men hei, det er heller ikke noe stort! Ja, alt dette er litt mer ekstra arbeid og mentalt overhead når du komponerer spesifikasjonene, men på forsiden gir det deg virkelig en kraftig evne til å skjære opp testpakken din på forespørsel. På større prosjekter kan det spare deg for mye tid på den måten.

Siste tanker

Det du har lært så langt, skal utstyre deg med det absolutt grunnleggende for å spille med egne tester, en overlevelse kit for nybegynnere. Og virkelig spiller og gjør feil så mye du kan. Ta RSpec og hele testdrevet thingie for et spinn, og forvent ikke å skrive kvalitetstest med en gang. Det er fortsatt et par stykker mangler før du vil føle deg komfortabel og før du blir effektiv med den. 

For meg var dette litt frustrerende i begynnelsen fordi det var vanskelig å se hvordan man skulle teste noe når jeg ikke hadde implementert det og ikke fullt ut forstod hvordan det ville oppføre seg. 

Testing viser virkelig om du forstår et rammeverk som Rails og vet hvordan brikkene passer sammen. Når du skriver tester, må du kunne skrive forventninger til hvordan et rammeverk skal oppføre seg. 

Det er ikke lett hvis du bare starter med alt dette. Håndtere flere domenespesifikke språk - her RSpec og Rails, for eksempel-pluss å lære Ruby API, kan være forvirrende som helvete. Ikke føl deg dårlig hvis lærekurven virker skremmende; det blir enklere hvis du holder fast i det. Å gjøre denne lyspæren gå av vil ikke skje over natten, men for meg var det veldig mye verdt innsatsen.