RSpec-testning för nybörjare, del 2

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.

ämnen

  • matchers
  • Låta
  • ämnen
  • callbacks
  • generatorer
  • Tags

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.

matchers

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.

Några Spec

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

Uppmärksamhet!

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

Några Spec

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

Några Spec

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

Några Spec

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

Några Spec

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

Några Spec

... 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:

Några Spec

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

Vissa Agent Spec

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

Vissa Agent Spec

... 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… 
  • predikatorns matchare

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:

Agent Model

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å:

Vissa Agent Spec

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

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.

Några Spec File

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.

ämnen

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:

Några Spec File

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.

callbacks

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.

Några Spec File

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.

Uppmärksamhet!

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.

Några Spec File

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. 

Några Spec File

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:

Produktion

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"

generatorer

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?

Terminal

skenor generera rspec: modell another_dummy_model

Produktion

skapa spec / models / another_dummy_model_spec.rb

Snabbt eller inte? Eller en ny specifikation för ett kontrollerstest, till exempel:

  • rspec: styrenheten

Terminal

rails generera rspec: controller dummy_controller

Produktion

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.

Terminal

skenor generera rspec: hjälpar dummy_helper

Produktion

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.

Terminal

skenor generera rspec: hjälpar important_stuff

Produktion

skapa spec / helpers / important_stuff_helper_spec.rb

Ett ord om hjälpare

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:

Några hjälpar Spec

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:

  • rspec: mailer
  • rspec: funktion
  • rspec: integration

En titt på genererade specifikationer

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:

spec / modeller / dummy_model_spec.rb

kräva 'rails_helper' RSpec.describe DummyModel, typ:: modell väntar "lägg till några exempel på (eller ta bort) # __ FILE__" avsluta

spec / controllers / dummy_controller_controller_spec.rb

kräva 'rails_helper' RSpec.describe DummyControllerController, typ:: controller slutar

spec / hjälpare / dummy_helper_helper_spec.rb

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.

Tags

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.

Några Spec File

beskriv agent, wip gör det "är en röra just nu" förvänta (agent.favorite_gadgets). till eq 'Unknown' slutet

Terminal

rspec - tag wip

Produktion

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.

Terminal

rspec - tag ~ wip

Att köra flera taggar synkront är inte heller ett problem:

Terminal

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.

Slutgiltiga tankar

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.