Mitt senaste arbete har varit på ett molnbaserat Ruby-projekt för BBC News, kommande 2014-val. Det kräver snabb I / O, skalbarhet och behöver testas väl. Kravet på "bli testat" är det jag vill fokusera på i denna handledning.
Detta projekt använder några olika Amazon-tjänster som:
Vi måste kunna skriva test som är snabba och ge oss omedelbar feedback om problem med vår kod.
Trots att vi inte kommer att använda Amazon-tjänster i den här handledningen nämner jag dem, för att vi måste ha tester som är snabba, det kräver oss att förfalska dessa externa objekt (till exempel, vi behöver inte ha en nätverksanslutning för att köra vår tester, eftersom det beroendet kan resultera i långsamma körprov).
Vid sidan av den tekniska ledaren Robert Kenny (som är mycket välbevandad i skrivande TDD-testbaserade utvecklingsbaserade Ruby-applikationer) har vi använt olika verktyg som har gjort denna process och vår programmering fungerar mycket enklare.
Jag vill dela lite information om dessa verktyg med dig.
De verktyg jag ska täcka är:
Jag antar att du är bekant med Ruby-kod och Ruby Eco-systemet. Till exempel, jag borde inte behöva förklara för dig vilka "pärlor" är eller hur vissa Ruby-syntax / koncept fungerar.
Om du är osäker, då innan du flyttar, skulle jag rekommendera att läsa en av mina andra inlägg på Ruby för att få dig snabbt.
Du kanske inte är bekant med Guard, men i huvudsak är det ett kommandoradsverktyg som använder Ruby för att hantera olika händelser.
Till exempel kan Guard meddela dig när särskilda filer har redigerats och du kan utföra en viss åtgärd baserat på vilken typ av fil eller händelse som avfyrades.
Detta är känt som en "task runner", du kanske har hört frasen förut, eftersom de är mycket användande i front-end / client-sidevärlden för tillfället (Grunt and Gulp är två populära exempel).
Anledningen till att vi använder Guard är att det bidrar till att återkopplingsslingan (när du gör TDD) blir mycket hårdare. Det tillåter oss att redigera våra testfiler, se ett fel test, uppdatera och spara vår kod och omedelbart se om den passerar eller misslyckas (beroende på vad vi skrev).
Du kan använda något som Grunt eller Gulp istället, men vi föredrar att använda dessa typer av arbetslöpare för hantering av front-end / client-side saker. För back-end / server-sida kod använder vi Rake and Guard.
RSpec, om du inte redan är medveten, är ett testverktyg för Ruby-programmeringsspråket.
Du kör dina test (med RSpec) via kommandoraden och jag ska visa hur du kan göra processen lättare genom att använda Rubys byggprogram, Rake.
Slutligen använder vi en annan Ruby-pärla som heter Pry vilket är ett extremt kraftfullt Ruby-felsökningsverktyg som sprutar sig in i din ansökan medan den körs, så att du kan inspektera din kod och ta reda på varför något inte fungerar.
Även om det inte är nödvändigt att visa användningen av RSpec och Guard, är det värt att notera att jag fullt ut stöder användningen av TDD som ett sätt att säkerställa att varje kodlinje du skriver har ett syfte och har utformats på ett testbart och pålitligt sätt.
Jag ska redogöra för hur vi skulle göra TDD med en enkel applikation, så i alla fall får du en känsla för hur processen fungerar.
Jag har skapat ett grundläggande exempel på GitHub för att rädda dig från att skriva ut allt själv. Känn dig fri att ladda ner koden.
Låt oss nu gå vidare och granska det här projektet, steg för steg.
Det finns tre primära filer som krävs för att vår exemplarapplikation ska fungera, det här är:
Gemfile
Guardfile
Rakefile
Vi går snart över innehållet i varje fil, men det första vi behöver göra är att få vår katalogstruktur på plats.
För vårt exempelprojekt behöver vi två mappar skapade:
lib
(det här håller vår programkod)spec
(det här håller vår testkod)Detta är inte ett krav för din ansökan, du kan enkelt tweak koden i våra andra filer för att fungera med vilken struktur som helst som passar dig.
Öppna din terminal och kör följande kommando:
geminstallationsbuntaren
Bundler är ett verktyg som gör det enklare att installera andra pärlor.
När du har kört det här kommandot skapar du ovanstående tre filer (Gemfile
, Guardfile
och Rakefile
).
De Gemfile
ansvarar för att definiera en lista över beroenden för vår ansökan.
Så här ser det ut:
källa "https://rubygems.org" gem 'rspec' grupp: utveckling gör pärla 'vakt' pärla 'guard-rspec' pärla 'pry' slutet
När filen är sparad kör du kommandot buntinstallation
.
Detta kommer att installera alla våra ädelstenar för oss (inklusive de ädelstenar som anges i utveckling
grupp).
Syftet med utveckling
grupp (som är en buntningsspecifik egenskap) är så när du distribuerar din applikation kan du berätta för din produktionsmiljö att bara installera de pärlor som krävs för att din ansökan ska fungera korrekt.
Så till exempel, alla pärlor i utveckling
grupp, krävs inte för att programmet ska fungera korrekt. De används endast för att hjälpa oss medan vi utvecklar och testar vår kod.
För att installera lämpliga ädelstenar på din produktionsserver, skulle du behöva köra något som:
buntinstallation - utan utveckling
De Rakefile
kommer att tillåta oss att köra våra RSpec-tester från kommandoraden.
Så här ser det ut:
kräver 'rspec / core / rake_task' RSpec :: Kärna :: RakeTask.new do | task | task.rspec_opts = ['- color', '- format', 'doc'] slutet
Notera: du behöver inte Guard för att kunna köra dina RSpec test. Vi använder Guard för att göra det lättare att göra TDD.
När du installerar RSpec ger du tillgång till en inbyggd Rake-uppgift och det är vad vi använder här.
Vi skapar en ny instans av RakeTask
som som standard skapar en uppgift som heter spec
som kommer att leta efter en mapp som heter spec
och kör alla testfilerna i den mappen, med hjälp av de konfigurationsalternativ som vi har definierat.
I det här fallet vill vi att våra skalutdata har färg och vi vill formatera utmatningen till doc
stil (du kan ändra formatet för att vara kapslade
som ett exempel).
Du kan konfigurera Rake-uppgiften att fungera som du vill och titta på olika kataloger, om det är vad du har. Men standardinställningarna fungerar bra för vår applikation och så är det vad vi ska använda.
Nu om jag vill köra testen i mitt exempel GitHub-arkiv, måste jag öppna min terminal och köra kommandot:
rake spec
Detta ger oss följande produktion:
rake spec / bin / ruby -S rspec ./spec/example_spec.rb --färg --format doc RSpecGreeter RSpecGreeter # greet () Avslutad på 0,0006 sekunder 1 exempel, 0 fel
Som ni kan se finns det inga fel. Det beror på att även om vi inte har någon ansökningskod skrivit, har vi inte heller någon testkod som har skrivits heller.
Innehållet i den här filen berättar Guard vad du ska göra när vi kör vakt
kommando:
guard 'rspec' gör # watch / lib / files watch (% r ^ lib /(.+). rb $) gör | m | "spec / # m [1] _spec.rb" slutet # watch / spec / files watch (% r ^ spec /(.+). rb $) gör | m | "spec / # m [1]. rb" änden
Du har märkt inom vår Gemfile
Vi angav pärlan: vakt-rspec
. Vi behöver den pärlan för att tillåta Guard att förstå hur man hanterar ändringar i RSpec-relaterade filer.
Om vi tittar igen på innehållet kan vi se om vi sprang skydda rspec
då Guard skulle titta på de angivna filerna och exekvera de angivna kommandona när någon ändring av dessa filer hade inträffat.
Notera: för att vi bara har en vaktuppgift, rspec
, då körs det som standard om vi körde kommandot vakt
.
Du kan se Guard ger oss en Kolla på
funktion som vi passerar en vanlig uttryck för att vi ska kunna definiera vilka filer vi är intresserade av att titta på.
I det här fallet berättar vi Guard för att titta på alla filer i vårt lib
och spec
mappar och om några ändringar förekommer i någon av dessa filer då att utföra testfilerna inom vår spec
mapp för att se till att inga ändringar som vi gjorde bröt våra test (och bröt sedan inte vår kod).
Om du har alla filer som laddas ner från GitHub repo kan du prova kommandot själv.
Springa vakt
och spara sedan en av filerna för att se att den kör testen.
Innan vi börjar titta på någon test- och applikationskod, låt mig förklara vad vår ansökan ska göra. Vår ansökan är en enda klass som returnerar ett hälsningsmeddelande till den som kör koden.
Våra krav är avsiktligt förenklade, eftersom det kommer att göra processen som vi ska göra lättare att förstå.
Låt oss nu titta på en exempelspecifikation (till exempel vår testfil) som beskriver våra krav. Därefter börjar vi gå igenom den kod som definieras i specifikationen och se hur vi kan använda TDD för att hjälpa oss att skriva vår ansökan.
Vi ska skapa en fil med titeln example_spec.rb
. Syftet med den här filen är att bli vår specifikationsfil (med andra ord, detta kommer att bli vår testkod och representerar den förväntade funktionaliteten).
Anledningen till att vi skriver vår testkod innan du skriver vår faktiska ansökningskod är att det i sista hand innebär att alla programkod vi producerar kommer att existera eftersom det faktiskt användes.
Det är en viktig punkt jag gör och så låt mig ta en stund för att förtydliga det mer detaljerat.
Typiskt, om du skriver din ansökningskod först (så du inte gör TDD), så hittar du dig själv att skriva kod som vid någon tidpunkt i framtiden är överkonstruerad och potentiellt föråldrad. Genom processen att refactoring eller ändra krav kan du upptäcka att vissa funktioner aldrig kommer att ringas.
Därför anses TDD vara den bättre praxis och den föredragna utvecklingsmetoden att använda, eftersom varje kodlinje du producerar kommer att ha producerats av en anledning: att få en felaktig specifikation (ditt faktiska företagskrav) att passera. Det är en mycket kraftfull sak att tänka på.
Här är vår testkod:
kräva 'spec_helper' beskriv 'RSpecGreeter' gör det 'RSpecGreeter # greet ()' greeter = RSpecGreeter.new # Gett hälsning = greeter.greet # När hälsning.should eq ('Hello RSpec!') # Sedan avsluta slutet
Du kanske märker kodkommentarerna i slutet av varje rad:
Given
När
Sedan
Dessa är en form av BDD (Beteende-Driven utveckling) terminologi. Jag inkluderade dem för läsare som är mer bekanta med BDD (Behavior-Driven Development) och som var intresserade av hur de kan jämföra dessa uttalanden med TDD.
Det första vi gör inom den här filen är belastning spec_helper.rb
(som finns i samma katalog som vår spec fil). Vi kommer tillbaka och titta på innehållet i den filen på ett ögonblick.
Därefter har vi två kodblock som är specifika för RSpec:
beskriv "x" gör
det gör det
Den första beskriva
block ska beskriva den specifika klass / modul vi arbetar på och tillhandahålla test för. Du kan mycket väl ha flera beskriva
block i en enda specifikationsfil.
Det finns många olika teorier om hur man använder beskriva
och Det
beskrivning block. Jag föredrar personligen enkelhet och så använder jag identifierarna för den klass / moduler / metoder som vi ska testa. Men du hittar ofta några personer som föredrar att använda hela meningar för deras beskrivningar. Varken är rätt eller fel, bara personlig preferens.
De Det
block är annorlunda och bör alltid placeras inuti a beskriva
blockera. Det borde förklara på vilket sätt vi vill att vår ansökan ska fungera.
Återigen kan du använda en normal mening för att beskriva kraven, men jag har funnit att det ibland kan göra att beskrivningarna blir alltför explicit när de verkligen borde vara mer implicit. Att vara mindre explicit minskar risken för förändringar i din funktionalitet, vilket gör att din beskrivning blir föråldrad (måste uppdatera din beskrivning varje gång mindre funktionsändringar uppstår är mer av en börda än en hjälp). Genom att använda identifieraren för metoden som vi testar (till exempel namnet på den metod som vi kör) kan vi undvika det problemet.
Innehållet i Det
block är koden vi ska testa.
I ovanstående exempel skapar vi en ny instans av klassen RSpecGreeter
(som inte existerar än). Vi skickar meddelandet hälsa
(som inte existerar ännu) till det skapade instantierade objektet (notera: dessa två linjer är standard Ruby-kod vid denna tidpunkt).
Slutligen berättar vi testramen att vi förväntar oss resultatet av att ringa hälsa
metod för att vara texten "Hej RSpec!
", genom att använda RSpec-syntaxen: ekv (något)
.
Lägg märke till hur syntaxen gör det lätt att läsa (även av en icke-teknisk person). Dessa är kända som påståenden.
Det finns många olika RSpec påståenden och vi kommer inte att gå in i detaljerna, men gärna granska dokumentationen för att se alla funktioner som RSpec tillhandahåller.
Det krävs en viss mängd pannplattor för att våra tester ska kunna köras. I det här projektet har vi bara en specifikationsfil men i ett verkligt projekt kommer du sannolikt att ha dussintals (beroende på storleken på din ansökan).
För att hjälpa oss att minska kedjekodskoden placerar vi den inuti en speciell hjälparfil som vi laddar från våra specifikationsfiler. Den här filen kommer att vara titeln spec_helper.rb
.
Den här filen kommer att göra ett par saker:
bända
pärla (hjälper oss att felsöka vår kod, om vi behöver).Här är koden:
$ << File.join(File.dirname(FILE), '… ', 'lib') require 'pry' require 'example'
Notera: första raden kan se lite kryptisk ut, så låt mig förklara hur det fungerar. Här säger vi att vi vill lägga till / Lib /
mapp till Ruby s $ LOAD_PATH
systemvariabel. När Ruby utvärderar kräver "some_file"
den har en lista över kataloger som den kommer att försöka hitta den filen. I det här fallet ser vi till att om vi har koden kräva "exempel"
att Ruby kommer att kunna hitta det eftersom det kommer att kolla vårt / Lib /
katalog och där hittar den filen som anges. Det här är ett smart trick du kommer att se används i många Ruby-ädelstenar, men det kan vara ganska förvirrande om du aldrig sett det tidigare.
Vår programkod kommer att vara inne i en fil med titeln example.rb
.
Innan vi börjar skriva någon ansökningskod, kom ihåg att vi gör detta projekt TDD. Så vi ska låta testen i vår specifikationsfil guide oss om vad vi ska göra först.
Låt oss börja med att anta att du använder vakt
att köra dina tester (så varje gång vi byter till example.rb
, Vakten kommer att märka förändringen och fortsätta att springa example_spec.rb
för att se till att våra tester passerar).
För oss att göra TDD ordentligt, vår example.rb
filen kommer att vara tom och så om vi öppnar filen och "spara" den i sitt nuvarande tillstånd, så kommer Guard att springa och vi kommer att upptäcka (otroligt) att vårt test kommer att misslyckas:
Fel: 1) RSpecGreeter RSpecGreeter # greet () Fel / Fel: greeter = RSpecGreeter.new # Given NameError: oinitialiserad konstant RSpecGreeter # ./spec/example_spec.rb:5:inblock (2 nivåer) i 'Avslutad i 0,00059 sekunder 1 exempel, 1 fel Misslyckades exempel: rspec ./spec/example_spec.rb:4 # RSpecGreeter RSpecGreeter # greet ()
Nu innan vi går längre, låt mig klargöra igen att TDD bygger på förutsättningen att varje kodlinje har en anledning att existera, så inte börja racing framåt och skriva ut mer kod än vad du behöver. Skriv bara den minsta mängd kod som krävs för att testet ska passera. Även om koden är ful eller uppfyller inte den fullständiga funktionaliteten.
Poängen med TDD är att ha en tight Återkopplingsslinga, även känd som "röd, grön, refaktor"). Vad detta innebär i praktiken är:
Du ser på ett ögonblick att eftersom våra krav är så enkla behöver vi inte refactor. Men i ett verkligt projekt med mycket mer komplexa krav måste du troligtvis ta det tredje steget och refactor koden du skrev in för att få provet att passera.
Kommer tillbaka till vårt misslyckade test, som du kan se i felet finns det ingen RSpecGreeter
klass definieras. Låt oss fixa det och lägg till följande kod och spara filen så att våra test körs:
klass RSpecGreeter # kod kommer så småningom att gå här slutet
Detta kommer att resultera i följande fel:
Fel: 1) RSpecGreeter RSpecGreeter # greet () Fel / Fel: greeter = greeter.greet # När NoMethodError: undefined methodgreet 'för # # ./spec/example_spec.rb:6:in' block (2 levels) i 'Färdig 0,00036 sekunder 1 exempel, 1 fel
Nu kan vi se att detta fel berättar för oss metoden hälsa
existerar inte, så låt oss lägga till det och spara sedan vår fil igen för att köra våra tester:
klass RSpecGreeter def greet # kod kommer så småningom att gå hit i slutet
OK, vi är nästan där. Felet vi får nu är:
Fel: 1) RSpecGreeter RSpecGreeter # greet () Fel / Fel: greeter = greeting.should eq ('Hello RSpec!') # Då förväntat: "Hej RSpec!" fick: noll (jämfört med ==) # ./spec/example_spec.rb:7:in 'block (2 levels) i' Avslutad i 0,00067 sekunder 1 exempel, 1 fel
RSpec berättar för oss att det förväntade sig att se Hej RSpec!
men istället fick den noll
(eftersom vi definierade hälsa
metod men definierade faktiskt inte något inom metoden och så återvänder det noll
).
Vi lägger till det återstående kodstycket som skulle få vårt test att passera:
klass RSpecGreeter def hälsar "Hej RSpec!" änden
Där har vi det, ett godkänd test:
Avslutad i 0,00061 sekunder 1 exempel, 0 fel
Vi är färdiga här. Vårt test är skrivet och koden passerar.
Hittills har vi tillämpat en testdriven utvecklingsprocess för att bygga vår applikation, tillsammans med den populära RSpec-testramen.
Vi ska lämna den här för nu. Kom tillbaka och följ med oss för del två där vi ska titta på fler RSpec-specifika funktioner och använda Pry-pärlan för att hjälpa dig att felsöka och skriva din kod.