Den andra artikeln i den här korta serien lär dig hur du använder olika matchare som följer med RSpec. Det visar också hur du skar din testpaket genom taggning, hur callbacks fungerar och hur man extraherar viss data. Vi utvider lite på grundöverlevnadssatsen från den första artikeln och visar dig tillräckligt för att vara farlig utan alltför stort rep att hänga dig själv.
I den första artikeln spenderade vi ganska mycket tid på att försöka svara på "varför" av testningen. Jag föreslår att vi kommer tillbaka direkt till "hur?" Och spara oss något mer sammanhang. Vi omfattade den delen mycket redan. Låt oss se vad RSpec har att erbjuda att du som nybörjare kan hantera direkt.
Så detta kommer att närma sig hjärtat av saker. RSpec ger dig massor av så kallade matchare. Det här är ditt bröd och smör när du skriver dina förväntningar. Hittills har du sett .till ekv
och .not_to eq
. Men det finns en mycket större arsenal för att skriva dina specs. Du kan testa för att höja fel, för truthy och falska värden, eller till och med för specifika klasser. Låt oss köra ner några alternativ för att komma igång:
.till ekv
.not_to eq
Detta tester för ekvivalens.
... det är en del smart beskrivning "förvänta sig (agent.enemy). Till eq 'Ernst Stavro Blofeld' förvänta (agent.enemy) .not_to eq 'Winnie Pooh' ände ...
För att hålla sakerna korta, packade jag två förväntade uttalanden inom en Det
blockera. Det är dock bra att bara testa en enda sak per test. Detta håller sakerna mycket mer fokuserat, och dina tester blir mindre sköra när du byter saker.
.att vara sant
.att vara sant
... det är en del smart beskrivning "förvänta sig (agent.hero?). Att vara troende att förvänta sig (enemy.megalomaniac?). Att vara sann ände ...
Skillnaden är det be_truthy
är sant när det inte är det noll
eller falsk
. Så det kommer att passera om resultatet inte är något av dessa två slags "sanna". .att vara sant
Å andra sidan accepterar endast ett värde som är Sann
och ingenting annat.
.att vara felfri
.att vara falsk
... det är en del smart beskrivning "förvänta sig (agent.coward?). Att vara falsen förvänta sig (enemy.megalomaniac?). Vara falsk ände ...
Liknande de två exemplen ovan, .att vara felfri
förväntar sig antingen a falsk
eller a noll
värde och .att vara falsk
kommer bara att göra en direkt jämförelse på falsk
.
.att vara
.to_not be_nil
Och sist men inte minst, testar detta exakt för noll
sig. Jag sparar dig exemplet.
.att matcha()
Jag hoppas att du redan hade nöjet att titta på reguljära uttryck. Om inte, det här är en sekvens av tecken som du kan definiera ett mönster som du sätter mellan två framåt snedstreck för att söka efter strängar. En regex kan vara mycket praktisk om du vill leta efter bredare mönster som du kan generalisera i ett sådant uttryck.
... det är en del smart beskrivning "förvänta sig (agent.number.to_i) .för att matcha (/ \ d 3 /) slutet ...
Antag att vi har att göra med agenter som James Bond, 007, som tilldelas tresiffriga nummer. Då kunde vi testa på det här sättet - primitivt här, förstås.
>
<
<=
> =
Jämförelser är mer användbara än man tror. Jag antar att exemplen nedan kommer att täcka vad du behöver veta.
... det är en del smart beskrivning ", förvänta dig (agent.number) att vara < quartermaster.number expect(agent.number).to be > m.number förväntar (agent.kill_count) .för att vara> = 25 förvänta sig (quartermaster.number_of_gadgets). att vara <= 5 end…
Nu får vi någonstans lite tråkig. Du kan också testa för klasser och typer:
.att vara av anställning
.att vara en
.att vara_an
... det är en del smart beskrivning 'gör uppdrag = Mission.create (namn:' Moonraker ') agent = Agent.create (namn:' 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 dummyexemplet ovan kan du se att en lista över agenter som är kopplade till ett uppdrag inte är av klass Ombud
men av Active :: Föreningar :: CollectionProxy
. Vad du borde ta bort från den här är att vi enkelt kan testa för klasser själva medan de är högt uttrycksfulla. .att vara en
och .att vara_an
gör en och samma sak. Du har båda alternativen tillgängliga för att hålla sakerna läsbara.
Testning av fel är också massivt bekvämt i RSpec. Om du är super fräsch för Rails och inte säker på vilka fel ramverket kan kasta på dig, kanske du inte känner behovet av att använda dessa - såklart, det är helt klart. I ett senare skede i din utveckling hittar du dem väldigt praktiska. Du har fyra sätt att hantera dem:
.att raise_error
Detta är det mest generiska sättet. Oavsett vilket fel som höjs kommer att kastas i ditt nät.
.att raise_error (ErrorClass)
På så sätt kan du ange exakt vilken klass felet ska komma ifrån.
.att raise_error (ErrorClass, "Något felmeddelande")
Detta är ännu mer fint kornat eftersom du inte bara nämner felets klass utan ett specifikt meddelande som ska kastas med felet.
.att raise_error ("Något felmeddelande)
Eller du nämner bara felmeddelandet själv utan felklassen. Den förväntade delen behöver skrivas lite annorlunda, dock - vi måste pakka in delen under text i ett kodblock själv:
... det är en del smart beskrivning "gör agent = Agent.create (namn:" James Bond ") förvänta sig agent.lady_killer?. Att raise_error (NoMethodError) förvänta sig double_agent.name .to raise_error (NameError) förvänta sig double_agent. namn .to raise_error ("Fel: Inga dubbelagenter runt") förvänta sig double_agent.name .to raise_error (NameError, "Error: No double agents around") slutar ...
.till att börja med
.till slutet med
Eftersom vi ofta handlar om samlingar när vi bygger webbapps, är det trevligt att ha ett verktyg för att titta på dem. Här lade vi till två agenter, Q och James Bond, och ville bara veta vem som kommer först och sist i samlingen av agenter för ett visst uppdrag - här Moonraker.
... det är en del smart beskrivning 'do moonraker = Mission.create (namn:' Moonraker ') bond = Agent.create (namn:' James Bond ') q = Agent.create (namn:' Q ') moonraker.agents << bond moonraker.agents << q expect(moonraker.agents).to start_with(bond) expect(moonraker.agents).to end_with(q) end…
.att inkludera
Den här är också till hjälp för att kontrollera innehållet i samlingar.
... det är en del smart beskrivning 'gör uppdrag = Mission.create (namn:' Moonraker ') bond = Agent.create (namn:' James Bond ') mission.agents << bond expect(mission.agents).to include(bond) end…
Dessa predikatkompatörer är en funktion i RSpec för att dynamiskt skapa matchare för dig. Om du har predikatmetoder i dina modeller, till exempel (slutar med ett frågetecken), vet RSpec att det ska bygga matchare för dig som du kan använda i dina test. I exemplet nedan vill vi testa om en agent är James Bond:
klassmäklare < ActiveRecord::Base def bond? name == 'James Bond' && number == '007' && gambler == true end… end
Nu kan vi använda detta i våra specifikationer som så:
... det är en smart beskrivning 'gör agent = Agent.create (namn:' James Bond ', nummer:' 007 ', gambler: true) expect (agent) .to be_bond avsluta det' lite smart beskrivning 'gör agent = Agent. skapa (namn: "James Bond") förvänta (agent) .not_to be_bond end ...
RSpec låter oss använda metodenamn utan frågetecken-för att skapa en bättre syntax, antar jag. Coolt, är det inte?
låta
och låta!
kan se ut som variabler först, men de är faktiskt hjälpar metoder. Den första utvärderas löjligt, vilket innebär att det bara körs och utvärderas när en spec faktiskt använder den, och den andra låter med bang (!) Körs oavsett om den används av en spec eller inte. Båda versionerna memoiseras och deras värden kommer att cachas inom samma exempelområde.
Beskriv uppdraget, "#prepare",: låt släppa (: uppdrag) Mission.create (namn: 'Moonraker') let! (: bond) Agent.create (namn: 'James Bond') agenter till ett uppdrag "förväntar sig mission.prepare (bond) (mission.agents)
Bangversionen som inte utvärderas löjligt kan vara tidskrävande och därför kostsamt om det blir din snygga nya vän. Varför? Eftersom det kommer att ställa upp dessa data för varje test i fråga, oavsett vad, och kanske så småningom hamnar en av dessa otäcka saker som saktar din testpaket avsevärt.
Du bör känna till denna funktion av RSpec sedan låta
är allmänt känd och används. Med det sagt kommer nästa artikel att visa några problem med det som du borde vara medveten om. Använd dessa hjälpar metoder med försiktighet, eller åtminstone i små doser för nu.
RSpec ger dig möjlighet att förklara ämnet som testas mycket explicit. Det finns bättre lösningar för detta, och vi kommer att diskutera nackdelarna med detta tillvägagångssätt i nästa artikel när jag visar några saker som du vanligtvis vill undvika. Men för nu, låt oss ta en titt på vad ämne
kan göra för dig:
beskriv agent, "#status" gör ämne Agent.create (namn: "Bond") det returnerar agenten status "förvänta sig (subject.status) .not_to vara" MIA "slutet
Detta tillvägagångssätt kan å ena sidan hjälpa dig att minska koddubbling, med en huvudperson som deklareras en gång i ett visst räckvidd, men det kan också leda till någonting som kallas en mystisk gäst. Detta innebär helt enkelt att vi kan hamna i en situation där vi använder data för en av våra testscenarier men har ingen aning längre var den faktiskt kommer från och vad den består av. Mer om det i nästa artikel.
Om du inte är medveten om återkallingar än, låt mig ge dig en kort uppfattning. Återuppringningar körs vid vissa specifika punkter i kodens livscykel. När det gäller Rails, skulle det innebära att du har kod som körs innan objekt skapas, uppdateras, förstörs, etc..
I samband med RSpec är det livscykeln för test som körs. Det innebär helt enkelt att du kan ange krokar som ska köras före eller efter varje test körs i specfilen, till exempel - eller helt enkelt runt varje test. Det finns några fler finkorniga alternativ tillgängliga, men jag rekommenderar att vi undviker att gå vilse i detaljerna för nu. Första saker först:
före (: vardera)
Denna återuppringning körs före varje testexempel.
beskriva agent, "#favorite_gadget" gör före (: varje) gör @gagdet = Gadget.create (namn: 'Walther PPK') avsluta det 'returnerar ett objekt, agentens favoritgränssnitt' gör agent = Agent.create (namn : "James Bond") agent.favorite_gadgets << @gadget expect(agent.favorite_gadget).to eq 'Walther PPK' end… end
Låt oss säga att du skulle behöva en viss gadget för varje test du kör i ett visst räckvidd. innan
kan du extrahera detta till ett block och förbereda det här lilla fragmentet för dig bekvämt. När du konfigurerar data på så vis måste du använda instansvariabler för att få tillgång till det bland olika områden.
Låt dig inte luras av bekvämlighet i det här exemplet. Bara för att du kan göra den här typen av saker betyder inte att du borde. Jag vill undvika att gå in i Antipatterns territorium och förvirra helvetet ur dig, men å andra sidan vill jag förklara nackdelarna för dessa enkla dummyövningar lite också.
I exemplet ovan skulle det vara mycket mer uttrycksfullt om du ställer in de nödvändiga objekten på en test-för-test-basis. Speciellt på större spec-filer kan du snabbt förlora syn på dessa små anslutningar och göra det svårare för andra att sammanfoga dessa pussel.
före (: alla)
Detta innan
block körs bara en gång före alla andra exempel i en spec fil.
beskriv agent, "#ememy" gör före (: alla) gör @main_villain = Villain.create (namn: 'Ernst Stavro Blofeld') @mission = Mission.create (namn: '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 kommer ihåg de fyra faserna i testet, innan
block är ibland hjälpsamma för att ställa upp något för dig som behöver upprepas regelbundet, förmodligen saker som är lite mer meta i naturen.
efter (: vardera)
och trots allt)
har samma beteende men körs helt enkelt efter att dina test har utförts. efter
används ofta för att städa upp dina filer, till exempel. Men jag tycker att det är lite tidigt att ta itu med det. Så begå det till minne, vet att det är där om du börjar behöva det och låt oss gå vidare för att utforska andra mer grundläggande saker.
Alla dessa återkallelser kan placeras strategiskt för att passa dina behov. Placera dem i någon beskriva
blockera räckvidd som du behöver köra dem-de behöver inte nödvändigtvis placeras ovanpå din specfil. De kan enkelt nästas inuti dina specifikationer.
Beskriv Agent gör före (: var) gör @ Mission = Mission.create (namn: 'Moonraker') @bond = Agent.create (namn: 'James Bond', nummer: '007') ände beskriva '#ememy' (: var och en) gör @main_villain = Villain.create (namn: '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 observera kan du placera återkallningsblock på något sätt som du vill, och gå så djupt som du behöver. Koden i återuppringningen kommer att utföras inom ramen för något beskrivet blockomfattning. Men lite råd: om du känner behovet av att boa för mycket och saker tycks bli lite rörigt och komplicerat, ompröva din inställning och överväga hur du kan förenkla testen och deras inställning. KYSS! Håll det enkelt, dumt. Också uppmärksamma hur snyggt detta läser när vi tvingar dessa test att misslyckas:
Misslyckanden: 1) Agent # fiende Double 0 Agent med tillhörande uppdrag återvänder den främsta fienden som agenten måste möta i sitt uppdrag. Failure / Error: expect (@ bond.enemy). Till eq 'Ernst Stavro Blofeld' förväntas: "Ernst Stavro Blofeld "fick:" Blofeld "2) Agent # fiende Lågnivå agent med tillhörande uppdragsresa ingen information om den viktigaste skurken inblandad Fel / Fel: förvänta (some_schmuck.enemy) .to eq 'Det ligger ovanför din paygrade!' förväntat: "Det är ovanför din paygrade!" fick: "Blofeld"
Låt oss också titta på vilka generatorer som tillhandahålls av RSpec för dig. Du har redan sett en när vi använde skenor generera rspec: installera
. Denna lilla fella gjorde att vi snabbt och enkelt skulle kunna konfigurera RSpec. Vad har vi mer?
rspec: modell
Vill du ha en annan dummy modell spec?
skenor generera rspec: modell another_dummy_model
skapa spec / models / another_dummy_model_spec.rb
Snabbt eller inte? Eller en ny specifikation för ett kontrollerstest, till exempel:
rspec: styrenheten
rails generera rspec: controller dummy_controller
spec / controllers / dummy_controller_controller_spec.rb
rspec: view
Samma fungerar för synpunkter, förstås. Vi kommer dock inte att testa några synpunkter på det. Specifikationer för visningar ger dig det minsta stödet för pengarna, och det är helt tillräckligt i förmodligen nästan alla scenarier för att indirekt testa dina åsikter via funktionstester.
Funktionstest är inte en specialitet av RSpec i sig och passar bättre för en annan artikel. Med det sagt, om du är nyfiken, kolla Capybara, vilket är ett utmärkt verktyg för den typen av saker. Det låter dig testa hela flöden som utövar flera delar av din app som kommer tillsammans - testa fullständiga funktioner samtidigt som du simulerar webbläsarupplevelsen. Till exempel, en användare som betalar för flera objekt i en kundvagn.
rspec: hjälpare
Samma generatorstrategi låter oss också placera en hjälper utan mycket krångel.
skenor generera rspec: hjälpar dummy_helper
skapa spec / helpers / dummy_helper_helper_spec.rb
Det dubbla helper_helper
del var inte en olycka. När vi ger det ett mer "meningsfullt" namn ser du att RSpec bara bifogas _hjälpare
på egen hand.
skenor generera rspec: hjälpar important_stuff
skapa spec / helpers / important_stuff_helper_spec.rb
Nej, den här katalogen är inte en plats att hämma dina värdefulla hjälpar metoder som kommer upp när du refactoring dina test. Dessa skulle gå under spec / stöd
, faktiskt. spec / hjälpare
är för de tester som du borde skriva för dina åsikt hjälpare-en hjälpar som bestämma datum
skulle vara ett vanligt exempel. Ja, fullständig test täckning av din kod bör också inkludera dessa hjälpar metoder. Bara för att de ofta verkar små och triviala betyder inte att vi ska förbise dem eller ignorera deras potential för buggar som vi vill fånga. Ju mer komplexa hjälpen faktiskt visar sig, desto mer anledning borde du behöva skriva en helper_spec
för det!
Bara om du börjar spela med det med en gång, kom ihåg att du behöver köra dina hjälpar metoder på en hjälpare
objekt när du skriver dina hjälptester för att kunna fungera. Så de kan bara exponeras med det här objektet. Något som det här:
beskriv '#set_date' do ... helper.set_date ... end ...
Du kan använda samma typ av generatorer för funktionsspecifikationer, integrationsspecifikationer och mailerspecifikationer. Dessa är utom vårt räckvidd för idag, men du kan begå dem till minne för framtida användning:
Specifikationerna som vi skapade via generatorn ovan är redo att gå, och du kan lägga till dina test där direkt. Låt oss ha en liten titt på skillnaden mellan specifikationer, men:
kräva 'rails_helper' RSpec.describe DummyModel, typ:: modell väntar "lägg till några exempel på (eller ta bort) # __ FILE__" avsluta
kräva 'rails_helper' RSpec.describe DummyControllerController, typ:: controller slutar
kräva 'rails_helper' RSpec.describe DummyHelperHelper, typ:: hjälpen väntar "lägg till några exempel på (eller ta bort) # __ FILE__" avsluta
Det behöver inte en wunderkind att räkna ut att de alla har olika typer. Detta :typ
RSpec-metadata ger dig möjlighet att skära och tippa dina tester över filstrukturer. Du kan rikta dessa tester lite bättre på det sättet. Säg att du vill ha någon typ av hjälpare som bara laddas för controllerspecifikationer, till exempel. Ett annat exempel är att du vill använda en annan katalogstruktur för specifikationer som RSpec inte förväntar sig. Att ha denna metadata i dina test gör det möjligt att fortsätta använda RSpec-supportfunktionerna och inte resa upp testpaketet. Så du är fri att använda vilken katalogstruktur som helst för dig om du lägger till det här :typ
metadata.
Din standard RSpec-test beror inte på den metadata, å andra sidan. När du använder dessa generatorer kommer de att läggas till gratis, men du kan helt undvika dem också om du inte behöver dem.
Du kan även använda denna metadata för filtrering i dina specifikationer. Säg att du har ett tidigare block som bara ska springa på modellspecifikationer, till exempel. Propert! För större testpaket kan det här vara mycket praktiskt en dag. Du kan filtrera vilken inriktad grupp av test du vill köra i stället för att köra hela sviten, vilket kan ta en stund.
Dina alternativ sträcker sig bortom de tre märkningsmöjligheterna ovan, förstås. Låt oss lära oss mer om skivning och dicing dina tester i nästa avsnitt.
När du samlar en större testpaket över tiden kommer det inte bara vara tillräckligt att köra test i vissa mappar för att snabbt och effektivt kunna köra RSpec-test. Vad du vill kunna göra är att köra test som hör samman men kan spridas över flera kataloger. Märkning till räddningen! Får inte fel, att organisera dina test smart i dina mappar är också viktigt, men taggning tar det bara lite längre.
Du ger dina tester några metadata som symboler som ": wip", ": checkout", eller vad som passar dina behov. När du kör dessa fokuserade testgrupper anger du bara att RSpec ska ignorera att köra andra tester den här gången genom att ge en flagga med namnet på taggarna.
beskriv agent, wip gör det "är en röra just nu" förvänta (agent.favorite_gadgets). till eq 'Unknown' slutet
rspec - tag wip
Fel: 1) Agent är en röra just nu Fel / Fel: Förvänta (agent.favorite_gadgets) .to eq 'Unknown' ...
Du kan också köra alla typer av test och ignorera ett gäng grupper som är märkta på ett visst sätt. Du ger bara en tilde (~) framför taggen namnet, och RSpec är glad att ignorera dessa tester.
rspec - tag ~ wip
Att köra flera taggar synkront är inte heller ett problem:
rspec - tag wip - tag checkout rspec - tag ~ wip --tag checkout
Som du kan se ovan kan du blanda och matcha dem efter vilja. Syntaxen är inte perfekt-repeterande --märka
är kanske inte idealisk-men hej, det är inte heller en biggie! Ja, allt detta är lite mer extra arbete och mentala kostnader när du komponerar specifikationerna, men på fliksidan ger det dig verkligen en kraftfull förmåga att skära upp din testpaket på begäran. På större projekt kan det spara dig en massa tid på det sättet.
Vad du har lärt dig hittills borde utrusta dig med absoluta grunderna för att spela med egna tester - en överlevnadssats för nybörjare. Och verkligen spela och göra misstag så mycket som möjligt. Ta RSpec och hela testdriven thingie för en tur och förvänta dig inte att skriva kvalitetsprov direkt. Det finns fortfarande ett par bitar saknas innan du kommer att känna dig bekväm och innan du kommer att vara effektiv med det.
För mig var det lite frustrerande i början eftersom det var svårt att se hur man testa någonting när jag inte hade genomfört det och förstod inte helt hur det skulle uppträda.
Testning visar verkligen om du förstår ett ramverk som Rails och vet hur bitarna passar ihop. När du skriver tester måste du kunna skriva förväntningar på hur en ram ska fungera.
Det är inte lätt om du bara börjar med allt detta. Hantera flera domänspecifika språk - här kan RSpec och Rails, till exempel-plus att lära Ruby API, vara förvirrande som helvete. Känn inte dåligt om inlärningskurvan verkar skrämmande; det blir lättare om du håller fast vid det. Att göra denna glödlampa gå av kommer inte att ske över natten, men för mig var det mycket värt ansträngningen.