Hur jag testar

I en ny diskussion om Google+ kommenterade en kompis av mig, "Testdriven utveckling (TDD) och beteendedriven utveckling (BDD) är Ivory Tower BS."Detta ledde till att jag tänkte på mitt första projekt, hur jag kände på samma sätt då och hur jag känner mig om det nu. Sedan det första projektet har jag utvecklat en rytm av TDD / BDD som inte bara fungerar för mig, utan också för kunden också.

Ruby on Rails skickas med en testpaket, kallad Test Unit, men många utvecklare föredrar att använda RSpec, Gurka eller någon kombination av de två. Personligen föredrar jag den senare, med en kombination av båda.


RSpec

Från RSpec-webbplatsen:

RSpec är ett testverktyg för Rubys programmeringsspråk. Född under banan av beteendedriven utveckling, är den utformad för att göra testdriven utveckling en produktiv och trevlig upplevelse.

RSpec ger en kraftfull DSL som är användbar för både enhet och integrationstestning. Medan jag har använt RSpec för att skriva integrationsprov, föredrar jag att använda den endast i en testkapacitet för enhet. Därför kommer jag att täcka hur jag använder RSpec exklusivt för enhetstestning. Jag rekommenderar att du läser The RSpec Book av David Chelimsky och andra för fullständig och djupgående RSpec-täckning.


Gurka

Jag har funnit fördelarna med TDD / BDD betydligt större än nackdelarna.

Gurka är ett integrations- och acceptanstestramverk som stöder Ruby, Java, .NET, Flex och en mängd andra webbspråk och ramverk. Dess sanna kraft kommer från sin DSL; Det är inte bara tillgängligt på vanlig engelska, men det har översatts till över 40 talade språk.

Med ett personligt läsbart godkännande test kan du få kunden att logga ut på en funktion innan du skriver en enda kodlinje. Som med RSpec kommer jag bara att täcka Gurka i den kapacitet som jag använder den. För fullständig rundown på gurka, kolla in gurka boken.


Upplägget

Låt oss först börja ett nytt projekt, instruera Rails att hoppa över testenheten. Skriv följande i en terminal:

 skenor new how_i_test -T

Inom Gemfile, Lägg till:

 källkod 'https: //rubygems.org'... grupp: test göra pärla' capybara 'pärla' gurka-skenor ', kräver: falsk pärla' databas renare 'pärla' factory_girl_rails 'pärla' shoulda 'slutgrupp: utveckling, pärla "rspec-rails" slutet

Jag använder mest RSpec för att se till att mina modeller och deras metoder hålls i kontroll.

Här har vi lagt gurka och vänner inuti gruppen testa blockera. Detta säkerställer att de bara laddas korrekt i Rails testmiljö. Lägg märke till hur vi också laddar RSpec inuti utveckling och testa block, vilket gör det tillgängligt i båda miljöerna. Det finns några andra pärlor. som jag kortfattat kommer att beskriva nedan. Glöm inte att springa buntinstallation att installera dem.

  • Capybara: simulerar webbläsarens interaktioner.
  • Databasrengörare: rensar databasen mellan testkörningar.
  • Factory Girl Rails: Byte av fixtur.
  • Shoulda: hjälpar metoder och matchare för RSpec.

Vi måste köra dessa ädelstenar generatorer för att ställa upp dem. Du kan göra det med följande terminalkommandon:

 rails g rspec: installera skapa .rspec skapa spec skapa spec / spec_helper.rb rails g gurka: installera skapa config / cucumber.yml skapa script / gurka chmod script / gurka skapa funktioner / step_definitions skapa funktioner / support skapa funktioner / support / env. rb finns lib / uppgifter skapa lib / uppgifter / cucumber.rake gsub config / database.yml gsub config / database.yml force config / database.yml

Vid denna tidpunkt kunde vi börja skriva specs och cukes för att testa vår ansökan, men vi kan ställa upp några saker för att göra testningen enklare. Låt oss börja i application.rb fil.

 modul HowITest class Application < Rails::Application config.generators do |g| g.view_specs false g.helper_specs false g.test_framework :rspec, :fixture => sant g.fixture_replacement: factory_girl,: dir => 'spec / factories' slutet ... slutet

Inom applikationsklassen åsidosätter vi några av Rails standardgeneratorer. För de första två, hoppar vi över visningarna och hjälparnas generationsspecifikationer.

Dessa test är inte nödvändiga, eftersom vi endast använder RSpec för enhetsprov.

Den tredje raden informerar Rails om att vi avser att använda RSpec som vårt val av testram, och det ska också skapa matchningar vid generering av modeller. Slutlinjen ser till att vi använder factory_girl för våra armaturer, vilka är skapade i spec / fabriker katalog.


Vår första funktion

För att hålla det enkelt ska vi skriva en enkel funktion för att logga in i vår ansökan. För korthetens skull kommer jag att hoppa över det faktiska genomförandet och hålla fast vid testpaketet. Här är innehållet i funktioner / signing_in.feature:

 Funktion: Inloggning För att kunna använda programmet Som registrerad användare vill jag logga in via ett formulär Scenario: Logga in via formuläret Med tanke på att det finns en registrerad användare med email "[email protected]" Och jag är på skylten på sidan När jag skriver in rätt uppgifter och jag trycker på inloggningsknappen Då ska blixtmeddelandet "Signed in successfully."

När vi kör detta i terminalen med gurka funktioner / signing_in.feature, vi ser mycket utdata som slutar med våra odefinierade steg:

 Givet / ^ Det finns en registrerad användare med e-post "(. *?)" $ / Do | arg1 | väntar # uttrycka regexp ovan med koden du önskar att du hade slutat med / ^ Jag är på inloggningssidan $ / väntar # uttrycka regexp ovan med koden du önskar att du hade slutet När / ^ Jag anger rätt behörighetsuppgifter $ / väntar # uttrycka regexp ovan med koden du önskar att du hade slutet När / ^ Jag trycker på inloggningsknappen $ / gör väntar # uttrycka regexp ovan med koden du önskar att du hade slutet Då / ^ ska blixtmeddelandet vara " (. *?) "$ / do | arg1 | väntar # uttrycka regexp ovan med koden du önskar att du hade slut

Nästa steg är att definiera vad vi förväntar oss av var och en av dessa steg att göra. Vi uttrycker detta i funktioner / stepdefinitions / signin_steps.rb, använder vanlig Ruby med Capybara och CSS selectors.

 Givet / ^ Det finns en registrerad användare med email "(. *?)" $ / Do | email | @user = FactoryGirl.create (: användare, email: email) end Given / ^ Jag är på inloggningssidan $ / gör visit sign_in_path slutet När / ^ skriver jag in rätt credentials $ / do fillin "Email" med: @user .email fillin "Password", med: @ user.password end När / ^ Jag trycker på inloggningsknappen $ / do click_button "Logga in" avsluta Då / ^ flashmeddelandet ska vara "(. *?)" $ / do | text | inom (". flash") gör page.should have_content textändänden

Inom varje av Given, När, och Sedan block använder vi Capybara DSL för att definiera vad vi förväntar oss från varje block (förutom i den första). I det första angivna blocket berättar vi factory_girl att skapa en användare lagrad i användare instansvariabel för senare användning. Om du kör gurka funktioner / signing_in.feature igen bör du se något som liknar följande:

 Scenario: Inloggning via formuläret # features / signing_in.feature: 6 Eftersom det finns en registrerad användare med email "[email protected]" # features / step_definitions / signing \ _in \ _steps.rb: 1 Fabriks inte registrerad: användare ( ArgumentError) ./features/step_definitions/signing\_in\_steps.rb:2:in '/ ^ Det finns en registrerad användare med email "(. *?)" $ /' Features / signing_in.feature: 7: in 'Given Det finns en registrerad användare med email "[email protected]" "

Vi kan se från felmeddelandet att vårt exempel misslyckas på rad 1 med en Argument av användarfabriken är inte registrerad. Vi skulle kunna skapa denna fabrik oss själva, men några av de magiker vi installerat tidigare gör att Rails gör det för oss. När vi genererar vår användarmodell får vi användarfabriken gratis.

 rails g modell användar email: stränglösenord: sträng påkalla active_record skapa db / migrate / 20121218044026 \ _create \ _users.rb skapa app / models / user.rb åberopa rspec skapa spec / models / user_spec.rb åberopa factory_girl skapa spec / fabriker / användare .RB

Som du kan se kallar modellgenerern factory_girl och skapar följande fil:

ruby spec / fabriker / users.rb FactoryGirl.define gör fabrik: användarnamn "MyString" lösenord "MyString" slutet

Jag kommer inte att gå in på djupet factory_girl här, men du kan läsa mer i sin startguide. Glöm inte att springa rake db: migrera och rake db: test: förbereda att ladda det nya schemat. Detta borde få det första steget i vår funktion att passera, och börja dig på väg att använda Gurka för din integrationsprovning. På varje pass av dina funktioner leder gurka dig till de bitar som den ser sakna för att få det att passera.


Modellprovning med RSpec och Shoulda

Jag använder mest RSpec för att se till att mina modeller och deras metoder hålls i kontroll. Jag brukar också använda den för en del kontroller på hög nivå, men det går mer i detalj än den här guiden tillåter. Vi kommer att använda samma användarmodell som vi tidigare skapat med vår inloggningsfunktion. Om vi ​​ser tillbaka på utgången från att köra modellgeneratorn kan vi se att vi också har user_spec.rb gratis. Om vi ​​kör rspec spec / models / user_spec.rb vi borde se följande produktion.

 Väntar: Användare lägg till några exempel på (eller ta bort) /Users/janders/workspace/how\_i\_test/spec/models/user_spec.rb

Och om vi öppnar den filen ser vi:

 kräva "spechelper" beskriv Användare väntar "lägg till några exempel till (eller ta bort) # FILE" slutet

Den väntade linjen ger oss den produktion vi såg i terminalen. Vi utnyttjar Shouldas ActiveRecord- och ActiveModel-matchare för att säkerställa att vår användarmodell matchar vår affärslogik.

 kräva 'spechelper' beskriv Användare gör sammanhang "#fields" gör det ska svara (: email) det ska svara (: lösenord) det ska svara (: förnamn) det ska svara (: efternamn) avsluta kontext "#validations" gör det bör validera_presence_of (: email) det bör validera_presence_of (: password) det bör validera_uniqueness_of (: email) slutet sammanhang "#associations" gör det should have_many (: tasks) avsluta beskriva "#methods" låt! (: användare) FactoryGirl.create (: user) det "namn ska returnera användarnamnet" gör användarnamn.namn.should eql "Testy McTesterson" ändänden

Vi ställer in några kontextblock inuti vårt första beskriva blockera för att testa saker som fält, valideringar och föreningar. Medan det inte finns funktionella skillnader mellan a beskriva och a sammanhang blockera, det finns en kontextuell en. Vi använder beskriva block för att ställa in tillståndet för vad vi testar, och sammanhang blockerar för att gruppera dessa test. Detta gör våra tester mer läsliga och underhållbara på lång sikt.

Den första beskriva tillåter oss att testa mot Användare modell i omodifierat tillstånd.

Vi använder detta omodifierade tillstånd för att testa mot databasen med Shoulda-matcharna som grupperar var och en per typ. Nästa beskriva block skapar en användare från vår tidigare skapade användare fabrik. Ställa in användaren med låta Metoden inuti detta block gör att vi kan testa en förekomst av vår användarmodell mot kända attribut.

Nu när vi kör rspec spec / models / user_spec.rb, vi ser att alla våra nya test misslyckas.

 Fel: 1) Användarnamn: Metodnamn ska returnera användarnamnet Fel / Fel: Användarnamn. Skal eql "Testy McTesterson" NoMethodError: odefinierat metodnamn "för # # ./spec/models/user_spec.rb:26:inblock (3 nivåer) i '2) Användare # valideringar Fel / Fel: det bör validera_uniqueness_of (: email) Förväntad fel att inkludera "har redan tagits" när e-post är inställd på "godtyckligsträng ", fick inga fel # ./spec/models/userspec.rb: 15: i block (3 nivåer) i "3) Användare # valideringar Fel / Fel: det bör validera_presence_of (: password) Förväntad fel att inkludera" kan inte vara tom "när lösenordet är inställt på noll, fick inga fel # ./spec/models/user_spec.rb : 14: i block (3 nivåer) i "4) Användare # valideringar Fel / Fel: det bör validera_presence_of (: email) Förväntad fel att inkludera" kan inte vara tom "när e-post är inställd på noll, fick inga fel # ./spec/models/user_spec.rb : 13: i block (3 nivåer) i '5) Användare # föreningar Fel / Fel: det borde hamånga (: uppgifter) Förväntad användare att ha ett harmånga föreningar som heter uppgifter (ingen sammanslutning som heter uppgifter) # ./spec/models/user_spec.rb:19:in 'block (3 levels) in '6) Användare # fält Fel / Fel: det ska svarahållanamn) förväntat # att svara på: sistnamn # ./spec/models/userspec.rb: 9: i "block (3 levels) in '7) Användare # fält Fel / Fel: det ska svaratill (: förstanamn) förväntat # att svara på: förstnamn # ./spec/models/userspec.rb: 8: i block (3 nivåer) i '

När alla dessa test misslyckas har vi det ramverk vi behöver för att lägga till migreringar, metoder, föreningar och valideringar till våra modeller. När vår applikation utvecklas, modellerna utökas och vårt schema ändras, ger denna nivå av test oss skydd för att införa brytningsändringar.


Slutsats

Medan vi inte täckte för många ämnen i djupet, borde du nu ha en grundläggande förståelse för integration och enhetstestning med Gurka och RSpec. TDD / BDD är en av de saker som utvecklare antingen verkar göra eller inte gör, men jag har funnit att fördelarna med TDD / BDD överstiger nackdelarna mer än ett tillfälle.