I den här sista artikeln om RSpec-basics täcker vi några få delar du kan och borde undvika, hur du ska skriva dina tester, varför borde du undvika databasen så mycket som möjligt och hur du påskyndar din testpaket.
Nu när du har grunderna under ditt bälte, bör vi ta dig tid att diskutera några få delar av RSpec och TDD-några problem som lätt kan överanvändas och vissa nackdelar med att använda delar av RSpecs DSL-återreflekterade. Jag vill undvika att fylla i många avancerade koncept i dina nykläckta TDD-hjärnor, men jag tycker att några punkter måste göras innan du går på din första testningspree. Dessutom skapar du en långsam testpaket på grund av dåliga vanor som lätt kan undvikas, något du kan förbättra som en nybörjare genast.
Visst, det finns en hel del saker som du behöver få mer erfarenhet av innan du kommer att känna dig bekväm och effektiv med testning, men jag slår vad om att du också kommer att må bättre från början om du tar bort några av de bästa metoder som förbättrar din specs manifold utan att sträcka dina färdigheter för mycket just nu. Det är också ett litet fönster i mer avancerade koncept som du måste hämta över tiden för att "mastera" testning. Jag känner att jag inte bör störa dig för mycket i början med dessa eftersom det kanske bara känns förvirrad och förvirrande innan du har utvecklat den större bilden som binder allt tillsammans snyggt.
Låt oss börja med snabbhet. En snabb svit är inget som händer av misstag; det handlar om "konstant" underhåll. Att lyssna på dina test mycket ofta är ganska viktigt - åtminstone om du är ombord med TDD och har druckit Kool-Aid för en stund - och snabba testpaket gör det mycket mer rimligt att vara uppmärksam på var testen styr du.
Testhastighet är något du bör ta hand om. Det är viktigt att testa en vanlig vana och hålla det roligt. Du vill snabbt kunna köra dina test så att du får snabb feedback när du utvecklar. Ju längre tid det tar att utöva testpaketet desto mer sannolikt kommer det att vara att du hoppa över testning mer och mer tills du bara gör det i slutet innan du vill skicka en ny funktion.
Det kanske inte låter så illa först, men det här är inte en trivial fråga. En av de viktigaste fördelarna med en testpaket är att det leder till designen av din ansökan - för mig är det förmodligen den största vinsten från TDD. Längre testkörningar gör denna del ganska omöjlig eftersom det är mycket troligt att du inte kommer att köra dem för att inte bryta ditt flöde. Snabba tester garanterar att du inte har någon anledning att inte köra dina tester.
Du kan se denna process som en dialog mellan dig och testpaketet. Om denna konversation blir för långsam, är det verkligen smärtsamt att fortsätta. När din kodredigerare erbjuder möjligheten att även köra dina tester, bör du definitivt använda den här funktionen. Detta kommer dramatiskt att öka hastigheten och förbättra ditt arbetsflöde. Växling varje gång mellan din redaktör och ett skal för att köra dina test blir gammalt väldigt snabbt. Men eftersom dessa artiklar riktar sig till nybörjare programmerare, förväntar jag mig inte att du ställer in dina verktyg så här direkt. Det finns andra sätt att förbättra processen utan att behöva tinker med din redaktör direkt. Det är dock bra att veta, och jag rekommenderar att sådana verktyg ingår i ditt arbetsflöde.
Också vara medveten om att du redan har lärt dig hur du ska skära dina test och att du inte behöver köra hela testpaketet hela tiden. Du kan enkelt köra enskilda filer eller ens enkla Det
block-allt inom en skicklig kodredigerare utan att någonsin lämna den för terminalen. Du kan exempelvis fokusera testet på linjen som testas. Det känns som magi, att vara uppriktigt - det blir aldrig tråkigt.
Att skriva för mycket till databasen - ofta mycket onödigt så - är ett säkert sätt att snabbt sakta ner testserien betydligt. I många testscenarier kan du förfalska de data som du behöver för att ställa in ett test och bara fokusera på de data som testas direkt. Du behöver inte slå databasen för det mesta, speciellt inte för delar som inte testas direkt och stödjer bara testet på något sätt: en inloggad användare medan du testar det belopp som ska betalas vid en kassa, till exempel. Användaren är som en extra som kan faknas ut.
Du bör försöka komma undan med att inte slå databasen så mycket som möjligt eftersom det biter en stor bit ur en långsam testpaket. Försök också att inte ställa in för mycket data om du inte behöver det alls. Det kan vara mycket lätt att glömma med integrationsprov särskilt. Enhetstester är ofta mycket mer fokuserade per definition. Denna strategi kommer att visa sig mycket effektiv för att undvika att sänka testpaket över tiden. Välj dina beroenden med stor omsorg och se vad som är den minsta mängd data som får dina tester att passera.
Jag vill inte gå in i några mer specifika för nu - det är nog lite för tidigt i din bana att prata om stubbar, spioner, förfalskningar och saker. Förvirra dig här med sådana avancerade begrepp verkar kontraproduktiva, och du kommer snart att komma in i dessa. Det finns många strategier för snabba tester som även involverar andra verktyg än RSpec. För tillfället, försök att wrap ditt huvud runt den större bilden med RSpec och testning i allmänhet.
Du vill också sikta på att testa allt bara en gång - om möjligt. Testa inte samma sak om och om igen - det är bara slösigt. Detta sker oftast av misstag och / eller dåliga designbeslut. Om du började ha tester som är långsamma, är det här en lätt plats att refactor för att få en hastighetsökning.
Majoriteten av dina test ska också vara på Enhetsnivå, testa dina modeller. Det kommer inte bara att hålla sakerna snabba men ger dig också det största stödet för pengarna. Integrationstest som testar hela arbetsflöden-efterliknar användarens beteende i viss utsträckning genom att samla en massa komponenter och testa dem synkront - borde vara den minsta delen av din testpyramid. Dessa är ganska långsamma och "dyra". Kanske är 10% av dina totala tester inte orealistiska att skjuta för - men det beror på att jag antar.
Att utöva databasen så lite som möjligt kan vara svårt eftersom du behöver lära dig en hel del andra verktyg och tekniker för att uppnå det effektivt, men det är viktigt att vi utvecklar testpaket som är tillräckligt snabba tillräckligt för att verkligen köra dina test ofta.
Vårens server är en funktion i Rails och förprogrammerar din ansökan. Det här är en annan enkel strategi för att öka testhastigheten betydligt - direkt ur rutan, borde jag lägga till. Vad det gör är att helt enkelt hålla din ansökan igång i bakgrunden utan att behöva starta det med varje testkörning. Detsamma gäller för Rake-uppgifter och migreringar. Dessa kommer att springa snabbare också.
Eftersom Rails 4.1 har våren inkluderats i Rails-läggs till Gemfile automatiskt - och du behöver inte göra mycket för att starta eller stoppa den här förladdaren. Tidigare var vi tvungna att koppla upp våra egna verktyg för detta - vilket du fortfarande kan göra om du har andra preferenser. Vad som är riktigt snällt och tankeväckande är att det startar om automatiskt om du ändrar några pärlor, initialisatorer eller config-filer - en bra och praktisk touch eftersom det är lätt att glömma att ta hand om det själv.
Som standard är den konfigurerad att köra rails
och räfsa
kommandon bara. Så vi måste sätta upp det för att även springa med rspec
kommando för att köra våra tester. Du kan begära vårens status som sådan:
vårstatus
Våren körs inte.
Eftersom produktionen berättade att våren inte körs, startar du helt enkelt den med vår server.
När du kör nu vårstatus
, du borde se något liknande det här:
Våren körs: 3738 vår server | rspec-dummy | började 21 sekunder sedan
Nu bör vi kolla vad våren är inställd på att förinställa.
våren binstub --all
* bin / rake: våren redan närvarande * bin / skenor: våren redan närvarande
Detta berättar att våren är förspänningsskenor för räfsa
och rails
kommandon, och inget annat hittills. Det måste vi ta hand om. Vi måste lägga till pärlan våren-kommandon-rspec
, och våra test är då redo att förinstalleras.
pärla "fjäderkommandon-rspec", grupp:: utveckling
buntinstallationspaket exec spring binstub rspec
Jag sparar dig produktionen från buntinstallation
; Jag är säker på att du redan har sett mer än din rättvisa del av det. Löpning bunt exec spring binstub rspec
, å andra sidan genererar en bin / rspec
fil som i grunden lägger till att den förinstalleras av våren. Låt oss se om det här fungerade:
våren binstub --all
Detta skapade något som kallades en binstub-ett omslag för exekverbara filer som rails
, räfsa, bunt
, rspec
och så-så att när du använder rspec
kommando det kommer att använda våren. Som en sida säkerställer sådana binstubs att du kör dessa körbara filer i rätt miljö. De låter dig även köra dessa kommandon från varje katalog i din app, inte bara från roten. Den andra fördelen med binstubs är att du inte behöver förbeställa bunt exec
med allt.
* bin / rake: våren redan närvarande * bin / rspec: våren redan närvarande * bin / skenor: våren redan närvarande
Ser A-OK! Låt oss stoppa och starta om vårens server innan vi går vidare:
vårstopp vår server
Så nu kör du vårens server i ett dedikerat terminalfönster, och du kör dina tester med en något annorlunda syntax i en annan. Vi behöver helt enkelt prefixa varje testkörning med vår
kommando:
vår rspec spec
Det här förstås alla dina specfiler. Men det finns inget behov av att stanna där. Du kan även köra enskilda filer eller taggade tester via våren - inga problem! Och de kommer alla bli blixtsnabba nu; På mindre testpaket verkar de verkligen nästan omedelbart. Dessutom kan du använda samma syntax för din rails
och räfsa
kommandon. Trevligt, eh?
spring rake vårskenor g modell BondGirl namn: sträng fjäder rake db: migrera ...
Så vi får våren ur lådan för att påskynda saker i Rails, men vi får inte glömma att lägga till den lilla pärlan för att låta våren veta hur man spelar boll med RSpec.
De saker som nämns i det här avsnittet är noga bra att undvika så länge du kan hitta en annan lösning för dem. Att använda några av RSpec-bekvämligheterna kan leda till att man utvecklar dåliga testvanor - i alla fall iffy ones. Det vi kommer att diskutera här är bekvämt på ytan men kan bita dig lite senare på vägen.
De bör inte betraktas som AntiPatterns-saker att undvika direkt - men snarare ses som "dofter", saker som du bör vara försiktig med och som kan införa en betydande kostnad som du ofta inte vill betala. Anledningen till detta innebär några fler idéer och begrepp som du som nybörjare troligen inte känner till ännu - och helt uppriktigt kan vara lite över huvudet än vid denna tidpunkt - men jag borde åtminstone skicka dig hem med några röda flaggor att tänka på och begå till minne för nu.
låta
Har mycket av låta
referenser kan verka väldigt praktiska först, särskilt för att de torkar upp saker ganska mycket. Det verkar som en ganska bra utvinning i början för att få dem överst på dina filer, till exempel. Å andra sidan kan de enkelt ge dig en svår tid att förstå din egen kod om du besöker specifika tester någon betydande tid senare. Har inte datasättningen uppställd inom din låta
block hjälper inte förståelsen av dina test för mycket. Det är inte så trivialt som det kanske låter först, särskilt om andra utvecklare är involverade som behöver läsa ditt arbete också.
Den här typen av förvirring blir mycket dyrare, desto mer utvecklare är involverade. Det är inte bara tidskrävande om du måste jaga låta
referenser om och om igen, det är också dumt eftersom det skulle ha kunnat undvikas med mycket liten ansträngning. Klarhet är kung, ingen tvekan om det. Ett annat argument för att hålla dessa data inline är att din testpaket blir mindre skört. Du vill inte bygga ett korthus som blir mer instabilt med alla låta
Det döljer bort detaljer från varje test. Du har säkert lärt dig att det inte är en bra idé att använda globala variabler. I det avseendet, låta
är semi-global inom dina spec filer.
Ett annat problem är att du måste testa många olika variationer, olika tillstånd för liknande scenarier. Du kommer snart att gå tom för rimligt namngiven låta
uttalanden som täcker alla olika versioner du kanske behöver - eller hamnar med en höstack med ton av liknande namnvariationer. När du ställer in data i varje test direkt har du inte det problemet. Lokala variabler är billiga, läsbara och röra inte med andra omfattningar. Faktum är att de kan vara ännu mer uttrycksfulla eftersom du inte behöver överväga många andra test som kan ha problem med ett visst namn. Du vill undvika att skapa en annan DSL ovanpå det ramverk som alla behöver dechiffrera för varje test som använder låta
. Jag hoppas det känns väldigt mycket som ett slöseri med allas tid.
innan
& efter
Spara saker som innan
, efter
och dess variationer för speciella tillfällen och inte använda den hela tiden, överallt. Se den som en av de stora pistolerna du drar ut för meta saker. Rengöring av dina data är ett bra exempel som är för meta för varje enskilt test att hantera. Du vill förstå det självklart.
Ofta sätter du låta
saker längst upp i en fil och gömma bort dessa detaljer från andra tester som använder dem ner i filen. Du vill ha relevant information och data så nära som möjligt till den del där du faktiskt utövar testet - inte mil bort, vilket gör individuella test mer obskyra.
Till sist känns det som för mycket rep att hänga med, för låta
introducerar breda gemensamma armaturer. Det bryter i grunden till dummy testdata vars räckvidd inte är tillräckligt tätt.
Detta leder lätt till en stor lukt som kallas "mystery guest". Det betyder att du har testdata som visar sig ur ingenstans eller helt enkelt antas. Du måste ofta jaga dem först för att förstå ett test, särskilt om det har gått någon tid sedan du skrev koden eller om du är ny i en kodbas. Det är mycket effektivare att definiera dina testdata inline exakt var du behöver det - i inställningen av ett visst test och inte i ett mycket bredare räckvidd.
... beskriv agent, "#print_favorite_gadget" gör det "skriver ut agenten namn, rang och favorit gadget" förvänta (agent.print_favorite_gadget). Till eq ("Commander Bond har en sak för Aston Martins") änden
När du tittar på det här läser det ganska bra, eller hur? Det är kortfattat, en liner, ganska rent, nej? Låt oss inte lura oss själva. Detta test berättar inte mycket om ombud
i fråga, och det berättar inte hela historien. Implementeringsinformationen är viktig, men vi ser inte något av det. Agent verkar ha skapats någon annanstans, och vi måste jaga det först för att kunna förstå vad som händer här. Så det ser kanske elegant ut på ytan, men det kommer med ett stort pris.
Ja, dina tester kanske inte slutar vara supera DRY hela tiden i det avseendet, men det här är ett litet pris att betala för att vara mer uttrycksfull och lättare att förstå, tror jag. Visst finns det undantag, men de borde verkligen bara tillämpas på exceptionella omständigheter efter att du har uttömt de alternativ som ren Ruby erbjuder direkt.
Med en mystisk gäst måste du ta reda på var data kommer ifrån, varför det spelar roll och vad dess specifika är. Att inte se implementeringsdetaljerna i ett visst test gör ditt liv bara svårare än det behöver vara. Jag menar, gör vad du tycker om om du arbetar på dina egna projekt, men när andra utvecklare är inblandade, skulle det vara trevligare att tänka på att göra sin upplevelse med din kod så smidig som möjligt.
Som med många saker ligger naturligtvis de viktigaste sakerna i detaljerna, och du vill inte behålla dig själv och andra i mörkret om dem. Läsbarhet, succinctness och bekvämligheten av låta
borde inte komma på bekostnad av att förlora tydlighet över genomförandedetaljer och felriktning. Du vill att varje enskilt test ska berätta hela berättelsen och ge allt sammanhang att förstå det direkt.
Lång historia kort, du vill ha test som är lätta att läsa och lättare att räkna om-på test-för-test-basis. Försök att ange allt du behöver i själva testet - och inte mer än det. Denna typ av avfall börjar "lukta" precis som alla andra skräp. Det innebär också att du ska lägga till de uppgifter du behöver för specifika tester så sent som möjligt - när du skapar testdata övergripande, inom det aktuella scenariot och inte någon plats fjärrkontroll. Den föreslagna användningen av låta
erbjuder en annan typ av bekvämlighet som verkar motsätta sig denna idé.
Låt oss gå med det föregående exemplet och implementera det utan att ge mysteriet gästproblemet. I lösningen nedan hittar vi all relevant information för testinline. Vi kan stanna rätt i denna spec om det misslyckas och behöver inte leta efter ytterligare information någon plats annars.
... beskriv agent, "#print_favorite_gadget" gör det "skriver ut agenten namn, rang och favorit gadget" agent = Agent.new (namn: "James Bond", rang: "Commander", favorite_gadget: 'Aston Martin') förväntar sig (agent.print_favorite_gadget) .to eq ('Commander Bond har en sak för Aston Martins') änden
Det skulle vara trevligt om låta
Låt dig ställa in testdata för barebones som du kan förbättra på ett visst sätt i varje specifikt test, men det här är inte hur låta
rullar. Så här använder vi fabriker via Factory Girl dessa dagar.
Jag kommer att spara dig detaljerna, särskilt eftersom jag redan har skrivit några stycken om det. Här är mina nybegränsade artiklar 101 och 201 om vad Factory Girl har att erbjuda - om du redan är nyfiken på det. Det är skrivet för utvecklare utan massor av erfarenhet också.
Låt oss titta på ett annat enkelt exempel som utnyttjar stödjande testdata som ställs in inline:
beskriv agent, "#current_mission" gör det "skriver ut agentens nuvarande uppdragsstatus och dess mål" gör mission_octopussy = Mission.new (namn: "Octopussy", mål: "Stoppa dålig vit dude") bond = Agent.new : 'James Bond', status: 'Undercover operation', avsnitt: '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 ni kan se har vi all information som detta test behöver på ett ställe och behöver inte jaga några specifika platser någonstans. Det berättar en historia och är inte obskyrlig. Som nämnts är detta inte den bästa strategin för DRY-kod. Utbetalningen är dock bra. Klarhet och läsbarhet uppväger den här lilla repetitiva koden med ett långt skott, särskilt i stora kodbaser.
Till exempel, säg att du skriver en ny, till synes orelaterad funktion, och plötsligt börjar detta test att misslyckas som skador på säkerheten och du har inte rört den här specfilen i åldrarna.
Tror du att du kommer att vara glad om du behöver dechiffrera installationskomponenterna först för att förstå och åtgärda detta felprov innan du kan fortsätta med en helt annan funktion du jobbar med? Jag tror inte det! Du vill komma ut ur denna "orelaterade" spec så snart som möjligt och komma tillbaka till slutförandet av den andra funktionen.
När du hittar alla testdata där där dina tester berättar var det misslyckas ökar du dina chanser med ett långt skott av att fixa det här snabbt utan att "ladda ner" en helt annan del av appen i din hjärna.
Du kan rengöra och torka din kod avsevärt genom att skriva egna hjälpar metoder. Det finns ingen anledning att använda RSpec DSL för något så billigt som en Ruby-metod.
Låt oss säga att du hittade ett par repeterande armaturer som börjar känna lite smutsiga. Istället för att gå med en låta
eller a ämne
, definiera en metod längst ner i en beskrivningsblok-en konvention-och extrahera gemensamheterna i den. Om den används lite mer i en fil kan du placera den längst ner i filen.
En bra bieffekt är att du inte hanterar några semi-globala variabler på det sättet. Du kommer också att spara dig från att göra en massa förändringar överallt om du behöver justera data lite. Du kan nu gå till en central plats där metoden definieras och påverka alla platser där den används samtidigt. Inte dåligt!
Beskriv agent, "#current_status" gör det "spekulerar om agentens val av destination om status är semester" do bond = Agent.new (namn: "James Bond", status: "På semester", avsnitt: '00', licens till_kill : true) expect (bond.current_status) .to eq ("Commander Bond är på semester, förmodligen i Bahamas") slutar det "spekulerar om kvartalsmästarens val av destination om status är semester" gör q = Agent.new (namn: "Q", status: "På semester", avsnitt: "00", licence_to_kill: true) förvänta sig (q.current_status) .to eq ("kvartmästaren är på semester, förmodligen i DEF CON")
Som du kan se finns det lite repetitiv inställningskod, och vi vill undvika att skriva detta om och om igen. I stället vill vi bara se väsentliga för detta test och ha en metod att bygga resten av objektet för oss.
Beskriv agent, "#current_status" gör det "spekulerar om agentens val av destination om status är semester", gör bond = build_agent_on_vacation ("James Bond", "På semester") förvänta sig (bond.current_status) .to eq ('Commander Bond är på semester, förmodligen i Bahamas) slutar det "spekulerar om kvartalsmästarens val av destination om status är semester" förväntar sig q = build_agent_on_vacation ("Q", "On Vacation") (q.current_status) .to eq (' Kvartalmästaren är på semester, förmodligen hos DEF CON ') slut def build_agent_on_vacation (namn, status) Agent.new (namn: namn, status: status, avsnitt:' 00 ', licence_to_kill: true) änden
Nu tar vår extraherade metod hand om sektion
och licence_to_kill
saker och därmed inte distrahera oss från testets väsentliga delar. Det här är självklart ett exempel på dummy, men du kan skala sin komplexitet så mycket som du behöver. Strategin förändras inte. Det är en mycket enkel refactoringsteknik-det är därför jag introducerar det här tidigt men en av de mest effektiva. Det gör det också nästan ingen brytare för att undvika extraktionsverktygen RSpecs-erbjudanden.
Vad du också bör uppmärksamma är hur uttrycksfull dessa hjälpar metoder kan vara utan att betala något extra pris.
Att undvika några delar av RSpec DSL och utnyttja bra ol 'Ruby och Object Oriented Programming principer är ett bra sätt att närma sig att skriva dina test. Du kan fritt använda de väsentliga, beskriva
, sammanhang
och Det
, självklart.
Hitta en bra anledning att använda andra delar av RSpec och undvika dem så länge du kan. Bara för att saker kan tyckas bekvämt och fint är inte tillräckligt bra för att använda dem. Det är bättre att hålla saker enklare.
Enkelt är bra; Det håller dina tester hälsosamma och snabba.