I denne siste artikkelen om grunnleggende om RSpec dekker vi noen få iffy-deler du kan og bør unngå, hvordan du skal skrive tester, hvorfor du bør unngå databasen så mye som mulig, og hvordan du kan øke testpakken din.
Nå som du har det grunnleggende under beltet ditt, bør vi ta deg tid til å diskutere noen få delaktige deler av RSpec og TDD-noen få problemer som lett kan brukes overfor, og noen ulemper ved å bruke deler av RSpecs DSL-avspeilet. Jeg ønsker å unngå å fylle mange avanserte konsepter i din nyutviklede TDD-hjerner, men jeg føler at noen få poeng må gjøres før du går på din første testingstamme. Også, å lage en langsom test suite på grunn av dårlige vaner som er lett å unngå, er noe du kan forbedre som en nybegynner med en gang.
Visst, det er ganske mange ting du trenger for å få mer erfaring med før du vil føle deg komfortabel og effektiv med testing, men jeg vedder på at du også vil føle deg bedre fra starten hvis du fjerner noen av de beste praksiser som vil forbedre din Spesifikasjoner manifold uten å strekke dine ferdigheter for mye akkurat nå. Det er også et lite vindu i mer avanserte konsepter som du må hente over tid for å "mestre" testing. Jeg føler at jeg ikke bør forstyrre deg for mye i begynnelsen med disse fordi det kanskje bare føles innviklet og forvirrende før du har utviklet det større bildet som knytter alt sammen pent.
La oss starte med fart. En rask suite er ingenting som skjer ved et uhell; Det handler om "konstant" vedlikehold. Det er ganske viktig å lytte til testene dine ganske ofte, i hvert fall hvis du er om bord med TDD og har drukket Kool-Aid for en stund - og raske testpakker gjør det mye mer rimelig å ta hensyn til hvor testerne styrer du.
Testhastighet er noe du bør passe godt på. Det er viktig å teste en vanlig vane og holde det morsomt. Du vil raskt kunne kjøre testene dine slik at du får rask tilbakemelding mens du utvikler. Jo lenger tid det tar å utøve testpakken, desto mer sannsynlig vil det være at du hopper over testing mer og mer til du bare gjør det på slutten før du vil sende en ny funksjon.
Det kan ikke høres så ille først, men dette er ikke et trivielt problem. En av de viktigste fordelene med en testpakke er at den styrer utformingen av din søknad - for meg er dette trolig den største gevinsten fra TDD. Lengre testkjøringer gjør denne delen ganske umulig fordi det er svært sannsynlig at du ikke vil kjøre dem for ikke å ødelegge strømmen. Hastige tester garanterer at du ikke har noen grunn til ikke å kjøre testene dine.
Du kan se denne prosessen som en dialog mellom deg og testpakken. Hvis denne samtalen blir for treg, er det veldig vondt å fortsette. Når kodeditoren din tilbyr muligheten til å kjøre testene dine, bør du definitivt bruke denne funksjonen. Dette vil øke hastigheten dramatisk og forbedre arbeidsflyten din. Bytte hver gang mellom editoren din og et skall for å kjøre testene blir gamle veldig raskt. Men siden disse artiklene er rettet mot nybegynnere, forventer jeg ikke at du skal sette opp verktøyene dine like dette med en gang. Det er andre måter du kan forbedre denne prosessen uten å måtte tinker med redaktøren din med en gang. Det er godt å vite, og jeg anbefaler at du gjør slike verktøy til en del av arbeidsflyten din.
Vær også oppmerksom på at du allerede har lært hvordan du skal skille testene dine, og at du ikke trenger å kjøre hele testpakken hele tiden. Du kan enkelt kjøre enkeltfiler eller til og med enkelt den
blokker - alt innenfor en dygtig kodeditor uten å forlate det for terminalen. Du kan for eksempel fokusere testen på linjen under test. Det føles som magi, for å være ærlig, det blir aldri kjedelig.
Å skrive for mye til databasen - ofte veldig unødvendig, det er en sikker måte å raskt bremse testpakken din på. I mange testscenarier kan du fake ut de dataene du trenger for å sette opp en test og bare fokusere på dataene som er under test. Du trenger ikke å slå databasen for det hele tatt - spesielt ikke for deler som ikke er testet direkte, og bare støtte testen på en eller annen måte: En innlogget bruker mens du tester beløpet som skal betales på en kasse, for eksempel. Brukeren er som en ekstra som kan falses ut.
Du bør prøve å komme unna med å ikke slå databasen så mye som mulig fordi dette biter en stor del av en langsom test suite. Prøv heller ikke å konfigurere for mye data hvis du ikke trenger det i det hele tatt. Det kan være veldig enkelt å glemme med integreringstester spesielt. Enhetstester er ofte mye mer fokusert per definisjon. Denne strategien vil vise seg å være svært effektiv for å unngå å senke testpakker over tid. Velg dine avhengigheter med stor forsiktighet og se hva som er den minste mengden data som får testene dine til å passere.
Jeg ønsker ikke å gå inn i noen spesifikasjoner for nå - det er nok litt for tidlig i din bane for å snakke om stubber, spioner, feber og ting. Forvirre deg her med slike avanserte begreper virker kontraproduktivt, og du vil komme inn i disse snart nok. Det er mange strategier for hurtige tester som også involverer andre verktøy enn RSpec. For nå, prøv å vikle hodet rundt det større bildet med RSpec og testing generelt.
Du vil også sikte på å teste alt bare en gang - om mulig. Ikke prøv det samme igjen og igjen - det er bare sløsing. Dette skjer for det meste ved et uhell og / eller dårlige designbeslutninger. Hvis du begynte å ha tester som er treg, er dette et enkelt sted å refactor for å få et fartforhøyelse.
De fleste av testene dine bør også være på Enhetsnivå, teste modellene dine. Dette vil ikke bare holde tingene raskere, men vil også gi deg det største banget for pengene. Integrasjonstester som tester hele arbeidsflyten, etterligner brukerens oppførsel i en grad ved å samle en mengde komponenter og teste dem synkront, bør være den minste delen av testpyramidet. Disse er ganske sakte og "dyre". Kanskje 10% av dine samlede tester ikke er urealistiske å skyte for - men dette avhenger, antar jeg.
Å utøve databasen så lite som mulig, kan være vanskelig fordi du trenger å lære mange flere verktøy og teknikker for å oppnå det effektivt, men det er viktig å vokse testpakker som er rimelig rask raskt nok til å virkelig kjøre testene dine ofte.
Vår-serveren er en funksjon av Rails og forlaster din søknad. Dette er en annen enkel strategi for å øke testhastigheten betydelig - helt ut av boksen, bør jeg legge til. Hva det gjør, er at du bare holder applikasjonen din i gang i bakgrunnen uten å måtte starte den med hver enkelt testkjøring. Det samme gjelder for Rake-oppgaver og migrasjoner; disse vil løpe raskere også.
Siden Rails 4.1 har Spring blitt inkludert i Rails - lagt til Gemfile automatisk - og du trenger ikke å gjøre mye for å starte eller stoppe denne forhåndsinnlasteren. Tidligere måtte vi koble opp våre egne valgfrie verktøy for dette - som du fortsatt kan gjøre selvfølgelig hvis du har andre preferanser. Det som er veldig hyggelig og gjennomtenkt, er at det vil starte automatisk om du endrer noen edelstener, initialisatorer eller config-filer. En fin og praktisk berøring fordi det er lett å glemme å ta vare på det selv.
Som standard er den konfigurert til å kjøre skinner
og rake
kun kommandoer. Så vi må sette opp for å også kjøre med rspec
kommandoen for å kjøre testene våre. Du kan be om status for våren slik:
vårstatus
Våren kjører ikke.
Siden produksjonen fortalte oss at våren ikke kjører, starter du det bare med vår server.
Når du kjører nå vårstatus
, du bør se noe som ligner på dette:
Våren kjører: 3738 vår server | rspec-dummy | startet 21 sekunder siden
Nå bør vi sjekke hva våren er satt opp for å forhåndsbelaste.
våren binstub - all
* bin / rake: vår allerede tilstede * bin / skinner: vår allerede tilstede
Dette forteller oss at våren er forhåndsbelastet Rails for rake
og skinner
kommandoer, og ingenting annet så langt. Det må vi ta vare på. Vi må legge til perlen fjær kommandoer-rspec
, og våre tester er så klare til å bli forhåndslastet.
perle 'vår-kommandoer-rspec', gruppe:: utvikling
bunt installasjonspakke exec spring binstub rspec
Jeg sparer deg for produksjonen fra bunt installasjon
; Jeg er sikker på at du har sett mer enn din del av det allerede. Løping bunt exec spring binstub rspec
, på den annen side genererer en bin / rspec
fil som i utgangspunktet legger til at den skal lastes inn av våren. La oss se om dette virket:
våren binstub - all
Dette skapte noe som kalles en binstub-en wrapper for kjørbare som skinner
, rake, bunt
, rspec
og slik-så at når du bruker rspec
kommandoen den vil bruke våren. Som et tillegg sørger slike binstubs for at du kjører disse kjørbarhetene i riktig miljø. De lar deg også kjøre disse kommandoene fra hver katalog i appen din, ikke bare fra roten. Den andre fordelen med binstubs er at du ikke trenger å prepend bundle exec
med alt.
* bin / rake: vår allerede tilstede * bin / rspec: vår allerede tilstede * bin / skinner: vår allerede til stede
Ser A-OK! La oss stoppe og starte vår-serveren før vi går videre:
vårstopp våren server
Så nå kjører du vårens server i et dedikert terminalvindu, og du driver testene dine med en litt annen syntaks i en annen. Vi trenger bare å prefikse hver testkjøring med vår
kommando:
våren rspec spec
Dette kjører alle dine spesifikke filer, selvfølgelig. Men det er ikke nødvendig å stoppe der. Du kan også kjøre enkeltfiler eller merkede tester via våren - ikke noe problem! Og alle vil bli lynrask nå; På mindre testpakker synes de egentlig nesten øyeblikkelig. I tillegg kan du bruke samme syntaks for din skinner
og rake
kommandoer. Hyggelig, eh?
spring rake vårskinner g modell BondGirl navn: string spring rake db: migrere ...
Så vi får våren ut av boksen for å få fart på ting i Rails, men vi må ikke glemme å legge til denne lille perlen for å la våren vite hvordan du spiller ball med RSpec.
Tingene som er omtalt i denne delen, er sannsynligvis gode for å unngå så lenge du kan finne en annen løsning for dem. Overusing noen av RSpec-bekvemmelighetene kan føre til å utvikle dårlige testvaner - i det minste dårlige. Det vi vil diskutere her er praktisk på overflaten, men kan bite deg litt senere nedover veien.
De bør ikke betraktes som AntiPatterns-ting å unngå med det samme, men som "lukter", ting du bør være forsiktig med og som kan introdusere en betydelig kostnad du ofte ikke vil betale. Begrunnelsen for dette innebærer noen flere ideer og begrep som du som en nybegynner er mest sannsynlig ikke kjent med ennå - og helt ærlig, kan være litt over hodet ditt ennå på dette punktet - men jeg skal i det minste sende deg hjem med noen få røde flagg for å tenke på og forplikte seg til minne for nå.
la
Å ha mye av la
referanser kan virke veldig praktisk først - spesielt fordi de tørker ting opp ganske mye. Det virker som en rimelig god utvinning først for å få dem øverst på filene dine, for eksempel. På den annen side kan de lett gi deg en vanskelig tid å forstå din egen kode hvis du besøker bestemte tester, noe betydelig tid senere. Har ikke datasettet oppført i din la
blokker hjelper ikke forståelsen av testene dine for mye. Det er ikke så trivielt som det kan høres først, spesielt hvis andre utviklere er involvert som trenger å lese arbeidet ditt også.
Denne forvirringen blir mye dyrere jo flere utviklere er involvert. Det er ikke bare tidkrevende hvis du må jakte på la
referanser om og om igjen, det er også dumt fordi det ville vært unngått med svært liten innsats. Klarhet er konge, ingen tvil om det. Et annet argument for å holde disse dataene innebygd er at testpakken din blir mindre sprø. Du vil ikke bygge et kortkort som blir mer ustabilt med alle la
som skjuler bort detaljer fra hver test. Du har sikkert lært at bruk av globale variabler ikke er en god ide. I den forstand, la
er semi-global innenfor dine spesifikke filer.
Et annet problem er at du må teste mange forskjellige variasjoner, forskjellige stater for lignende scenarier. Du vil snart gå tom for en rimelig navn la
uttalelser for å dekke alle de forskjellige versjonene du måtte trenge - eller ende opp med en haystack av tonn med tilsvarende navngitte statlige variasjoner. Når du setter opp dataene i hver test direkte, har du ikke det problemet. Lokale variabler er billige, høyt lesbare, og ikke rot med andre mål. Faktisk kan de være enda mer uttrykksfulle fordi du ikke trenger å vurdere tonnevis med andre tester som kan ha et problem med et bestemt navn. Du vil unngå å skape en annen DSL på toppen av rammen som alle trenger å dechiffrere for hver test som bruker la
. Jeg håper det føles veldig som en sløsing med alles tid.
før
& etter
Lagre ting som før
, etter
og dens variasjoner for spesielle anledninger og ikke bruk det hele tiden, over alt. Se det som en av de store våpen du trekker ut for meta ting. Rengjøring av data er et godt eksempel som er for meta for hver enkelt test å håndtere. Du vil selvsagt trekke ut det.
Ofte legger du la
ting øverst på en fil og gjemme bort disse detaljene fra andre tester som bruker dem ned i filen. Du vil ha relevant informasjon og data så nært som mulig til delen der du faktisk utøver testen - ikke miles unna, noe som gjør individuelle tester mer obskure.
Til slutt føles det som for mye tau å henge med, fordi la
introduserer mye felles inventar. Det går i utgangspunktet til dummy testdata hvis omfang ikke er tett nok.
Dette fører lett til en stor lukt kalt "mystery guest". Det betyr at du har testdata som dukker opp fra ingensteds eller bare antas. Du må ofte jage dem først for å forstå en test - spesielt hvis det har gått noe tid siden du skrev koden eller hvis du er ny i en kodebase. Det er mye mer effektivt å definere testdataene dine inline akkurat der du trenger det - i oppsettet av en bestemt test og ikke i et mye bredere omfang.
... beskrive agent, '#print_favorite_gadget' gjør det 'skriver ut agentenes navn, rang og favoritt-gadget' forventer (agent.print_favorite_gadget) .til eq ('Commander Bond har en ting for Aston Martins') slutten
Når du ser på dette, leses det ganske pent, ikke sant? Det er kortfattet, en en-liner, ganske rent, nei? La oss ikke lure oss selv. Denne testen forteller oss ikke mye om middel
i spørsmålet, og det forteller oss ikke hele historien. Implementeringsdetaljer er viktige, men vi ser ikke noe av det. Agenten ser ut til å ha blitt opprettet et annet sted, og vi må først jage det for å kunne forstå hva som skjer her. Så det ser kanskje elegant ut på overflaten, men det kommer med en heftig pris.
Ja, testene dine kan ikke ende opp med å bli super DRY hele tiden, men dette er en liten pris å betale for å være mer uttrykksfulle og enklere å forstå, tror jeg. Sikker på at det er unntak, men de burde egentlig bare brukes på eksepsjonelle omstendigheter etter at du har brukt de mulige alternativene Ruby tilbyr med en gang.
Med en mystisk gjest må du finne ut hvor data kommer fra, hvorfor det betyr noe, og hva dens spesifikasjoner egentlig er. Hvis du ikke ser implementeringsdetaljer i en bestemt test selv, blir livet ditt enda vanskeligere enn det du trenger å være. Jeg mener, gjør det du føler om du jobber med egne prosjekter, men når andre utviklere er involvert, ville det være bedre å tenke på å gjøre deres erfaring med koden så jevn som mulig.
Som med mange ting, selvfølgelig, ligger de viktigste sakene i detaljene, og du vil ikke holde deg selv og andre i mørket om dem. Lesbarhet, succinctness og bekvemmeligheten av la
bør ikke komme på bekostning av å miste klarhet over implementeringsdetaljer og misdirection. Du vil at hver enkelt test skal fortelle hele historien og gi all sammenheng til å forstå det med en gang.
Lang historie kort, du vil ha tester som er enkle å lese og enklere å begrunnelse om - på en test-for-test-basis. Prøv å spesifisere alt du trenger i selve testen - og ikke mer enn det. Denne typen avfall begynner å "lukte" akkurat som alle andre slags søppel. Det innebærer også at du må legge til detaljene du trenger for bestemte tester så sent som mulig - når du oppretter testdata samlet, innenfor selve scenariet og ikke noe sted fjernt. Den foreslåtte bruken av la
tilbyr en annen form for bekvemmelighet som ser ut til å motsette seg denne ideen.
La oss ta en annen tur med det forrige eksempelet og implementere det uten mysteriet gjesten problemet. I løsningen nedenfor finner vi all relevant informasjon for testen inline. Vi kan bli rett i denne spesifikasjonen hvis den feiler og ikke trenger å lete etter ytterligere info noe annet sted.
... beskrive agent, '#print_favorite_gadget' gjør det 'skriver ut agentenes navn, rang og favoritt gadget' agent = Agent.new (navn: 'James Bond', rang: 'Commander', favorite_gadget: 'Aston Martin') forventer (agent.print_favorite_gadget). til eq ('Commander Bond har en ting for Aston Martins') slutten
Det ville vært fint hvis la
la deg sette opp barebones testdata som du kan forbedre på et behov for å vite i hver enkelt test, men dette er ikke hvordan la
er rullende. Slik bruker vi fabrikker via Factory Girl i disse dager.
Jeg vil spare deg for detaljene, spesielt siden jeg har skrevet noen stykker om det allerede. Her er mine nybegynnerartikler 101 og 201 om hva Factory Girl har å tilby - hvis du allerede er nysgjerrig på det. Det er skrevet for utviklere uten tonnevis av erfaringer også.
La oss se på et annet enkelt eksempel som gir god bruk av støttende testdata som er satt opp inline:
beskriv agent, '#current_mission' gjør det 'skriver ut agentens nåværende oppdragsstatus og dens mål' do mission_octopussy = Mission.new (navn: 'Octopussy', mål: 'Stopp dårlig hvit mann') bond = Agent.new (navn : 'James Bond', status: 'Undercover-operasjon', seksjon: '00', licence_to_kill: true) bond.missions << mission_octopussy expect(bond.current_mission).to eq ('Agent Bond is currently engaged in an undercover operation for mission Octopussy which aims to stop bad white dude') end end
Som du kan se, har vi all den informasjonen denne testen trenger på ett sted, og trenger ikke å jakte på noen spesifikasjoner noe annet sted. Den forteller en historie og er ikke uklar. Som nevnt er dette ikke den beste strategien for DRY-kode. Utbetalingen er bra, skjønt. Klarhet og lesbarhet oppveier denne lille repetitive koden med en lang skudd, spesielt i store kodebaser.
For eksempel, si at du skriver noen nye, tilsynelatende ikke-relaterte funksjoner, og plutselig begynner denne testen å mislykkes som sikkerhetsskade og du har ikke rørt denne spesifikke filen i aldre.
Tror du at du vil være glad hvis du trenger å dechiffrere oppsettkomponentene først for å forstå og fikse denne sviktende testen før du kan fortsette med en helt annen funksjon du jobber med? Jeg tror ikke! Du vil komme ut av denne "ikke-relaterte" spesifikasjonen så snart som mulig og komme tilbake til å fullføre den andre funksjonen.
Når du finner alle testdataene der, der tester forteller deg hvor det mislykkes, øker du sjansene dine med et langt skudd med å fikse dette raskt uten å laste ned en helt annen del av appen i hjernen din..
Du kan rengjøre og tørke koden betydelig ved å skrive egne hjelpemetoder. Det er ikke nødvendig å bruke RSpec DSL for noe like billig som en Ruby-metode.
La oss si at du fant et par repeterende armaturer som begynner å føle seg litt skitne. I stedet for å gå med a la
eller a Emne
, definer en metode på bunnen av en beskrivende blokk-en konvensjon - og trekk ut fellesene i den. Hvis den brukes litt mer utbredt i en fil, kan du legge den i bunnen av filen også.
En fin bivirkning er at du ikke arbeider med noen semi-globale variabler på den måten. Du vil også redde deg selv fra å gjøre en rekke endringer over alt hvis du trenger å finjustere dataene litt. Nå kan du gå til et sentralt sted hvor metoden er definert og påvirke alle stedene den brukes på en gang. Ikke verst!
Beskriv agent, '#current_status' gjør det 'spekulerer om agentens valg av reisemål hvis status er ferie' do bond = Agent.new (navn: 'James Bond', status: 'På ferie', seksjon: '00', lisens_til_kill : true) expect (bond.current_status) .to eq ('Commander Bond er på ferie, sannsynligvis i Bahamas') slutter det 'spekulerer om kvartmesterens valg av reisemål hvis status er ferie' gjør q = Agent.new (navn: 'Q', status: 'På ferie', seksjon: '00', lisens_to_kill: sant) forventer (q.current_status) .til eq ('Kvartmesteren er på ferie, sannsynligvis ved DEF CON')
Som du kan se er det litt repeterende oppsettkode, og vi vil unngå å skrive dette igjen og igjen. I stedet vil vi bare se grunnleggende for denne testen og ha en metode for å bygge resten av objektet for oss.
Beskriv agent, '#current_status' gjør det 'spekulerer om agentens valg av destinasjon hvis status er ferie' forvente bond = build_agent_on_vacation ('James Bond', 'On vacation') (bond.current_status). til eq ('Commander Bond er på ferie, sannsynligvis i Bahamas '), spekulerer det om kvartalsmesterens valg av reisemål hvis status er ferie, forvent Q = build_agent_on_vacation (' Q ',' On Vacation ') (q.current_status) .to eq (' Kvartalmesteren er på ferie, sannsynligvis ved DEF CON ') slutten def build_agent_on_vacation (navn, status) Agent.new (navn: navn, status: status, seksjon:' 00 ', lisens_to_kill: sann) ende
Nå tar vår ekstraherte metode seg av seksjon
og licence_to_kill
ting og dermed ikke distrahere oss fra testamentets grunnleggende. Selvfølgelig er dette et dummy eksempel, men du kan skala kompleksiteten så mye du trenger. Strategien endres ikke. Det er en veldig enkel refaktoringsteknikk - derfor presenterer jeg det tidlig, men en av de mest effektive. Også, det gjør det nesten en no-brainer for å unngå utvinningsverktøyene RSpecs tilbyr.
Det du bør også være oppmerksom på, er hvordan uttrykksfulle disse hjelpemetodene kan være uten å betale ekstra pris.
Unngå noen få deler av RSpec DSL og gjøre god bruk av gode ol 'Ruby og Object Oriented Programmeringsprinsipper er en god måte å nærme seg med å skrive testene dine. Du kan fritt bruke det nødvendige, beskrive
, kontekst
og den
, selvfølgelig.
Finn en god grunn til å bruke andre deler av RSpec og unngå dem så lenge du kan. Bare fordi ting kan virke praktisk og fancy, er ikke en god nok grunn til å bruke dem - det er bedre å holde ting enklere.
Enkel er god; det holder testene dine sunne og raske.