Testing JavaScript med Jasmine

Vi vet alle at vi skal teste koden vår, men vi gjør det egentlig ikke. Jeg antar det er rimelig å si at de fleste av oss slår det av fordi, ni ganger ut av ti, betyr det å lære enda et konsept. I denne opplæringen vil jeg introdusere deg til et flott lite rammeverk for å teste JavaScript-koden din med letthet.

Forresten, visste du at du kan få JavaScript-feilene dine løst raskt og enkelt av en ekspert på Envato Studio?

ThemeManiac, for eksempel, vil reparere javaScript-feil eller webkompatibilitetsproblemer på ditt nettsted eller webapplikasjon. Rettene kan fullføres raskt, basert på kompleksiteten og tilgjengelig informasjon. Han kan også omorganisere skriptene dine og få en helt ny brukeropplevelse. Han har gjennomført mer enn 1000 jobber på Envato Studio, med 99% av kundene som anbefalte ham.


Trinn 0: Forstå BDD

I dag skal vi lære om Jasmine BDD testing rammeverket. Men vi stopper her for en omvei først, for å snakke veldig kort, om BDD og TDD. Hvis du ikke er kjent med disse akronymer, står de for Behavior-Driven Development og Testdrevet utvikling. Jeg er midt i å lære om hva hver av disse er i praksis og hvordan de er forskjellige, men her er noen av de grunnleggende forskjellene:

BDD og TDD? står for Behavior-Driven Development og Testdrevet utvikling.

TDD i sin enkleste form er bare dette:

  1. Skriv dine tester
  2. Se dem mislykkes
  3. Få dem til å passere
  4. Refactor
  5. Gjenta

Det er ganske lett å forstå, eh?

BDD er litt mer komplisert: Som jeg forstår det akkurat nå, tror jeg ikke at du eller jeg som en enkelt utvikler faktisk kan øve det fullt ut; det er mer av et lag ting. Her er noen av praksisene i BDD:

  • Etablere målene for ulike interessenter som kreves for at en visjon skal implementeres
  • Involverer interessenter i gjennomføringsprosessen gjennom eksternt i programvareutvikling
  • Bruke eksempler for å beskrive oppførselen til applikasjonen eller kodenes enheter
  • Automatiserer disse eksemplene for å gi rask tilbakemelding og regresjonstesting

For å lære mer, kan du lese den omfattende Wikipedia-artikkelen (hvorfra poengene ble tatt).

Alt dette for å si at mens Jasmine regner seg som et BDD-rammeverk, skal vi bruke det på en mer TDD-stil måte. Det betyr ikke at vi bruker det galt, skjønt. Når vi er ferdige, kan du enkelt teste JavaScript? og jeg forventer at du skal gjøre det!


Trinn 1: Lære syntaksen

Jasmine tar mange tegn fra Rspec.

Hvis du er kjent med Rspec, er de facto BDD-rammeverk, ser du at Jasmine tar mange tegn fra Rspec. Jasmintester er hovedsakelig to deler: beskrive blokker og den blokker. La oss se hvordan dette virker.

Vi vil se på noen nærmere tester i noen, men for nå vil vi holde det enkelt:

Beskriv ('JavaScript tilleggsoperatør', funksjon () det ('legger til to tall sammen', funksjon () forventer (1 + 2). tilEkvivalent (3);););

Begge beskrive og den Funksjoner tar to parametre: en tekststreng og en funksjon. De fleste testrammer prøver å lese så mye som engelsk som mulig, og du kan se dette med Jasmine. Først legg merke til at strengen gikk til beskrive og strengen gikk til den danner en setning (av sorter):? JavaScript tilleggsoperatør legger til to tall sammen.? Så fortsetter vi å vise hvordan.

Inne i det den blokkere, kan du skrive all oppsettskoden du trenger for testen din. Vi trenger ikke noe for dette enkle eksemplet. Når du er klar til å skrive den faktiske testkoden, starter du med forvente funksjon, passerer det uansett hva du tester. Legg merke til hvordan dette danner en setning også: vi forventer 1 + 2 til lik 3.?

Men jeg kommer foran oss selv. Som jeg sa, hvilken verdi du passerer inn i forvente vil bli testet. Metoden du ringer, av den returnerte verdien av forvente, vil bli bestemt av hvilken test som kjøres. Denne gruppen av metoder kalles 'matchers', og vi ser på flere av dem i dag. I dette tilfellet bruker vi toEqual matcher, som sjekker for å se at verdien har gått til forvente og verdien gikk til toEqual er den samme verdien.

Jeg tror du er klar til å ta dette til neste nivå, så la oss sette opp et enkelt prosjekt ved hjelp av Jasmine.


Trinn 2: Sette opp et prosjekt

Jasmine kan brukes av seg selv; eller du kan integrere det med et Rails-prosjekt. Vi skal gjøre det forrige. Mens Jasmine kan løpe utenfor nettleseren (tenk Node, blant annet), kan vi få en veldig fin liten mal med nedlastingen.

Så, gå videre til den frittstående nedlastingssiden og få den nyeste versjonen. Du bør få noe slikt:

Du finner de faktiske Jasmine rammefilene i lib mappe. Hvis du foretrekker å strukturere prosjektene dine annerledes, vennligst gjør det; men vi skal holde dette for nå.

Det er faktisk noen prøvekode koblet opp i denne prosjektmalen. Den faktiske? JavaScript (koden vi vil teste) finnes i src katalogen; Vi skal snart sette oss der. Testkoden - den specs-gå inn i spec mappe. Ikke bekymre deg for SpecHelper.js filen bare ennå; vi kommer tilbake til det.

At SpecRunner.html fil er det som driver testene i en nettleser. Åpne den opp (og merk av for "passet" -feltet i øverste høyre hjørne), og du bør se noe slikt:

Dette viser oss at alle testene for prøveprosjektet passerer. Når du har kommet gjennom denne opplæringen, anbefaler jeg at du åpner spec / PlayerSpec.js fil og les den koden. Men akkurat nå, la oss gi denne testen skrive ting en prøve.

  • Skape convert.js i src mappe.
  • Skape convertSpec.js i spec mappe,
  • Kopier SpecRunner.html fil og endre navn på den SpecRunner.original.html.
  • Fjern koblingene til utvalgsprosjektfilene i SpecRunner.html og legg til disse linjene:

Nå er vi klare til å lage et mini-bibliotek som konverterer mellom måleenheter. Vi starter med å skrive tester for vårt mini-bibliotek.


Trinn 3: Skrive testene

Så, la oss skrive våre tester, skal vi?

Beskriv ("Konverter bibliotek", funksjon () beskriv ("avstandskonverter", funksjon () ); beskriv ("volumomformer", funksjon () ););

Vi starter med dette; vi tester vår Konvertere bibliotek. Du vil legge merke til at vi nesting beskrive uttalelser her. Dette er helt lovlig. Det er faktisk en fin måte å teste separate funktjonsbiter av samme kodebase. I stedet for to separate beskrive krever Konvertere bibliotekets avstandskonverteringer og volumkonverteringer, kan vi få en mer beskrivende pakke med tester som dette.

Nå, på de faktiske testene. Jeg gjentar det indre beskrive ringer her for din bekvemmelighet.

beskrive ("avstandskonverter", funksjon () det "omformer tommer til sentimeter", funksjon () forventer (Konverter (12, "i"). til ("cm")) .Equal (30,48);) ; den ("konverterer sentimeter til meter", funksjon () forventer (Konverter (2000, "cm"). til ("yards")) .Equal (21.87);););

Her er våre tester for avstandskonverteringer. Det er viktig å legge merke til noe her: Vi har ikke skrevet et speck kode for vår Konvertere bibliotek, men i disse testene gjør vi mer enn bare å se om det virker: Vi bestemmer faktisk hvordan den skal brukes (og derfor implementeres). Slik har vi besluttet å gjøre konverteringene våre:

Konvertere(, ).til();

Ja, jeg tar en cue fra måten Jasmine har implementert sine tester på, men jeg synes det er et fint format. Så i disse to testene har jeg konvertert meg selv (ok, med en kalkulator) for å se hva resultatene av våre samtaler skal være. Vi bruker toEqual matcher for å se om våre tester passerer.

Her er volumtestene:

beskrive ("volumomformer", funksjon () det konverterer liter til gallon », funksjon () forventer (Konverter (3," liter ") til (" gallons ")) .Equal (0.79);) ; ("konverterer gallon til kopper", funksjon () forvente (Konverter (2, "gallon"). til ("kopper")) .Equal (32);););

Og jeg skal legge til to tester i toppnivå beskrive anrop:

det ("kaster en feil ved bestått en ukjent fra-enhet", funksjon () var testFn = funksjon () Konverter (1, "dollar") til ("yens"); forvente (testFn) .toThrow ny feil ("ukjent fra enhet"));); det ("kaster en feil ved bestått en ukjent enhet", funksjon () var testFn = funksjon () Konverter (1, "cm") til ("furlongs"); forvente (testFn) .toThrow ny feil ("ukjent til enhet")););

Disse kontrollene for feil som skal kastes når ukjente enheter sendes inn i enten Konvertere funksjon eller til metode. Du vil legge merke til at jeg pakker den faktiske konverteringen i en funksjon og overfører den til forvente funksjon. Det er fordi vi ikke kan ringe funksjonen som forvente parameter; Vi må gi den en funksjon og la den kalle selve funksjonen. Siden vi må sende en parameter til det til funksjon, kan vi gjøre det på denne måten.

Den andre tingen å merke seg er at jeg introduserer en ny matcher: å kaste, som tar et feilobjekt. Vi ser snart på noen flere kampanjer.

Nå, hvis du åpner SpecRunner.html i en nettleser får du dette:

Flott! Våre tester er sviktende. Nå, la oss åpne vår convert.js fil og gjør noe arbeid:

Funksjon Konverter (tall, fraUnit) var konverteringer = avstand: meter: 1, cm: 0,01, føtter: 0,3048, tommer: 0,0254, meter: 0,9144, volum: liter: 1, gallon: 3,785411784, kopper: 0,236588236 , mellomUnit = falsk, type, enhet; for (skriv inn konverteringer) if (konverteringer (type)) if ((unit = konverteringer [type] [fraUnit])) mellomUnit = tall * enhet * 1000;  returnere til: funksjon (toUnit) if (betweenUnit) for (skriv inn konverteringer) if (conversions.hasOwnProperty (type)) if ((unit = konverteringer [type] [toUnit])) fikse (mellomUnit / (enhet * 1000));  kaste ny feil ("ukjent til enhet");  ellers kaste ny feil ("ukjent fra-enhet");  funksjonsrett (num) return parseFloat (num.toFixed (2)); ; 

Vi kommer ikke til å diskutere dette, fordi vi lærer Jasmine her. Men her er hovedpoengene:

  • Vi gjør konverteringene ved å lagre konverteringen i et objekt; Konverteringsnummer er klassifisert etter type (avstand, volum, legg til din egen). For hvert måleområde har vi en basisverdi (meter eller liter, her) som alt konverterer til. Så når du ser meter: 0,9144, du vet at det er hvor mange meter det er i en meter. Så, for å konvertere meter til, si, centimeter, vi multipliserer yards ved den første parameteren (for å få antall meter) og deretter dele produktet med cm, antall meter i en centimeter. På denne måten behøver vi ikke lagre konverteringsfrekvensene for hvert par verdier. Dette gjør det også enkelt å legge til nye verdier senere.
  • I vårt tilfelle forventer vi at enhetene som sendes inn, er de samme som nøklene vi bruker i konverterings-tabellen.? Hvis dette var et ekte bibliotek, ønsker vi å støtte flere formater som 'in', 'tommer' og 'tommer', og derfor vil vi legge til noen logikk for å matche fromUnit til høyre tast.
  • På slutten av Konvertere funksjon, lagrer vi mellomverdien i betweenUnit, som er initialisert til falsk. På den måten, hvis vi ikke har fromUnit, betweenUnit vil være falsk går inn i til metode, og så en feil med kastes.
  • Hvis vi ikke har toUnit, en annen feil vil bli kastet. Ellers deler vi som nødvendig og returnerer den konverterte verdien.

Nå, gå tilbake til SpecRunner.html og last siden på nytt. Du bør nå se dette (etter å ha lest? Vis bestått?):

Der går du! Våre tester går forbi. Hvis vi utviklet et virkelig prosjekt her, ville vi skrive tester for en viss del av funksjonalitet, få dem til å passere, skrive tester for en annen sjekk, få dem til å passere osv. Men siden dette var et enkelt eksempel, har vi nettopp gjort det alt i ett fall.

Og nå som du har sett dette enkle eksempelet på å bruke Jasmine, la oss se på noen få flere funksjoner som den tilbyr deg.


Trinn 4: Lære spillerne

Så langt har vi brukt to matchere: toEqual og å kaste. Det er selvsagt mange andre. Her er noen du vil sikkert finne nyttig; Du kan se hele listen på wiki.

toBeDefined / toBeUdedefined

Hvis du bare vil sørge for at en variabel eller egenskap er definert, er det en matcher for det. Det er også en som bekrefter at en variabel eller eiendom er udefinert.

det ("er definert", funksjon () var navnet = "Andrew"; forvente (navn) .toBeDefined ();) det ("er ikke definert", funksjon () var navn; forventer (navn). tilBeUdedefinert (););

toBeTruthy / toBeFalsy

Hvis noe skal være sant eller falskt, vil disse kampene gjøre det.

det ("er sant", funksjon () forventer (Lib.isAWeekDay ()). toBeTruthy ();); det ("er falskt", funksjon () forventer (Lib.finishedQuiz) .toBeFalsy (););

toBeLessThan / toBeGreaterThan

For alt du nummererer folk. Du vet hvordan disse fungerer:

det ("er mindre enn 10", funksjon () forventer (5) .tilBeLessThan (10);); det ("er større enn 10", funksjon () forvente (20) .toBeGreaterThan (10););

å passe

Har du noe utskriftstekst som skal matche et vanlig uttrykk? De å passe Matcher er klar og villig.

det ("outputs the right text", funksjon () forvente (cart.total ()). toMatch (/ \ $ \ d *. \ d \ d /););

å inneholde

Denne er ganske nyttig. Det kontrollerer for å se om et array eller en streng inneholder et element eller en substring.

det ("skal inneholde appelsiner", funksjon () forvente (["epler", "appelsiner", "pærer"]) .Contain ("appelsiner"););

Det er også noen andre matchere som du finner i wiki. Men hva om du vil ha en matcher som ikke eksisterer? Virkelig, du bør kunne gjøre omtrent alt med noen oppsettkode og kampanjene Jasmine gir, men noen ganger er det bedre å abstrahere noen av den logikken for å få en lesbar test. Serendipitøst (vel, faktisk ikke), tillater Jasmine oss å skape våre egne kampere. Men for å gjøre dette må vi lære litt noe annet først.

Trinn 5: Dekker før og etter

Ofte - når du tester en kodebase - vil du utføre noen få linjer med oppsettkoden for hver test i en serie. Det ville være smertefullt og verbose å måtte kopiere det for alle den ring, så Jasmine har en praktisk liten funksjon som gjør at vi kan utpeke koden til å kjøre før eller etter hver test. La oss se hvordan dette virker:

beskrive ("MyObject", funksjon () var obj = ny MyObject (); beforeEach (funksjon () obj.setState ("clean");); den ("endrer tilstand", funksjon () obj.setState ("skitne"), forvent (obj.getState ()) .Equal ("dirty");) det ("legger til stater", funksjon () obj.addState ("pakket"); )) .Ekvivalent (["rent", "pakket"]););

I dette utførlige eksemplet kan du se hvordan, før hver test kjøres, tilstanden til obj er satt til? ren ?. Hvis vi ikke gjorde dette, fortsetter den endrede til et objekt i en tidligere test som standard ved neste test. Selvfølgelig kunne vi også gjøre noe lignende med AfterEach funksjon:

beskrive ("MyObject", funksjon () var obj = ny MyObject ("clean"); // setter initial tilstand afterEach (funksjon () obj.setState ("clean");); , funksjon () obj.setState ("dirty"); expect (obj.getState ()) .Equal ("dirty");) det ("legger til stater", funksjon () obj.addState ("pakket" ); forvente (obj.getState ()) .Equal (["clean", "packaged"]););

Her setter vi opp objektet til å begynne med, og deretter har det rettet etter hver test. Hvis du vil ha MyObject fungere slik at du kan gi denne koden et forsøk, du kan få det her i et GitHub-gist.

Trinn 6: Skrive tilpassede kampanjer

Som vi sa tidligere, vil kundematchere trolig være nyttig til tider. Så la oss skrive en. Vi kan legge til en matcher i enten a BeforeEach ring eller en den ringe (vel, jeg antar deg kunne gjør det i en AfterEach ring, men det ville ikke gi mye mening). Slik begynner du:

beforeEach (funksjon () this.addMatchers (););

Ganske enkelt, eh? Vi ringer this.addMatchers, sender den en objektparameter. Hver nøkkel i dette objektet blir navnet på en kampfører, og den tilknyttede funksjonen (verdien) blir hvordan den kjøres. La oss si at vi vil lage en matcher som med sjekk for å se om ett tall er mellom to andre. Her er hva du vil skrive:

beforeEach (funksjon () this.addMatchers (toBeBetween: funksjon (rangeFloor, rangeCeiling) if (rangeFloor> rangeCeiling) var temp = rangeFloor; rangeFloor = rangeCeiling; rangeCeiling = temp; returnere dette.aktuelle> rangeFloor && this. faktiske < rangeCeiling;  ); );

Vi tar bare to parametere, sørg for at den første er mindre enn den andre, og returner en boolsk uttalelse som vurderer til sann hvis betingelsene våre er oppfylt. Det viktige å merke seg her er hvordan vi får tak i verdien som ble sendt til forvente funksjon: this.actual.

det ("er mellom 5 og 30", funksjon () forvente (10). tilBeBetween (5, 30);); det ("er mellom 30 og 500", funksjon () forvente (100). tilBeBetween (500, 30););

Dette er hva SpecHelper.js filen gjør; den har en beforeEach samtale som legger til spilleren tobePlaying (). Sjekk det ut!


Konklusjon: Ha det gøy selv!

Det er mye mer du kan gjøre med Jasmine: funksjonsrelaterte matchere, spioner, asynkrone detaljer og mer. Jeg anbefaler at du undersøker wiki hvis du er interessert. Det er også noen medfølgende biblioteker som gjør testing i DOM enklere: Jasmine-jQuery og Jasmine-fixture (som avhenger av Jasmine-jQuery).

Så hvis du ikke tester JavaScript så langt, er det nå en fin tid å starte. Som vi har sett, gjør Jasmines raske og enkle syntaks testen ganske enkel. Det er bare ingen grunn til at du ikke skal gjøre det, nå er det der?

Hvis du vil ta JavaScript-utviklingen ytterligere, hvorfor ikke sjekk utvalget av JavaScript-elementer på Envato Market? Det er tusenvis av skript, apper og kodestykker som hjelper deg.