Ruby for Newbies Testing med Rspec

Ruby er et av de mest populære språkene som brukes på nettet. Vi driver en sesjon her på Nettuts + som vil introdusere deg til Ruby, i tillegg til de store rammeverkene og verktøyene som følger med Ruby-utviklingen. I denne episoden lærer du om å teste din Ruby-kode med Rspec, en av de beste testbiblioteker i virksomheten.


Foretrekker en Screencast?

Ser kjent ut?

Hvis du har lest min siste opplæring på JasmineJS, vil du sannsynligvis legge merke til flere likheter i Rspec. Faktisk er likhetene i Jasmine: Jasmine ble opprettet med Rspec i tankene. Vi skal se på hvordan du kan bruke Rspec til å gjøre TDD i Ruby. I denne opplæringen skaper vi noen oppbygde Ruby-klasser for å gjøre oss kjent med Rspec-syntaxen. Men den neste? Ruby for Newbies? episode vil fungere ved hjelp av Rspec i forbindelse med noen andre biblioteker for å teste webapps? så hold deg innstilt!


Setter opp

Det er ganske enkelt å installere Rspec. Pop åpne den kommandolinjen og kjør dette:

perle installasjon rspec

Så lett.

La oss nå sette opp et lite prosjekt. Vi skal lage to klasser: Bok og Bibliotek. Våre Bok objekter vil bare lagre en tittel, forfatter og kategori. Våre Bibliotek objektet lagrer en liste over bøker, lagrer dem i en fil og tillater oss å hente dem etter kategori.

Slik ser prosjektkatalogen ut:

Vi legger spesifikasjonene (eller spesifikasjonene) i a spec mappe; Vi har en spesifikke fil for hver klasse. Legg merke til spec_helper.rb fil. For våre spesifikasjoner å løpe, må vi krever Ruby-klassene vi tester. Det er det vi gjør inne i spec_helper fil:

require_relative '? / bibliotek 'require_relative'? / bok 'krever' yaml '

(Har du møtt require_relative ennå? Nei? Vi vil, require_relative er akkurat som krever, bortsett fra at i stedet for å søke på Ruby-banen, søker den slektning til gjeldende katalog.)

Du er kanskje ikke kjent med YAML-modulen; YAML er en enkel tekstdatabase som vi skal bruke til å lagre data. Du får se hvordan det fungerer, og vi snakker mer om det senere.

Så, nå som vi alle er satt opp, la oss få sprekker på noen spesifikasjoner!


De Bok Klasse

La oss starte med tester for Bok klasse.

krever 'spec_helper' beskrive boken slutter

Slik begynner vi: med a beskrive blokkere. Vår parameter til beskrive forklarer hva vi tester: dette kan være en streng, men i vårt tilfelle bruker vi klassenavnet.

Så hva skal vi sette inn i dette beskrive blokkere?

før: hver gjør @book = Book.new "Title", "Author",: category end

Vi begynner med å ringe til før; vi sender symbolet :Hver å spesifisere at vi vil at denne koden skal kjøre før hver test (vi kan også gjøre det :alle å kjøre det en gang før alle tester). Hva gjør vi nøye før hver test? Vi lager en forekomst av Bok. Legg merke til hvordan vi gjør det til en instansvariabel ved å forhåndsbeheve variabelenavnet med @. Vi må gjøre dette slik at vår variabel vil være tilgjengelig fra våre tester. Ellers får vi bare en lokal variabel som bare er god inne i før blokkere? som ikke er bra i det hele tatt.

Går videre,

Beskriv "#ny" gjør det "tar tre parametere og returnerer et bokobjekt" gjør @ bok.en skal være av en avtale

Her er vår første test. Vi bruker en nestet beskrive blokkere her for å si at vi beskriver handlingene til en bestemt metode. Du vil legge merke til at jeg har brukt strengen? # Ny ?; det er en konvensjon i Ruby å snakke referere til eksempel metoder som dette: Classname # metode Siden vi har klassenavnet i toppnivå beskrive, Vi setter bare metodens navn her.

Vår test forteller bare at vi faktisk har laget et bokobjekt.

Legg merke til grammatikken vi bruker her: object.should do something. Nitti og ni prosent av testene dine vil ta dette skjemaet: du har et objekt, og du begynner å ringe bør eller burde ikke på objektet. Deretter sender du til det aktuelle objektet anropet til en annen funksjon. I dette tilfellet er det be_an_instance_of (som tar Bok som en enkelt parameter). Samlet gir dette en perfekt lesbar test. Det er veldig klart at @bok bør være en forekomst av klassen Bok. Så, la oss kjøre den.

Åpne terminalen din, cd inn i prosjektkatalogen, og kjør rspec spec. De spec er mappen der rspec vil finne testene. Du bør se produksjonen si noe om? Uninitialized Constant Object :: Book ?; dette betyr bare at det er nei Bok klasse. La oss fikse det.

Ifølge TDD, vil vi bare skrive nok kode for å fikse dette problemet. I book.rb fil, det ville være dette:

klasse bok slutten

Kjør testen på nytt (rspec spec), og du finner det går bra. Vi har ikke en initial metode, så ringe Ruby # new har ingen effekt akkurat nå. Men vi kan skape Bok objekter (om enn hule). Normalt vil vi følge denne prosessen gjennom resten av vår utvikling: Skriv en test (eller noen få relaterte tester), se det mislykkes, få det til å passere, refactor, gjenta. Men for denne opplæringen vil jeg bare vise deg testene og koden, og vi diskuterer dem.

Så, flere tester for Bok:

beskrive "#title" gjør det "returnerer riktig tittel" do @ book.title.should eql "Tittel" slutten end beskriver "#author" gjør det "returnerer den riktige forfatteren" gjør @ book.author.should eql "Author" slutten beskriver "#category" gjør det "returnerer den riktige kategorien" do @ book.category.should eql: category end end

Det skal alle være ganske strightforward til deg. Men legg merke til hvordan vi sammenligner i testen: med EQL. Det er tre måter å teste på for likestilling med Rspec: bruker operatøren == eller metoden EQL begge tilbake ekte hvis de to objektene har det samme innholdet. For eksempel er begge strenger eller symboler som sier det samme. Så er det lik, som bare returnerer sant i de to objektene, er virkelig og virkelig lik, noe som betyr at de er samme gjenstand i minnet. I vårt tilfelle, EQL (eller ==) er det vi vil ha.

Disse vil mislykkes, så her er koden for Bok å få dem til å passere:

klassebok attr_accessor: tittel,: forfatter,: kategori def initierer tittel, forfatter, kategori @title = title @author = author @category = category end end

La oss gå videre til Bibliotek!


Spesifisere ut Bibliotek klasse

Denne blir litt mer komplisert. La oss starte med dette:

krever "spec_helper" beskrive "Library Object" gjør før: alle gjør lib_obj = [Book.new ("JavaScript: The Good Parts", "Douglas Crockford",: utvikling), Book.new ("Design with Web Standards" Jeffrey Zeldman ",: design), Book.new (" Do not Make Me Think "," Steve Krug ",: brukbarhet), Book.new (" JavaScript Patterns "," Stoyan Stefanov ",: utvikling), Book. ny ("Responsive Web Design", "Ethan Marcotte",: design)] File.open "books.yml", "w" do | f | f.write YAML :: dump lib_obj ende ende før: hver gjør @lib = Library.new "books.yml" slutten

Dette er alt oppsett: vi bruker to før blokker: en for :Hver og en for :alle. I før: alle blokkere, lager vi en rekke bøker. Så åpner vi filen? Books.yml? (i "writer" -modus) og bruk YAML å dumpe arrayet i filen.

Kort kaninsti for å forklare YAML litt bedre: YAML er, ifølge nettstedet, en menneskelig vennlig serialiseringsstandard for alle programmeringsspråk.? Det er som en tekstbasert database, ganske som JSON. Vi importerer YAML i vår spec_helper.rb. De YAML modulen har to hovedmetoder du vil bruke: dump, som utdataer de serialiserte dataene som en streng. Deretter, laste tar datastrengen og dekker den tilbake til Ruby-objekter.

Så, vi har laget denne filen med noen data. Før :Hver test, vi skal lage en Bibliotek objekt, passerer den navnet på YAML-filen. La oss nå se testene:

beskrive "# ny" gjør kontekst "uten parametre" gjør det "har ingen bøker" do lib = Library.new lib.should have (0) .books slutten kontekst "med en yaml fil parameter" gjør det "har fem bøker "do @ lib.should have (5) .books slutten slutten det" returnerer alle bøkene i en gitt kategori "do @ lib.get_books_in_category (: development) .length.should == 2 avslutte det" aksepterer nye bøker " @ lib.add_book (Book.new ("Design for the Web", "Mark Boulton",: design)) @ lib.get_book ("Design for the Web"). should_an_instance_of Book end det "sparer biblioteket" = @ lib.books.map | book | book.title @ lib.save lib2 = Library.new "books.yml" books2 = lib2.books.map | book | book.title books.should eql books2 avslutte

Vi starter med en indre beskrive blokkere spesielt for Bibliotek # new metode. Vi introduserer en annen blokk her: kontekst Dette gjør at vi kan spesifisere en kontekst for tester inne i den, eller spesifisere ulike utfall for ulike situasjoner. I vårt eksempel har vi to forskjellige sammenhenger:? Uten parametere? og? med en yaml fil parameter ?; disse viser de to atferdene for bruk Bibliotek # new.

Legg også merke til testkamperne vi bruker i disse to testene: lib.should have (0) .books og @ lib.should have (5) .books. Den andre måten å skrive dette ville være lib.books.length.should == 5, men dette er ikke så lesbart. Det viser imidlertid at vi må ha en bøker eiendom som er en rekke bøker vi har.

Deretter har vi tre andre tester for å teste funksjonaliteten for å få bøker etter kategori, legge til en bok i biblioteket og lagre biblioteket. Disse svikter alle, så la oss skrive klassen nå.

klassebibliotek attr_accessor: bøker def initialiser lib_file = false @lib_file = lib_file @books = @lib_file? YAML :: load (File.read (@lib_file)): [] ende def get_books_in_category category @books.select do | book | book.category == kategori ende ende def add_book bok @ books.push bok slutten def get_book title @ books.select do | book | book.title == title end.first ende def lagre lib_file = false @lib_file = lib_file || @lib_file || "library.yml" File.open @lib_file, "w" do | f | f.write YAML :: dump @books slutten slutten

Vi kunne skrive opp flere tester og legge til mye annen funksjonalitet til dette Bibliotek klassen, men vi stopper der. Nå kjører rspec spec, Du vil se at alle tester passerer.

Dette gir ikke oss så mye informasjon om testene, skjønt. Hvis du vil se mer, bruk den nestede formatparameteren: rspec spec - formatert nestet. Du ser dette:


Et par siste kampspillere

Før vi pakker opp, la meg vise deg et par andre kamper

  • obj.should be_true, obj.should be_false, obj.should be_nil, obj.should be_empty - De tre første av disse kan gjøres av == sant, etc. be_empty vil være sant hvis obj.empty? er sant.
  • obj.should eksistere - finnes dette objektet enda en gang?
  • obj.should have_at_most (n) .items, object.should have_at_least (n) .items - som ha, men vil passere hvis det er mer eller færre enn n varer, henholdsvis.
  • obj.should include (a [, b,?]) - er ett eller flere elementer i en matrise?
  • obj.should match (string_or_regex) - samsvarer objektet med strengen eller regexen?
  • obj.should raise_exception (feil) - Fremhever denne metoden en feil når den kalles?
  • obj.should respond_to (metodenavn) - Har dette objektet denne metoden? Kan ta mer enn ett metodenavn, i enten strenger eller symboler.

Ønsker å lære mer?

Rspec er en av de beste rammene for testing i Ruby, og det er tonn du kan gjøre med det. For å lære mer, sjekk ut Rspec-nettsiden. Det er også The Rspec-boken, som lærer mer enn bare Rspec: det handler om TDD og BDD i Ruby. Jeg leser det nå, og det er ekstremt grundig og grundig.

Vel, det er alt for denne leksjonen! Neste gang ser vi på hvordan vi kan bruke Rspec for å teste grensesnittene i en nettapp.