Hvordan jeg tester

I en nylig diskusjon på Google+ kommenterte en venn av meg, "Testdrevet utvikling (TDD) og Behavior-Driven Development (BDD) er Ivory Tower BS."Dette førte til at jeg tenkte på mitt første prosjekt, hvordan jeg følte meg på samme måte da, og hvordan jeg føler om det nå. Siden det første prosjektet har jeg utviklet en rytme av TDD / BDD som ikke bare fungerer for meg, men også for klienten også.

Ruby on Rails leveres med en testpakke, kalt Test Unit, men mange utviklere foretrekker å bruke RSpec, Agurk, eller en kombinasjon av de to. Personlig foretrekker jeg sistnevnte, ved hjelp av en kombinasjon av begge.


RSpec

Fra RSpec-siden:

RSpec er et testverktøy for Ruby programmeringsspråk. Født under banner av Behavior-Driven Development, er den designet for å gjøre testdrevet utvikling en produktiv og hyggelig opplevelse.

RSpec gir en kraftig DSL som er nyttig for både enhet og integrasjonstesting. Selv om jeg har brukt RSpec for å skrive integreringstester, foretrekker jeg å bruke den bare i en enhetstestingskapasitet. Derfor vil jeg dekke hvordan jeg bruker RSpec utelukkende til enhetstesting. Jeg anbefaler å lese The RSpec Book av David Chelimsky og andre for fullstendig og grundig RSpec-dekning.


Agurk

Jeg har funnet fordelene med TDD / BDD langt oppover ulemper.

Agurk er et integrasjons- og aksepttestingsramme som støtter Ruby, Java, .NET, Flex, og en rekke andre webspråk og -rammer. Dens sanne kraft kommer fra sin DSL; Ikke bare er det tilgjengelig på vanlig engelsk, men det har blitt oversatt til over førti talte språk.

Med en menneskelig lesbar aksepttest kan du få kunden til å logge seg på en funksjon før du skriver en enkelt linje med kode. Som med RSpec, vil jeg bare dekke agurk i kapasiteten der jeg bruker den. For fullstendig rundown på agurk, sjekk ut The Agurk Book.


Oppsettet

La oss først starte et nytt prosjekt, instruere Rails for å hoppe over testenheten. Skriv inn følgende i en terminal:

 skinner nye how_i_test -T

Innen Gemfile, Legg til:

 kilde 'https: //rubygems.org'... gruppe: test gjør perle' capybara 'perle' agurk-rails ', krever: falsk perle' database cleaner 'perle' factory_girl_rails 'perle' shoulda 'endegruppe: utvikling, perle 'rspec-skinner' ende

Jeg bruker mest RSpec for å sikre at modellene og metodene deres holder seg i kontroll.

Her har vi satt Komkommer og venner inne i gruppen test blokkere. Dette sikrer at de bare er riktig lastet inn i Rails testmiljø. Legg merke til hvordan vi også laster RSpec innsiden av utvikling og test blokker, som gjør det tilgjengelig i begge miljøer. Det er noen andre perler. som jeg vil kort detaljere nedenfor. Ikke glem å løpe bunt installasjon å installere dem.

  • Capybara: simulerer nettleserinteraksjoner.
  • Database Cleaner: rydder databasen mellom testkjøringer.
  • Factory Girl Rails: Monteringsutstyr.
  • shoulda: hjelpemetoder og matchere for RSpec.

Vi må kjøre disse edelstenenes generatorer for å sette dem opp. Du kan gjøre det med følgende terminalkommandoer:

 skinner g rspec: installer skape .rspec lage spesifikke lage spec / spec_helper.rb skinner g agurk: installer skape config / cucumber.yml lage skript / agurk chmod script / agurk skape funksjoner / step_definitions lage funksjoner / support lage funksjoner / support / env. rb eksisterer lib / oppgaver lage lib / oppgaver / cucumber.rake gsub config / database.yml gsub config / database.yml force config / database.yml

På dette tidspunktet kan vi begynne å skrive spesifikasjoner og cukes for å teste applikasjonen vår, men vi kan sette opp noen ting for å gjøre testing enklere. La oss starte i application.rb fil.

 modul HowITest klasse Søknad < 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 / fabrikker' ende ... ende ende

I applikasjonsklassen overstyrer vi noen av Rails standardgeneratorer. For de to første, hopper vi over visningen og hjelpers generasjons spesifikasjoner.

Disse testene er ikke nødvendige, fordi vi bare bruker RSpec for enhetstester.

Den tredje linjen informerer Rails om at vi har til hensikt å bruke RSpec som vårt utvalgte valgramme, og det skal også generere inventar når generere modeller. Den endelige linjen sikrer at vi bruker factory_girl for våre inventar, som er opprettet i spec / fabrikker katalog.


Vår første funksjon

For å holde ting enkelt, skal vi skrive en enkel funksjon for å logge inn i vår søknad. For korthetens skyld vil jeg hoppe over den faktiske implementeringen og holde fast med testpakken. Her er innholdet i funksjoner / signing_in.feature:

 Funksjon: Logg inn For å kunne bruke programmet Som registrert bruker vil jeg logge inn via et skjema Scenario: Påmelding gjennom skjemaet Gitt det er en registrert bruker med e-post "[email protected]" Og jeg er på skiltet på side Når jeg skriver inn riktige legitimasjonsbeskrivelser Og jeg trykker på påloggings-knappen. Deretter skal meldingen bli "Signed in successfully."

Når vi kjører dette i terminalen med agurkfunksjoner / signering_in.feature, vi ser mye utgang som slutter med våre ubestemte trinn:

 Gitt / ^ Det er en registrert bruker med e-post "(. *?)" $ / Do | arg1 | ventende # uttrykk regexp ovenfor med koden du ønsker du hadde slutt Gitt / ^ Jeg er på siden for innlogging $ / gjør ventende # uttrykk regexp ovenfor med koden du ønsker du hadde slutt Når / ^ jeg oppgir riktig legitimasjon $ / ventende # uttrykk regexp ovenfor med koden du ønsker du hadde slutt Når / ^ Jeg trykker på innloggingsknappen $ / gjør ventende # uttrykk regexp ovenfor med koden du ønsker du hadde slutt Da / ^ skal flashmeldingen være " (. *?) "$ / do | arg1 | venter # uttrykk regexp ovenfor med koden du ønsker du hadde slutt

Det neste trinnet er å definere hva vi forventer at hver av disse trinnene skal gjøres. Vi uttrykker dette i funksjoner / stepdefinitions / signin_steps.rb, bruker vanlig Ruby med Capybara og CSS selectors.

 Gitt / ^ Det er en registrert bruker med e-post "(. *?)" $ / Do | email | @user = FactoryGirl.create (: bruker, e-post: e-post) slutt Gitt / ^ Jeg er på innloggingssiden $ / besøk sign_in_path-slutten Når / ^ skriver jeg inn riktige legitimasjonsdata $ / fyll på "Email", med: @user .email fillin "Passord", med: @ user.password end Når / ^ Jeg trykker på innloggingsknappen $ / do click_button "Logg inn" slutt Da / ^ skal flashmeldingen være "(. *?)" $ / do | tekst | innenfor (". flash") gjør side

Innenfor hver av gitt, Når, og Deretter blokker, bruker vi Capybara DSL til å definere hva vi forventer fra hver blokk (unntatt i den første). I den første oppgitte blokken forteller vi factory_girl å opprette en bruker lagret i bruker instansvariabel for senere bruk. Hvis du kjører agurkfunksjoner / signering_in.feature igjen, bør du se noe som ligner på følgende:

 Scenario: Logg inn via skjemaet # features / signing_in.feature: 6 Gitt det er en registrert bruker med e-post "[email protected]" # funksjoner / step_definitions / signering \ _in \ _steps.rb: 1 Fabrikk ikke registrert: bruker ( ArgumentError) ./features/step_definitions/signing\_in\_steps.rb:2:in '/ ^ Det er en registrert bruker med e-post "(. *?)" $ /' Features / signing_in.feature: 7: in 'Given Det er en registrert bruker med e-post "[email protected]" '

Vi kan se fra feilmeldingen at vårt eksempel mislykkes på linje 1 med en ArgumentError Brukerfabrikken er ikke registrert. Vi kan selv lage denne fabrikken, men noen av de magiske vi oppretter tidligere vil gjøre Rails gjøre det for oss. Når vi genererer vår brukermodell, får vi brukerfabrikken gratis.

 skinner g modell bruker e-post: streng passord: streng påkalle active_record lage db / migrate / 20121218044026 \ _create \ _users.rb lage app / models / user.rb påkalle rspec lage spec / models / user_spec.rb påkalle factory_girl lage spec / fabrikker / brukere .rb

Som du ser, benytter modellgeneratoren factory_girl og oppretter følgende fil:

ruby spec / fabrikker / users.rb FactoryGirl.define gjør fabrikken: bruker gjør e-post "MyString" passord "MyString" slutten

Jeg vil ikke gå inn i dybden av factory_girl her, men du kan lese mer i deres startveiledning. Ikke glem å løpe rake db: migrere og rake db: test: klargjøre for å laste det nye skjemaet. Dette bør få det første trinnet i vår funksjon å passere, og begynne deg på veien for å bruke agurk til integrasjonstesting. På hvert pass på dine funksjoner vil Gurka lede deg til brikkene som den ser mangler for å få det til å passere.


Modell Testing med RSpec og Shoulda

Jeg bruker mest RSpec for å sørge for at modellene og metodene deres holder seg i kontroll. Jeg bruker det ofte også til noen kontroller på høyt nivå, men det går inn i mer detalj enn denne veiledningen tillater. Vi skal bruke den samme brukermodellen som vi tidligere har konfigurert med vår påloggingsfunksjon. Ser tilbake på produksjonen fra å kjøre modellgeneratoren, vi kan se at vi også har user_spec.rb gratis. Hvis vi løper rspec spec / models / user_spec.rb vi bør se følgende utgang.

 Venter: Bruker legg til noen eksempler på (eller slett) /Users/janders/workspace/how\_i\_test/spec/models/user_spec.rb

Og hvis vi åpner den filen, ser vi:

 krever 'spechelper' beskrive brukeren venter "legg til noen eksempler på (eller slett) # FILE" slutten

Den ventende linjen gir oss produksjonen vi så i terminalen. Vi vil utnytte Shouldas ActiveRecord- og ActiveModel-kampanjer for å sikre at vår brukermodell samsvarer med vår forretningslogikk.

 krever 'spechelper' beskrive Bruker gjøre kontekst "#fields" gjør det skal svare (: email) det skal svare (: passord) det skal svare (: fornavn) det skal svare kontekst "#validations" gjør det skal validere_presence_of (: email) det skal validere_presence_of (: password) det skal validere_uniqueness_of (: email) ende kontekst "#associations" gjør det should have_many beskrive "#methods" la! (: bruker) FactoryGirl.create (: user) den "navnet skal returnere brukerens navn" gjør brukernavn.should eql "Testy McTesterson" slutten ende

Vi konfigurerer noen kontekstblokker inni vår første beskrive blokkere for å teste ting som felt, valideringer og foreninger. Mens det ikke er funksjonelle forskjeller mellom a beskrive og a kontekst blokkere, det er en kontekstuell en. Vi bruker beskrive blokker for å angi tilstanden til hva vi tester, og kontekst blokkerer for å gruppere disse testene. Dette gjør at våre tester blir mer lesbare og vedlikeholdbare i det lange løp.

Den første beskrive tillater oss å teste mot Bruker modell i en umodifisert tilstand.

Vi bruker denne umodifiserte tilstanden til å teste mot databasen med Shoulda-kampanjer gruppering hver etter type. Den neste beskrive blokk oppretter en bruker fra vår tidligere opprettet bruker fabrikk. Sette opp brukeren med la Metoden inne i denne blokken tillater oss å teste en forekomst av vår brukermodell mot kjente attributter.

Nå, når vi løper rspec spec / models / user_spec.rb, vi ser at alle våre nye tester mislykkes.

 Feil: 1) Brukernavn må returnere brukernavnet Feil / Feil: user.name.should eql "Testy McTesterson" NoMethodError: undefined metodenavn for # # ./spec/models/user_spec.rb:26:inblokkere (3 nivåer) i '2) Bruker # valideringer Feil / Feil: det skal validere_uniqueness_of (: email) Forventede feil å inkludere "har allerede blitt tatt" når e-post er satt til "vilkårligstreng ", fikk ingen feil # ./spec/models/userspec.rb: 15: i 'blokk (3 nivåer) i '3) Bruker # valideringer Feil / Feil: det skal validere_presence_of (: passord) Forventede feil å inkludere "kan ikke være tom" når passordet er satt til null, fikk ingen feil # ./spec/models/user_spec.rb : 14: i blokk (3 nivåer) i '4) Bruker # valideringer Feil / Feil: det skal validere_presence_of (: email) Forventede feil å inkludere "kan ikke være tom" når e-post er satt til null, fikk ingen feil # ./spec/models/user_spec.rb : 13: i blokk (3 nivåer) i '5) Bruker # foreninger Feil / Feil: det burde hamange (: oppgaver) Forventet bruker å ha en harmange foreninger kalt oppgaver (ingen tilknytning kalt oppgaver) # ./spec/models/user_spec.rb:19:in 'block (3 levels) in '6) Bruker # felt Feil / Feil: det skal svareå varenavn) forventet # å svare på: sistnavn # ./spec/models/userspec.rb: 9: i 'blokk (3 nivåer) i '7) Bruker # felt Feil / Feil: det skal svaretil (: førstnavn) forventet # å svare på: førstnavn # ./spec/models/userspec.rb: 8: i blokkere (3 nivåer) i '

Når hver av disse tester mangler, har vi det rammeverket vi trenger for å legge til migrasjoner, metoder, foreninger og valideringer til våre modeller. Etter hvert som vår applikasjon utvikler seg, utvider modellene og skjemaet endres, gir dette testnivået oss beskyttelse for å introdusere bryteendringer.


Konklusjon

Selv om vi ikke dekker for mange emner i dybden, bør du nå ha en grunnleggende forståelse av integrering og enhetstesting med agurk og RSpec. TDD / BDD er en av de tingene utviklerne enten synes å gjøre eller ikke gjør, men jeg har funnet ut at fordelene med TDD / BDD langt oppveier ulemper ved mer enn en anledning.