JavaScript-testning från början

Det här är troligt inte den första handledningen om testning som du någonsin har sett. Men kanske har du haft tvivel om att testa, och tog aldrig tid att läsa dem. Det kan trots allt tyckas som extra arbete utan anledning.

Denna handledning avser att ändra dina åsikter. Vi ska börja i början: Vad är testning och varför ska du göra det? Då ska vi prata kort om att skriva testbar kod, innan du vet, gör några test! Låt oss ta itu med det!


Föredrar en skärmdump?

Del 1


Del 2


Del 3



Definiera test

Helt enkelt, testning är tanken på att ha en uppsättning krav som en viss kod måste passera för att vara robust nog för att kunna användas i den verkliga världen. Ofta skriver vi lite JavaScript och öppnar det i webbläsaren och klickar lite om att allt fungerar som vi skulle förvänta oss. Även om det ibland är nödvändigt, det är inte typen av testning vi pratar om här. Faktum är att jag hoppas att denna handledning kommer att övertyga dig om att snabb och smutsig självprovning borde komplettera ett mer styvt provningsförfarande: självtestning är bra, men en grundlig lista över krav är avgörande.


Skäl till testning

Som du kan gissa är problemet med uppdatering och klickning av JavaScript-testning dubbelt:

  1. Vi kanske inte kommer ihåg att kontrollera något; även om vi gör det, kanske vi inte checkar om efter kod tweaks.
  2. Det kan finnas vissa delar av koden som inte kan testas på det sättet.

Genom att skriva tester som kontrollerar allt vad din kod ska göra kan du verifiera att din kod är i bästa form innan du använder den på en webbplats. När det är dags att någonting faktiskt körs i en webbläsare finns det förmodligen flera felpunkter. Skrivprov gör att du kan fokusera på varje testbar del individuellt; om varje bit gör sitt jobb rätt ska sakerna fungera tillsammans utan problem (testa enskilda delar som detta kallas enhetstestning).


Skrivbar testbar kod

Om du är en programmeringspolyglot) har du kanske gjort test på andra språk. Men jag har hittat test i JavaScript ett annat djur att döda. När allt kommer omkring bygger du inte för många användargränssnitt i, säger PHP eller Ruby. Ofta gör vi DOM-arbete i JavaScript, och hur exakt testar du det?

Jo, DOM-arbetet är inte det du vill skriva tester för; det är logiken. Självklart, då är nyckeln här att skilja din logik och din UI-kod. Detta är inte alltid lätt; Jag har skrivit min rättvisa del av jQuery-powered gränssnitt, och det kan bli ganska rörigt ganska snabbt. Det gör inte bara det svårt att testa, men sammanflätad logik och användarkoderkod kan också vara svåra att modifiera när det önskade beteendet ändras. Jag har funnit att använda metoder som mallar (även mallar) och pub / sub (även pub / sub) gör skrivning bättre, mer testbar kod lättare.

En sak innan vi börjar kodning: hur skriver vi våra test? Det finns många testbibliotek som du kan använda (och många bra handledning för att lära dig att använda dem, se länkarna som slutet). Vi ska dock bygga ett litet testbibliotek från början. Det kommer inte vara så fint som vissa bibliotek, men du får se exakt vad som händer.

Med det här i åtanke, låt oss komma igång!


Bygga ett test-mini-ramverk

Vi ska bygga ett mikrofotogalleri: en enkel lista med miniatyrer, med en bild som visar full storlek ovanför dem. Men först, låt oss bygga ut testfunktionen.

När du lär dig mer om att testa och testa bibliotek, hittar du många testmetoder för att testa alla typer av specifikationer. Det kan emellertid alla kokas ner om två saker är lika eller inte: till exempel,

  • Värdet returneras från den här funktionen lika med vad vi förväntade oss att komma tillbaka?
  • Är denna variabel av den typ som vi förväntade oss att den skulle vara?
  • Har denna matris det förväntade antalet objekt?

Så här är vår metod att testa för jämlikhet:

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

Det är ganska enkelt: metoden tar tre parametrar. De två första jämförs, och om de är lika passerar testen. Den tredje parametern är ett meddelande som beskriver testet. I det här enkla testbiblioteket lägger vi bara ut våra tester på konsolen, men du kan skapa HTML-utdata med lämplig CSS-styling om du vill.

Här är areNotEqual metod (inom samma TESTA objekt):

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

Du kommer att märka de två sista raderna av är jämlika och areNotEqual det samma. Så vi kan dra ut dem så här:

var TEST = areEqual: funktion (a, b, msg) returnera this.output (a === b, msg), areNotEqual: funktion (a, b, msg) returnera this._output (a! == b, msg); , _output: funktion (resultat, msg) konsol [resultat? "logg": "varna"] ((resultat? "PASS:": "FEL:") + msg); returresultat; ;

Bra! Snygg sak här är att vi kan lägga till andra "socker" metoder med de metoder vi redan har skrivit:

TEST.isTypeOf = funktion (obj, typ, msg) returnera this.areEqual (typof obj, typ, msg); ; TEST.isAnInstanceOf = funktion (obj, typ, msg) returnera this._output (obj instanceof type, msg);  TEST.isGreaterThan = funktion (val, min, msg) returnera this.output (val> min, msg); 

Du kan själv experimentera med det här; efter att ha gått igenom den här handledningen har du en bra bild av hur du använder den.


Förberedelser för vårt galleri

Så, låt oss skapa ett superklart fotogalleri med vår mini TESTA ram för att skapa några test. Jag kommer att nämna här att medan testdriven utveckling är en bra övning, kommer vi inte att använda den i den här handledningen, främst eftersom det inte är något du kan lära dig i en enda handledning. det krävs mycket träning för att verkligen grok. När du börjar är det lättare att skriva lite kod och sedan testa den.

Så, låt oss börja. Självklart behöver vi lite HTML för vårt galleri. Vi kommer att behålla det här ganska grundläggande:

     Testa i JavaScript     

Det finns två viktiga saker att märka här: Först har vi en

Det innehåller den mycket enkla markeringen för vårt bildgalleri. Nej, det är nog inte mycket robust, men det ger oss något att arbeta med. Lägg märke till att vi hakar upp tre >s: en är vårt lilla testbibliotek, vilket hittades ovan. En är det galleri vi ska skapa. Den sista håller testerna för vårt galleri. Lägg också märke till vägarna till bilderna: miniatyrfilerna har "-tumbel" bifogat dem. Så här hittar vi den större versionen av bilden.

Jag vet att du kliar för att få kodning, så kasta den här i en gallery.css fil:

.galleri bakgrund: #ececec; överflöde: gömd; bredd: 620px;  .gallery> img margin: 20px 20px 0; vaddering: 0;  .gallery ul list-style-type: none; marginal: 20px; vaddering: 0; overflow: hidden;  .gallery li float: left; marginal: 0 10px;  .gallery li: first-of-type margin-left: 0px;  .gallery li: last-of-type margin-right: 0px; 

Nu kan du ladda upp det här i webbläsaren och se något så här:

Okej redan! Låt oss skriva några JavaScript, ska vi?


Beskrivning av vårt galleri

Öppna det där gallery.js fil. Så här börjar vi:

var Galleri = (funktion () var Galleri = , galleryPrototype = ; Gallery.create = funktion (id) var gal = Object.create (galleryPrototype); return gal;; returnera Gallery; ;

Vi lägger till mer, men det här är en bra start. Vi använder en egenfunktionell anonym funktion (eller ett direkt uttryckt funktionsuttryck) för att hålla allt ihop. Vår "interna" Galleri variabel kommer att returneras och vara värdet av vår publik Galleri variabel. Som du kan se, ringer du Gallery.create kommer att skapa ett nytt galleriobjekt med Object.create. Om du inte är bekant med Object.create, det skapar bara ett nytt objekt med objektet som du skickar det som det nya objektets prototyp (det är också ganska webbläsare). Vi fyller i den prototypen och lägger till vår Gallery.create metod också. Men nu ska vi skriva vårt första test:

var gal = Gallery.create ("gal-1"); TEST.areEqual (typ av gal, "objekt", "Galleri ska vara ett objekt");

Vi börjar med att skapa en "instans" av Galleri; då kör vi ett test för att se om det returnerade värdet är ett objekt.

Sätt dessa två linjer i vårt galleri-test.js; nu öppna vår index.html sida i en webbläsare och öppna en JavaScript-konsol. Du borde se något så här:


Bra! Vårt första prov passerar!


Skriva konstruktören

Därefter fyller vi i vårt Gallery.create metod. Som du kommer att se, oroar vi oss inte för att göra denna exemplar kod super robust, så vi använder vissa saker som inte är kompatibla i varje webbläsare som någonsin skapats. Nämligen, document.querySelector / document.querySelectorAll; också, vi använder bara modern händelsehantering för webbläsare. Gärna byta ditt favoritbibliotek om du vill.

Så, låt oss börja med några deklarationer:

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 = [];

Fyra variabler: särskilt vårt galleriobjekt och galleriets

    nod (vi ska använda jag och len om en minut). Sedan, sex fastigheter på vår tjej objekt:

    • gal.el är "root" noden på vårt galleri's markyp.
    • gal.imgs är en lista över
    • s som håller våra miniatyrbilder.
    • gal.displayImage är den stora bilden i vårt galleri.
    • gal.idx är indexet, den aktuella bilden visas.
    • gal.going är en booleska: det är det Sann om galleriet cyklar genom bilderna.
    • gal.ids kommer att finnas en lista över ID-erna för bilderna i vårt galleri. Om till exempel, om miniatyren heter "dog-thumb.jpg", är "hund" id och "dog.jpg" är bildstorleksbilden.

    Observera att DOM-elementen har querySelector och querySelectorAll metoder också. Vi kan använda gal.el.querySelector för att säkerställa att vi bara väljer element inom det här galleriet.

    Fyll nu i gal.ids med bild-ID:

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

    Ganska rakt framåt, rätt?

    Slutligen, låt oss koppla upp en händelsehanterare på

      .

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

      Vi börjar med att kontrollera om det lägsta elementet som fick klicket (e.target; vi oroar oss inte för oldie support här) finns i vår lista med bilder; eftersom nodelists har inte en index för metod använder vi array-versionen (om du inte är bekant med ring upp och tillämpa i JavaScript, se vår snabba tips om det ämnet.). Om det är större än -1, skickar vi det vidare till gal.set. Vi har inte skrivit den här metoden än, men vi kommer till det.

      Låt oss nu vända tillbaka till vårt galleri-test.js fil och skriv några tester för att se till att vår Galleri Exempel har rätt egenskaper:

      TEST.areEqual (gal.el.id, "gal-1", "Gallery.el ska vara den som vi angav"); TEST.areEqual (gal.idx, 0, "Galleriindex ska börja vid noll");

      Vårt första test verifierar att vår gallerikonstruktör har hittat rätt element. Det andra testet verifierar att indexet börjar vid 0. Du kan noga skriva en massa test för att verifiera att vi har rätt egenskaper, men vi ska skriva test för de metoder som kommer att använda dessa egenskaper, så det blir inte riktigt vara nödvändigt.


      Bygga prototypen

      Nu, låt oss flytta tillbaka till det galleryPrototype objekt som för närvarande är tomt. Det är här vi kommer att hysa alla våra metoder Galleri "Instanser". Låt oss börja med uppsättning metod: det här är den viktigaste metoden, eftersom det är den som faktiskt ändrar den visade bilden. Det tar antingen indexet för bilden eller ID-strängen i bilden.

      // inom 'galleryProtytype' set: funktion (i) if (typeof i === 'string') i = this.ids.indexOf (i);  this.displayImage.setAttribute ("src", "images /" + this.ids [i] + ".jpg"); returnera (this.idx = i); 

      Om metoden får ID-strängen kommer den att hitta rätt indexnummer för det ID-numret. Sedan satte vi displayImage's src till rätt bildväg och returnera det nya aktuella indexet medan du ställer in det som det aktuella indexet.

      Nu, låt oss testa den metoden (tillbaka i galleri-test.js):

      TEST.areEqual (gal.set (4), 4, "Gallery.set (med nummer) ska returnera samma nummer som skickats in"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images_24 / javascript-testing-from-scratch.jpg", "Gallery.set (med nummer) ska ändra den visade bilden"); TEST.areEqual (gal.set ("post"), 3, "Gallery.set (med sträng) ska flytta till lämplig bild"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images / post.jpg", "Gallery.set (med sträng) ska ändra de visade bilderna");

      Vi testar vår testmetod med både ett tal och en strängparameter för uppsättning. I det här fallet kan vi kontrollera src för bilden och se till att användargränssnittet justeras i enlighet därmed; Det är inte alltid möjligt eller nödvändigt att se till vad användaren ser är att svara på lämpligt sätt (utan att använda något sådant). Det är där klick-typen av testning är användbar. Vi kan dock göra det här, så vi ska.

      Dessa tester bör passera. Du bör också kunna klicka på miniatyrerna och visa de större versionerna. Ser bra ut!

      Så, låt oss gå vidare till några metoder som rör sig mellan bilderna. Dessa kan vara användbara om du vill ha "nästa" och "tidigare" knappar för att cykla genom bilderna (vi kommer inte ha dessa knappar, men vi lägger in stödjande metoder).

      För det mesta är det inte svårt att flytta till nästa och tidigare bilder. De knepiga delarna går till nästa bild när du är på den sista eller den föregående när du är först.

      // inuti 'galleryPrototype' next: funktion () if (this.idx === this.imgs.length - 1) returnera detta.set (0);  returnera this.set (this.idx + 1); , prev: function () if (this.idx === 0) returnera this.set (this.imgs.length - 1);  returnera this.set (this.idx - 1); , curr: funktion () returnera this.idx; ,

      Okej, så det är faktiskt inte så knepigt. Båda dessa metoder är "socker" metoder för användning uppsättning. Om vi ​​är på den sista bilden (this.idx === this.imgs.length -1), vi in (0). Om vi ​​är vid förstathis.idx === 0), vi set (this.imgs.length -1). Annars lägger du bara till eller subtraherar en från det aktuella indexet. Glöm inte att vi återvänder exakt vad som returneras från uppsättning ring upp.

      Vi har också curr metod också. Det är inte komplicerat alls: det returnerar bara det aktuella indexet. Vi ska testa det lite senare.

      Så, låt oss testa dessa metoder.

      TEST.areEqual (gal.next (), 4, "Gallery ska avancera på .next ()"); TEST.areEqual (gal.prev (), 3, "Galleri ska gå tillbaka på .prev ()");

      Dessa kommer efter våra tidigare tester, så 4 och 3 är de värden som vi förväntar oss. Och de passerar!

      Det finns bara ett stycke kvar: det är automatisk fotocykling. Vi vill kunna ringa gal.start () spelar genom bilderna. Självklart är de en gal.stop () metod.

      // inuti 'galleryPrototype' start: funktion (tid) var thiz = this; tid = tid || 3000; this.interval = setInterval (funktion () thiz.next ();, tid); this.going = true; återvänd sant; , stopp: funktion () clearInterval (this.interval); this.going = false; återvänd sant; ,

      Vår Start Metoden tar parameter: antalet millisekunder som en bild visas. Om ingen parameter anges, standard till 3000 (3 sekunder). Sedan använder vi helt enkelt setInterval på en funktion som kommer att ringa Nästa vid lämplig tidpunkt. Självklart kan vi inte glömma att ställa in this.going till Sann. Slutligen återkommer vi Sann.

      sluta är inte för svårt. Eftersom vi sparade intervallet som this.interval, vi kan använda clearInterval för att avsluta det. Sedan satte vi this.going att falska och återvända sant.

      Vi har två praktiska funktioner för att låta veta om galleriet är looping:

      isGoing: function () returnera this.going; , isStopped: function () return! this.going; 

      Nu, låt oss testa denna funktion.

      gal.set (0); TEST.areEqual (gal.start (), true, "Gallery ska vara looping"); TEST.areEqual (gal.curr (), 0, "Nuvarande bildindex ska vara 0"); setTimeout (funktion () TEST.areEqual (gal.curr (), 1, "Nuvarande bildindex ska vara 1"); TEST.areEqual (gal.isGoing (), true, "Gallery should go"); TEST. areEqual (gal.stop (), true, "Gallery should be stopped"); setTimeout (funktion () TEST.areEqual (gal.curr (), 1, "Aktuell bild borde fortfarande vara 1"); TEST.areEqual gal.isStopped (), true, "Gallery should still be stopped");, 3050);, 3050);

      Det är lite mer komplicerat än våra tidigare tester: Vi börjar med att använda gal.set (0) för att se till att vi börjar i början. Då ringer vi gal.start () att starta loopingen. Därefter testar vi det gal.curr () returnerar 0, vilket betyder att vi fortfarande tittar på den första bilden. Nu använder vi en setTimeout att vänta 3050ms (bara lite mer än 3 sekunder) innan vi fortsätter våra test. Inuti det setTimeout, vi ska göra en annan gal.curr (); indexet ska nu vara 1. Då ska vi testa det gal.isGoing () är sant. Nästa stoppar vi galleriet gal.stop (). Nu använder vi en annan setTimeout att vänta en annan nästan 3 sekunder; om galleriet verkligen har slutat, så kommer inte bilden att slingras gal.curr () bör fortfarande vara 1; Det är vad vi testar inuti timeouten. Slutligen försäkrar vi oss isStopped Metoden fungerar.

      Om dessa tester har passerat, gratulerar! Vi har slutfört vår Galleri och dess test.


      Slutsats

      Om du inte har testat förut, hoppas jag att du har sett hur enkelt testningen kan vara i JavaScript. Som jag nämnde i början av denna handledning kommer bra testning sannolikt att kräva att du skriver din JavaScript lite annorlunda än du kanske brukar. Jag har dock funnit att det lätt kan testas JavaScript är också lätt att underhålla JavaScript.

      Jag lämnar dig med flera länkar som du kan hitta användbara när du går vidare och skriver bra JavaScript och bra test.

      • QUnit - Enhetsprovsviten (Handledning på QUnit)
      • Jasmine - BDD-ram (Tutorial on Jasmine)
      • JavaScript-test med Assert
      • Testdriven JavaScript (bok) (Provkod)