JavaScript-testing fra scratch

Dette er sannsynligvis ikke den første opplæringen om testing som du noensinne har sett. Men kanskje du har hatt tvil om å teste, og aldri tok deg tid til å lese dem. Tross alt kan det virke som ekstra arbeid uten grunn.

Denne opplæringen har til hensikt å endre dine synspunkter. Vi skal begynne i begynnelsen: hva er testing og hvorfor skal du gjøre det? Deretter snakker vi kort om å skrive testbar kode, før du faktisk vet, gjør noen test! La oss komme til det!


Foretrekker en skjermbilde?

Del 1


Del 2


Del 3



Definere Testing

Helt enkelt, testing er ideen om å ha et sett krav som et bestemt stykke kode må passere for å være robust nok til å bli brukt i den virkelige verden. Ofte skriver vi litt JavaScript og åpner det i nettleseren og klikker litt rundt for å sikre at alt fungerer som vi ville forvente. Selv om det noen ganger er nødvendig, er det ikke typen testing vi snakker om her. Faktisk håper jeg at denne opplæringen vil overbevise deg om at hurtig og skitten selvtest skal utfylle en mer stiv testprosedyre: selvtesting er bra, men en grundig liste over krav er avgjørende.


Grunner til testing

Som du kanskje gjetter, er problemet med JavaScript-testen for oppdatering og klikk dobbelt:

  1. Vi kan ikke huske å sjekke noe; Selv om vi gjør det, kan vi ikke sjekke om igjen etter kodejusteringer.
  2. Det kan være noen deler av koden som egentlig ikke kan testes.

Ved å skrive tester som kontrollerer alt koden din burde gjøre, kan du bekrefte at koden din er i beste form før du faktisk bruker den på et nettsted. Når noe faktisk kjører i en nettleser, er det sannsynligvis flere feilpunkter. Skrivingstester lar deg fokusere på hver testbar del enkeltvis; hvis hvert stykke gjør sin jobb riktig, bør ting samarbeide uten problem (testing av enkelte deler som dette kalles enhetstesting).


Skrivbar testbar kode

Hvis du er en programmeringspolyglot), har du kanskje gjort testing på andre språk. Men jeg har funnet testing i JavaScript et annet dyr å drepe. Tross alt bygger du ikke for mange brukergrensesnitt i, for eksempel PHP eller Ruby. Ofte gjør vi DOM-arbeid i JavaScript, og hvordan tester du det?

Vel, DOM-arbeidet er ikke det du vil skrive tester for; det er logikken. Åpenbart, så er nøkkelen her å skille logikken din og din UI-kode. Dette er ikke alltid lett; Jeg har skrevet min rettferdige andel av jQuery-drevet brukergrensesnitt, og det kan bli ganske rotete ganske raskt. Ikke bare gjør det vanskelig å teste, men sammenflettet logikk og UI-kode kan også være vanskelig å endre når den ønskede oppførselen endres. Jeg har funnet å bruke metoder som maler (også maler) og pub / sub (også pub / sub) gjør å skrive bedre, mer testbar kode lettere.

En ting til, før vi begynner koding: hvordan skriver vi testene våre? Det er mange testbiblioteker som du kan bruke (og mange gode opplæringsprogrammer for å lære deg å bruke dem, se linkene som enden). Vi skal imidlertid bygge et lite testbibliotek fra bunnen av. Det vil ikke være så fancy som noen biblioteker, men du får se nøyaktig hva som skjer.

Med dette i tankene, la oss komme på jobb!


Bygg en testminiramme

Vi skal bygge et mikrofotogalleri: En enkel liste over miniatyrer, med ett bilde som viser full størrelse over dem. Men først, la oss bygge ut testfunksjonen.

Når du lærer mer om testing og testing av biblioteker, finner du mange testmetoder for å teste alle typer spesifikasjoner. Men det kan alle bli kokt ned på om to ting er like eller ikke: for eksempel,

  • Er verdien returnert fra denne funksjonen tilsvarer det vi forventet å komme tilbake?
  • Er denne variabelen av typen vi forventet å være?
  • Har dette oppsettet det forventede antall elementer?

Så, her er vår metode for å teste for likestilling:

var TEST = areEqual: funksjon (a, b, msg) var result = (a === b); console.log ((result? "PASS:": "FAIL:") + msg); returresultat; ;

Det er ganske enkelt: metoden tar tre parametere. De to første er sammenlignet, og hvis de er like, går testene. Den tredje parameteren er en melding som beskriver testen. I dette enkle testbiblioteket skriver vi bare ut testene våre til konsollen, men du kan lage HTML-utdata med riktig CSS-styling hvis du vil.

Her er areNotEqual metode (innenfor det samme TEST gjenstand):

areNotEqual: funksjon (a, b, msg) var result = (a! == b); console.log ((result? "PASS:": "FAIL:") + msg); returresultat; 

Du vil legge merke til de to siste linjene i er like og areNotEqual det samme. Så, vi kan trekke dem ut som dette:

var TEST = areEqual: funksjon (a, b, msg) return this.output (a === b, msg), er ikke ekvivalent: funksjon (a, b, msg) return this._output (a! == b, msg); , _output: funksjon (resultat, msg) konsoll [resultat? "logg": "advare"] ((resultat? "PASS:": "FAIL:") + msg); returresultat; ;

Flott! Ryddig ting her er at vi kan legge til andre "sukker" metoder ved hjelp av metodene vi allerede har skrevet:

TEST.isTypeOf = funksjon (obj, type, msg) return this.areEqual (typeof obj, type, msg); ; TEST.isAnInstanceOf = funksjon (obj, type, msg) return this._output (obj instanceof type, msg);  TEST.isGreaterThan = funksjon (val, min, msg) return this._output (val> min, msg); 

Du kan eksperimentere med dette alene; Etter å ha gått gjennom denne opplæringen, har du en god ide om hvordan du bruker den.


Forbereder for vårt galleri

Så la oss lage et super-enkelt fotogalleri ved hjelp av vår mini TEST rammeverk for å lage noen tester. Jeg nevner her at mens testdrevet utvikling er en god praksis, vil vi ikke bruke den i denne opplæringen, hovedsakelig fordi det ikke er noe du kan lære i en enkelt opplæring; det tar mye øvelse for å virkelig Grok. Når du starter, er det lettere å skrive litt kode og deretter teste den.

Så, la oss starte. Selvfølgelig trenger vi noen HTML for vårt galleri. Vi vil holde dette ganske grunnleggende:

     Testing i JavaScript     

Det er to hovedpunkter verdt å merke seg her: først har vi en

som holder veldig enkelt oppslag for vårt bildegalleri. Nei, det er sannsynligvis ikke veldig robust, men det gir oss noe å jobbe med. Legg merke til at vi trekker opp tre >s: ett er vårt lille testbibliotek, som funnet ovenfor. Den ene er galleriet vi skal lage. Den endelige holder testene for vårt galleri. Legg også merke til stiene til bildene: miniatyrfilenavnene har "-tumb" vedlagt dem. Slik finner vi den større versjonen av bildet.

Jeg vet at du er kløe for å få koding, så kaster dette i a gallery.css fil:

.galleri bakgrunn: #ececec; overløp: skjult; bredde: 620px;  .gallery> img margin: 20px 20px 0; polstring: 0;  .gallery ul liste-stil-type: none; margin: 20px; polstring: 0; flow: hidden;  .gallery li float: left; margin: 0 10px;  .gallery li: first-of-type margin-left: 0px;  .gallery li: siste-of-type margin-right: 0px; 

Nå kan du laste dette opp i nettleseren din og se noe slikt:

OK ALDRI! La oss skrive litt JavaScript, skal vi?


Omtalt vårt galleri

Åpne opp det gallery.js fil. Slik begynner vi:

var Gallery = (funksjon () var Gallery = , galleryPrototype = ; Gallery.create = funksjon (id) var gal = Object.create (galleryPrototype); return gal;; return Gallery; ;

Vi legger til mer, men dette er en god start. Vi bruker en egenaktiverende anonym funksjon (eller et øyeblikkelig påtrykt funksjonsuttrykk) for å holde alt sammen. Våre "interne" Galleri variabel vil bli returnert og være verdien av vårt publikum Galleri variabel. Som du kan se, ringer Gallery.create vil opprette et nytt galleriobjekt med Object.create. Hvis du ikke er kjent med Object.create, det skaper bare et nytt objekt ved hjelp av objektet som du sender det som prototypen til det nye objektet (det er ganske nettleser-kompatibelt også). Vi fyller i prototypen og legger til vår Gallery.create metode også. Men, la oss nå skrive vår første test:

var gal = Gallery.create ("gal-1"); TEST.areEqual (typeof gal, "object", "Gallery skal være et objekt");

Vi starter med å skape en "forekomst" av Galleri; så løper vi en test for å se om verdien returnert er en gjenstand.

Sett disse to linjene i vår galleri-test.js; nå, åpne vår index.html side i en nettleser og pop åpne en JavaScript-konsoll. Du bør se noe slikt:


Flott! Vår første test går forbi!


Skriver Konstruktøren

Deretter fyller vi inn vår Gallery.create metode. Som du vil se, er vi ikke bekymret for å gjøre denne eksempelkoden super robust, så vi bruker noen ting som ikke er kompatible i alle nettlesere som noensinne er opprettet. nemlig, document.querySelector / document.querySelectorAll; også, vi bruker bare moderne nettleserhendelsehåndtering. Du kan gjerne erstatte favorittbiblioteket ditt hvis du vil.

Så, la oss starte med noen erklæringer:

var gal = Object.create (galleryPrototype), ul, i = 0, len; gal.el = document.getElementById (id); ul = gal.el.querySelector ("ul"); gal.imgs = gal.el.querySelectorAll ("ul li img"); gal.displayImage = gal.el.querySelector ("img: first-child"); gal.idx = 0; gal.going = false; gal.ids = [];

Fire variabler: spesielt vårt galleriobjekt og galleriets

    node (vi skal bruke Jeg og len om et øyeblikk). Deretter, seks eiendommer på vår gal gjenstand:

    • gal.el er "root" node på vårt galleri's markyp.
    • gal.imgs er en liste over
    • s som holder våre miniatyrbilder.
    • gal.displayImage er det store bildet i vårt galleri.
    • gal.idx er indeksen, det aktuelle bildet blir vist.
    • gal.going er en boolsk: det er det ekte hvis galleriet sykler gjennom bildene.
    • gal.ids vil være en liste over IDene for bildene i vårt galleri. For eksempel, hvis miniatyrbildet er kalt "dog-thumb.jpg", så er "hund" ID og "dog.jpg" er bildestørrelsesbildet.

    Legg merke til at DOM-elementene har querySelector og querySelectorAll metoder også. Vi kan bruke gal.el.querySelector for å sikre at vi bare velger elementer i dette galleriets oppslag.

    Nå fyll ut gal.ids med bilde-IDene:

    len = gal.imgs.length; for (; < len; i++ )  gal.ids[i] = gal.imgs[i].getAttribute("src").split("-thumb")[0].split("/")[1]; 

    Ganske rett frem, høyre?

    Til slutt, la oss koble opp en hendelsehandler på

      .

      ul.addEventListener ("klikk", funksjon (e) var i = [] .indexOf.call (gal.imgs, e.target); hvis (i> -1) gal.set (i); falsk);

      Vi starter med å sjekke for å se om det laveste elementet som fikk klikket (e.target; vi er ikke bekymret for oldie-støtte her) er i vår liste over bilder; siden NodeLists har ikke en oversikt over metode, bruker vi array-versjonen (Hvis du ikke er kjent med anrop og søke om i JavaScript, se vår raske tips om dette emnet.). Hvis det er større enn -1, sender vi det videre til gal.set. Vi har ikke skrevet denne metoden ennå, men vi kommer til det.

      Nå, la oss vende tilbake til vår galleri-test.js fil og skriv noen tester for å forsikre oss om Galleri eksempel har de riktige egenskapene:

      TEST.areEqual (gal.el.id, "gal-1", "Gallery.el skal være den vi oppgav"); TEST.areEqual (gal.idx, 0, "Gallery index skal starte på null");

      Vår første test bekrefter at vår gallerikonstruktør fant det rette elementet. Den andre testen verifiserer at indeksen starter ved 0. Du kan sannsynligvis skrive en rekke tester for å verifisere at vi har de riktige egenskapene, men vi skal skrive tester for metodene som skal bruke disse egenskapene, så det vil egentlig ikke være nødvendig.


      Bygg prototypen

      La oss nå flytte tilbake til det galleryPrototype objekt som er tomt for øyeblikket. Det er her vi skal huske alle våre metoder Galleri "Instanser." La oss starte med sett Metode: Dette er den mest viktige metoden, fordi det er den som faktisk endrer det viste bildet. Det tar enten indeksen til bildet eller ID-strengen av bildet.

      // innenfor 'galleryProtytype' sett: funksjon (i) hvis (typeof i === 'streng') i = this.ids.indexOf (i);  this.displayImage.setAttribute ("src", "images /" + this.ids [i] + ".jpg"); returnere (this.idx = i); 

      Hvis metoden får ID-strengen, vil den finne riktig indeksnummer for den aktuelle ID-en. Så satte vi displayImage's src til riktig bildebane, og returner den nye nåværende indeksen mens du angir den som den nåværende indeksen.

      Nå, la oss teste den metoden (tilbake i galleri-test.js):

      TEST.areEqual (gal.set (4), 4, "Gallery.set (med nummer) skal returnere det samme nummeret som ble sendt inn"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images_24 / javascript-testing-from-scratch.jpg", "Gallery.set (med nummer) skal endre det viste bildet"); TEST.areEqual (gal.set ("post"), 3, "Gallery.set (med streng) skal flytte til riktig bilde"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images / post.jpg", "Gallery.set (med streng) skal endre de viste bildene");

      Vi tester vår testmetode med både et tall og en strengparameter for sett. I dette tilfellet kan vi sjekke src for bildet og kontroller at brukergrensesnittet justeres tilsvarende; det er ikke alltid mulig eller nødvendig for å sikre at brukeren ser på å svare på riktig måte (uten å bruke noe som dette); Det er der klikk-typen typen testing er nyttig. Vi kan imidlertid gjøre det her, så vil vi.

      Disse tester skal passere. Du bør også kunne klikke på miniatyrbildene og vise de større versjonene. Ser godt ut!

      Så, la oss gå videre til noen metoder som beveger seg mellom bildene. Disse kan være nyttige hvis du vil ha "neste" og "forrige" knapper for å sykle gjennom bildene (vi vil ikke ha disse knappene, men vi legger inn støttemetodene).

      For det meste er det ikke vanskelig å flytte til neste og forrige bilder. De vanskelige delene går til neste bilde når du er på den siste eller den forrige når du er på den første.

      // innsiden 'galleryPrototype' neste: funksjon () hvis (this.idx === this.imgs.length - 1) return this.set (0);  returner dette.setet (this.idx + 1); , prev: function () if (this.idx === 0) return this.set (this.imgs.length - 1);  returner dette.setet (this.idx - 1); , curr: funksjon () return this.idx; ,

      Ok, så det er egentlig ikke så vanskelig. Begge disse metodene er "sukker" metoder for bruk sett. Hvis vi er på det siste bildet (this.idx === this.imgs.length -1), vi angitt (0). Hvis vi er på den første (this.idx === 0), vi sett (this.imgs.length -1). Ellers kan du bare legge til eller trekke en fra gjeldende indeks. Ikke glem at vi kommer tilbake akkurat det som returneres fra sett anrop.

      Vi har også curr metode også. Det er ikke komplisert i det hele tatt: det returnerer bare gjeldende indeks. Vi skal teste det litt senere.

      Så, la oss teste disse metodene.

      TEST.areEqual (gal.next (), 4, "Gallery skal forhåndsføre på .next ()"); TEST.areEqual (gal.prev (), 3, "Gallery bør gå tilbake på .prev ()");

      Disse kommer etter våre tidligere tester, så 4 og 3 er verdiene som vi forventer. Og de går forbi!

      Det er bare ett stykke igjen: det er automatisk fotocykling. Vi ønsker å kunne ringe gal.start () å spille gjennom bildene. Selvfølgelig vil de være en gal.stop () metode.

      // innsiden 'galleryPrototype' start: funksjon (tid) var thiz = dette; tid = tid || 3000; this.interval = setInterval (funksjon () thiz.next ();, tid); this.going = true; returnere sant; , stopp: funksjon () clearInterval (this.interval); this.going = false; returnere sant; ,

      Våre start Metoden vil ta parameter: antall millisekunder som ett bilde blir vist; Hvis ingen parameter er gitt, standardiserer vi til 3000 (3 sekunder). Da bruker vi ganske enkelt setInterval på en funksjon som vil ringe neste på riktig tidspunkt. Selvfølgelig kan vi ikke glemme å sette this.going til ekte. Til slutt kommer vi tilbake ekte.

      Stoppe er ikke for vanskelig. Siden vi lagret intervallet som this.interval, Vi kan bruke clearInterval for å avslutte det. Så setter vi this.going å falle og returnere sant.

      Vi har to nyttige funksjoner for å gi beskjed om galleriet er looping:

      isGoing: function () return this.going; , er Stoppet: funksjon () return! this.going; 

      La oss nå teste denne funksjonaliteten.

      gal.set (0); TEST.areEqual (gal.start (), true, "Gallery burde være looping"); TEST.areEqual (gal.curr (), 0, "Gjeldende bildeindeks skal være 0"); setTimeout (funksjon () TEST.areEqual (gal.curr (), 1, "Nåværende bildeindeks skal være 1"); TEST.areEqual (gal.isGoing (), true, "Gallery should go"); TEST. areEqual (gal.stop (), true, "Gallery bør stoppes"); setTimeout (funksjon () TEST.areEqual (gal.curr (), 1, "Nåværende bilde skal fortsatt være 1"); TEST.areEqual gal.isStoppet (), sant, "Galleri skal fortsatt stoppes");, 3050);, 3050);

      Det er litt mer komplisert enn våre tidligere sett med tester: Vi begynner med å bruke gal.set (0) for å sikre at vi starter i begynnelsen. Da kaller vi gal.start () å starte loopingen. Deretter tester vi det gal.curr () returnerer 0, noe som betyr at vi fortsatt ser på det første bildet. Nå skal vi bruke a setTimeout å vente 3050ms (bare litt mer enn 3 sekunder) før du fortsetter testene våre. Inne i det setTimeout, vi skal gjøre en annen gal.curr (); indeksen skal nå være 1. Da skal vi teste det gal.isGoing () er sant. Deretter stopper vi galleriet gal.stop (). Nå bruker vi en annen setTimeout å vente en annen nesten 3 sekunder; hvis galleriet virkelig har stoppet, vil bildet ikke være looping, så gal.curr () skal fortsatt være 1; Det er det vi tester inne i timeout. Endelig sørger vi for at vår isStopped metoden virker.

      Hvis disse testene har passert, gratulerer! Vi har fullført vår Galleri og dens tester.


      Konklusjon

      Hvis du ikke har prøvd testing før, håper jeg at du har sett hvor enkelt testing kan være i JavaScript. Som jeg nevnte i begynnelsen av denne opplæringen, vil god testing trolig kreve at du skriver JavaScript litt annerledes enn du kanskje pleide å være. Imidlertid har jeg funnet ut at lett testbare JavaScript også er lett å vedlikeholde JavaScript.

      Jeg vil gi deg flere koblinger som du kan finne nyttige når du går videre og skrive gode JavaScript og gode tester.

      • QUnit - en enhetstestpakke (opplæring på QUnit)
      • Jasmine - BDD rammeverk (Tutorial on Jasmine)
      • JavaScript-testing med Assert
      • Test Driven JavaScript (bok) (Eksempel kapittel)