JavaScript testen vanuit Scratch

Dit is waarschijnlijk niet de eerste zelfstudie over testen die je ooit hebt gezien. Maar misschien heb je je twijfels gehad over testen en heb je nooit de tijd genomen om ze te lezen. Per slot van rekening kan het zonder meer extra werk lijken.

Deze zelfstudie is bedoeld om je meningen te veranderen. We beginnen bij het allereerste begin: wat is testen en waarom zou je het moeten doen? Daarna zullen we kort praten over het schrijven van testbare code, voordat we feitelijk testen doen! Laten we ernaar toe gaan!


Liever een screencast?

Deel 1


Deel 2


Deel 3



Testen definiëren

Heel eenvoudig, testing is het idee om een ​​reeks vereisten te hebben waaraan een bepaald stuk code moet voldoen om robuust genoeg te zijn om in de echte wereld te worden gebruikt. Vaak schrijven we wat JavaScript en openen het vervolgens in de browser en klikken een beetje rond om te controleren of alles werkt zoals we zouden verwachten. Hoewel dat soms nodig is, is dat niet het soort testen waar we het hier over hebben. Ik hoop zelfs dat deze tutorial je zal overtuigen dat quick-and-dirty zelftesten een meer rigide testprocedure moet aanvullen: zelftesten is prima, maar een grondige lijst met vereisten staat voorop.


Redenen voor het testen

Zoals u wellicht vermoedt, is het probleem met de JavaScript-test voor vernieuwen en klikken tweeledig:

  1. We herinneren ons misschien niet dat we iets hebben gecontroleerd; zelfs als we dat doen, controleren we mogelijk niet opnieuw na code-tweaks.
  2. Mogelijk zijn sommige delen van de code op die manier niet echt testbaar.

Door te schrijven testen die alles wat uw code moet doen controleren, kunt u controleren of uw code in de beste staat is voordat u deze daadwerkelijk op een website gebruikt. Tegen de tijd dat iets daadwerkelijk in een browser wordt uitgevoerd, zijn er waarschijnlijk meerdere faalpunten. Door het schrijven van tests kunt u zich op elk testdeel afzonderlijk concentreren; als elk stuk zijn werk goed doet, zouden dingen zonder probleem moeten samenwerken (het testen van afzonderlijke onderdelen zoals deze wordt eenheidstest genoemd).


Testbare code schrijven

Als u een meertalige programmeertaal bent), hebt u mogelijk in andere talen getest. Maar ik heb in JavaScript een ander beest getest om te verslaan. U bouwt tenslotte niet teveel gebruikersinterfaces in, laten we zeggen, PHP of Ruby. Vaak doen we DOM-werk in JavaScript, en hoe test je dat precies?

Welnu, het DOM-werk is niet waarvoor je tests wilt schrijven; het is de logica. Het is duidelijk dat de sleutel hier is om je logica en je UI-code te scheiden. Dit is niet altijd gemakkelijk; Ik heb mijn deel van de door jQuery aangedreven UI geschreven en het kan vrij rommelig worden vrij snel. Dit maakt het niet alleen moeilijk om te testen, maar verweven logica en UI-code kunnen ook moeilijk te wijzigen zijn wanneer het gewenste gedrag verandert. Ik heb gevonden dat het gebruik van methodieken zoals sjablonen (ook sjablonen) en pub / sub (ook, pub / sub) schrijven beter maken, meer testbare code makkelijker maken.

Nog een ding, voordat we beginnen met coderen: hoe schrijven we onze tests? Er zijn talloze testbibliotheken die u kunt gebruiken (en veel goede zelfstudies om u te leren deze te gebruiken, zie de links als het einde). We gaan echter vanuit het niets een kleine testbibliotheek bouwen. Het zal niet zo luxe zijn als sommige bibliotheken, maar je zult zien wat er precies aan de hand is.

Met dit in gedachten, laten we aan het werk gaan!


Een testmini-framework bouwen

We gaan een microfotogalerie maken: een eenvoudige lijst met miniaturen, met één afbeelding die de volledige grootte boven hen weergeeft. Maar laten we eerst de testfunctie uitbouwen.

Naarmate u meer leert over het testen en testen van bibliotheken, vindt u tal van testmethoden voor het testen van allerlei specifieke kenmerken. Het kan echter allemaal worden ingegeven door de vraag of twee dingen gelijk zijn of niet: bijvoorbeeld,

  • Is de waarde terug van deze functie gelijk aan wat we verwachtten terug te krijgen?
  • Is deze variabele van het type dat we ervan verwacht hadden??
  • Heeft deze array het verwachte aantal items?

Dus, hier is onze methode om te testen op gelijkheid:

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

Het is vrij eenvoudig: de methode neemt drie parameters in beslag. De eerste twee worden vergeleken en als ze gelijk zijn, gaan de tests voorbij. De derde parameter is een bericht dat de test beschrijft. In deze eenvoudige testbibliotheek voeren we onze tests gewoon uit naar de console, maar u kunt HTML-uitvoer maken met de juiste CSS-stijl als u dat wilt.

Hier is de areNotEqual methode (binnen dezelfde TEST voorwerp):

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

Je zult de laatste twee regels van opmerken zijn gelijk en areNotEqual hetzelfde. Dus we kunnen ze eruit halen als volgt:

var TEST = areEqual: function (a, b, msg) return this._output (a === b, msg), areNotEqual: function (a, b, msg) return this._output (a! == b, msg); , _output: function (result, msg) console [result? "log": "warn"] ((resultaat? "PASS:": "FAIL:") + msg); terugkeer resultaat; ;

Super goed! Het mooie hier is dat we andere 'suiker'-methoden kunnen toevoegen met behulp van de methoden die we al hebben geschreven:

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

Je kunt hier alleen mee experimenteren; na het doornemen van deze tutorial, heb je een goed idee van hoe je het moet gebruiken.


Voorbereiding op onze galerij

Laten we dus een supereenvoudige fotogalerij maken met onze mini TEST kader om enkele tests te maken. Ik zal hier vermelden dat, terwijl testgestuurde ontwikkeling een goede oefening is, we deze in deze zelfstudie niet zullen gebruiken, voornamelijk omdat het niet iets is dat je in een enkele tutorial kunt leren; het kost echt veel oefening Grok. Als je begint, is het makkelijker om een ​​beetje code te schrijven en het vervolgens te testen.

Dus laten we beginnen. Natuurlijk hebben we wat HTML nodig voor onze galerij. We houden dit vrij eenvoudig:

     Testen in JavaScript     

Er zijn twee belangrijke dingen die hier moeten worden opgemerkt: ten eerste hebben we een

dat bevat de zeer eenvoudige opmaak voor onze afbeeldingengalerij. Nee, het is waarschijnlijk niet erg robuust, maar het geeft ons wel iets mee. Merk vervolgens op dat we er drie aan het aansluiten zijn >s: een is onze kleine testbibliotheek, zoals hierboven gevonden. Een daarvan is de galerij die we zullen maken. De laatste bevat de tests voor onze galerij. Let ook op de paden naar de afbeeldingen: de thumbnail-bestandsnamen hebben "-thumb" eraan toegevoegd. Dat is hoe we de grotere versie van de afbeelding zullen vinden.

Ik weet dat je het moeilijk vindt om te coderen, dus gooi dit in een gallery.css het dossier:

.galerij background: #ececec; overloop verborgen; breedte: 620 px;  .gallery> img margin: 20px 20px 0; opvulling: 0;  .gallery ul list-style-type: none; marge: 20px; opvulling: 0; overloop verborgen;  .gallery li float: left; marge: 0 10px;  .gallery li: first-of-type margin-left: 0px;  .gallery li: last-of-type margin-right: 0px; 

Nu kunt u dit in uw browser laden en zoiets zien als dit:

OK AL! Laten we wat JavaScript schrijven, zullen wij?


Een overzicht van onze galerij

Open dat gallery.js het dossier. Dit is hoe we beginnen:

var Galerij = (functie () var Galerij = , galleryPrototype = ; Gallery.create = function (id) var gal = Object.create (galleryPrototype); return gal;; return Gallery; ()) ;

We zullen meer toevoegen, maar dit is een goed begin. We gebruiken een zelfoproepende anonieme functie (of een onmiddellijk opgeroepen functie-uitdrukking) om alles bij elkaar te houden. Onze "interne" Galerij variabele wordt geretourneerd en is de waarde van ons publiek Galerij variabel. Zoals je kunt zien, roepend Gallery.create maakt een nieuw galerij-object met Object.create. Als u niet bekend bent met Object.create, het maakt gewoon een nieuw object aan met behulp van het object dat u het doorgeeft als het prototype van het nieuwe object (het is ook behoorlijk browser-compatibel). We zullen dat prototype invullen en toevoegen aan onze Gallery.create methode ook. Maar laten we nu onze eerste test schrijven:

var gal = Gallery.create ("gal-1"); TEST.areEqual (typeof gal, "object", "Galerij moet een object zijn");

We beginnen met het maken van een "instantie" van Galerij; Vervolgens voeren we een test uit om te zien of de geretourneerde waarde een object is.

Zet deze twee lijnen in onze gallery-test.js; nu, open onze index.html pagina in een browser openen en een JavaScript-console openen. Je zou zoiets als dit moeten zien:


Super goed! Onze eerste test is voorbij!


De constructeur schrijven

Vervolgens vullen we onze Gallery.create methode. Zoals je zult zien, maken we ons geen zorgen over het superstevig maken van deze voorbeeldcode. Daarom gebruiken we enkele dingen die niet compatibel zijn in elke browser die ooit is gemaakt. Namelijk, document.querySelector / document.querySelectorAll; we zullen ook alleen moderne browserafhandeling gebruiken. Als je wilt, kun je je favoriete bibliotheek vervangen.

Laten we beginnen met enkele verklaringen:

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

Vier variabelen: met name ons galerij-object en de galerij

    knooppunt (we zullen gebruiken ik en len binnen een minuut). Vervolgens zes eigenschappen op onze meisje voorwerp:

    • gal.el is het "root" -knooppunt op de markyp van onze galerij.
    • gal.imgs is een lijst van de
    • s die onze miniaturen bevatten.
    • gal.displayImage is de grote afbeelding in onze galerij.
    • gal.idx is de index, de huidige afbeelding wordt bekeken.
    • gal.going is een boolean: het is waar als de galerij door de afbeeldingen fietst.
    • gal.ids zal een lijst zijn van de ID's voor de afbeeldingen in onze galerij. Als de miniatuur bijvoorbeeld de naam 'dog-thumb.jpg' heeft, is 'hond' de ID en is 'dog.jpg' de afbeelding op het scherm..

    Merk op dat DOM-elementen dat wel hebben querySelector en querySelectorAll methoden ook. We kunnen gebruiken gal.el.querySelector om ervoor te zorgen dat we alleen elementen selecteren in de opmaak van deze galerij.

    Vul nu in gal.ids met de afbeelding-ID's:

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

    Vrij rechttoe rechtaan, toch?

    Eindelijk, laten we een event handler op de kabel aansluiten

      .

      ul.addEventListener ("klik", functie (e) var i = [] .indexOf.call (gal.imgs, e.target); if (i> -1) gal.set (i);, false);

      We beginnen met controleren of het laagste element dat de klik heeft ontvangen (e.target; we maken ons hier geen zorgen over de ondersteuning van oldie) staat in onze lijst met afbeeldingen; sinds nodelists hebben geen index van methode gebruiken we de array-versie (als u niet bekend bent met telefoontje en van toepassing zijn in JavaScript, zie onze snelle tip over dat onderwerp.). Als dat groter is dan -1, geven we het door aan gal.set. We hebben deze methode nog niet geschreven, maar we komen eraan.

      Laten we nu terugkeren naar onze gallery-test.js bestand en schrijf een aantal tests om ervoor te zorgen dat onze Galerij instantie heeft de juiste eigenschappen:

      TEST.areEqual (gal.el.id, "gal-1", "Gallery.el zou degene moeten zijn die we hebben opgegeven"); TEST.areEqual (gal.idx, 0, "Gallery-index moet bij nul beginnen");

      Onze eerste test verifieert of onze galerijbouwer het juiste element heeft gevonden. De tweede test verifieert of de index begint met 0. Je zou waarschijnlijk een aantal tests kunnen schrijven om te controleren of we de juiste eigenschappen hebben, maar we zullen tests schrijven voor de methoden die deze eigenschappen zullen gebruiken, dus dat zal niet echt nodig zijn.


      Het prototype bouwen

      Laten we daar nu naar teruggaan galleryPrototype object dat momenteel leeg is. Dit is waar we alle methoden van ons zullen huisvesten Galerij "Instanties." Laten we beginnen met de reeks methode: dit is de meest belangrijke methode, want het is degene die het weergegeven beeld daadwerkelijk verandert. Het neemt de index van de afbeelding of de ID-reeks van de afbeelding.

      // in 'galleryProtytype' set: function (i) if (type van i === 'string') i = this.ids.indexOf (i);  this.displayImage.setAttribute ("src", "images /" + this.ids [i] + ".jpg"); return (this.idx = i); 

      Als de methode de ID-tekenreeks krijgt, wordt het juiste indexnummer voor die ID gevonden. Vervolgens hebben we de Beeld weergeven's src naar het juiste afbeeldingspad en retourneer de nieuwe huidige index terwijl deze wordt ingesteld als de huidige index.

      Laten we die methode testen (terug in gallery-test.js):

      TEST.areEqual (gal.set (4), 4, "Gallery.set (met nummer) moet hetzelfde ingevoerde nummer retourneren"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images_24 / javascript-testing-from-scratch.jpg", "Gallery.set (with number) moet de weergegeven afbeelding wijzigen"); TEST.areEqual (gal.set ("post"), 3, "Gallery.set (with string) moet naar de juiste afbeelding gaan"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images / post.jpg", "Gallery.set (with string) moet de weergegeven afbeeldingen wijzigen");

      We testen onze testmethode met zowel een nummer- als een stringparameter voor reeks. In dit geval kunnen we de src voor de afbeelding en zorg ervoor dat de gebruikersinterface overeenkomstig wordt aangepast; het is niet altijd mogelijk of noodzakelijk om er zeker van te zijn dat wat de gebruiker ziet op de juiste manier reageert (zonder iets als dit te gebruiken); dat is waar het klik-rond type van testen nuttig is. We kunnen het hier echter wel doen, dus dat zullen we doen.

      Die tests zouden moeten slagen. Je moet ook op de miniaturen kunnen klikken en de grotere versies kunnen laten weergeven. Ziet er goed uit!

      Laten we dus verder gaan met enkele methoden die tussen de afbeeldingen worden verplaatst. Deze kunnen handig zijn als je de knoppen "volgende" en "vorige" wilt gebruiken om door de afbeeldingen te bladeren (we hebben deze knoppen niet, maar we zullen de ondersteuningsmethoden gebruiken).

      Grotendeels, verhuizen naar de volgende en vorige beelden is niet moeilijk. De lastige delen gaan naar de volgende afbeelding als je bij de laatste bent, of de vorige als je bij de eerste bent.

      // inside 'galleryPrototype' next: function () if (this.idx === this.imgs.length - 1) return this.set (0);  retourneer deze.set (this.idx + 1); , vorige: function () if (this.idx === 0) return this.set (this.imgs.length - 1);  retourneer deze.set (this.idx - 1); , curr: function () retourneer dit.idx; ,

      Oké, dus het is eigenlijk niet zo lastig. Beide methoden zijn "suikermethoden" voor gebruik reeks. Als we bij de laatste afbeelding zijn (this.idx === this.imgs.length -1), wij te stellen (0). Als we bij de eerste zijn (this.idx === 0), wij set (this.imgs.length -1). Anders voegt u er gewoon een toe of trekt u er een af ​​van de huidige index. Vergeet niet dat we precies teruggeven wat is teruggekeerd uit de reeks telefoontje.

      We hebben ook de curr methode ook. Het is helemaal niet gecompliceerd: het geeft gewoon de huidige index terug. We zullen het een beetje later testen.

      Laten we deze methoden testen.

      TEST.areEqual (gal.next (), 4, "Galerij moet verdergaan op .next ()"); TEST.areEqual (gal.prev (), 3, "Galerij moet teruggaan naar .prev ()");

      Deze komen na onze vorige tests, dus 4 en 3 zijn de waarden die we zouden verwachten. En ze gaan voorbij!

      Er is nog maar één stuk over: dat is de automatische fotocyclus. We willen kunnen bellen gal.start () door de beelden spelen. Natuurlijk zullen ze een zijn gal.stop () methode.

      // binnenin 'galleryPrototype' start: functie (tijd) var thiz = this; tijd = tijd || 3000; this.interval = setInterval (function () thiz.next ();, time); this.going = true; geef waar terug; , stop: function () clearInterval (this.interval); this.going = false; geef waar terug; ,

      Onze begin methode neemt parameter: het aantal milliseconden dat één afbeelding wordt getoond; als er geen parameter wordt gegeven, wordt standaard 3000 (3 seconden) gebruikt. Vervolgens gebruiken we gewoon setInterval op een functie die zal bellen volgende op het juiste moment. Natuurlijk kunnen we niet vergeten om in te stellen this.going naar waar. Eindelijk keren we terug waar.

      hou op is niet zo moeilijk. Omdat we het interval hebben opgeslagen als this.interval, we kunnen gebruiken clearInterval om het te beëindigen. Toen gingen we aan de slag this.going naar false en return true.

      We hebben twee handige functies om te laten weten of de galerij doorloopt:

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

      Laten we deze functionaliteit eens testen.

      gal.set (0); TEST.areEqual (gal.start (), true, "Galerij zou moeten lussen"); TEST.areEqual (gal.curr (), 0, "Huidige beeldindex moet 0 zijn"); setTimeout (function () TEST.areEqual (gal.curr (), 1, "Current image index should be 1"); TEST.areEqual (gal.isGoing (), true, "Gallery should go going"); TEST. areEqual (gal.stop (), true, "Galerij moet worden gestopt"); setTimeout (function () TEST.areEqual (gal.curr (), 1, "Huidige afbeelding moet nog steeds 1 zijn"); TEST.areEqual () gal.isStopped (), waar, "Galerij moet nog worden gestopt");, 3050);, 3050);

      Het is een beetje ingewikkelder dan onze vorige testsets: we beginnen met het gebruik gal.set (0) om ervoor te zorgen dat we bij het begin beginnen. Dan bellen we gal.start () om de lus te starten. Vervolgens testen we dat gal.curr () geeft 0 terug, wat betekent dat we nog steeds de eerste afbeelding bekijken. Nu gebruiken we een setTimeout wacht 3050ms (iets meer dan 3 seconden) voordat we onze tests voortzetten. Binnen dat setTimeout, we zullen een andere doen gal.curr (); de index zou nu 1 moeten zijn. Dan zullen we dat testen gal.isGoing () is waar. Vervolgens stoppen we de galerij gal.stop (). Nu gebruiken we een andere setTimeout nog eens bijna 3 seconden wachten; als de galerij echt is gestopt, loopt de afbeelding niet in een lus, dus gal.curr () zou nog steeds 1 moeten zijn; dat testen we binnen de time-out. Eindelijk zorgen we ervoor dat onze is gestopt methode werkt.

      Als deze tests voorbij zijn, gefeliciteerd! We hebben onze Galerij en zijn testen.


      Conclusie

      Als je nog niet eerder hebt geprobeerd te testen, hoop ik dat je hebt gezien hoe eenvoudig testen in JavaScript kan zijn. Zoals ik aan het begin van deze tutorial al zei, zal bij goede tests u waarschijnlijk uw JavaScript een beetje anders moeten schrijven dan u gewend bent. Ik heb echter geconstateerd dat eenvoudig te testen JavaScript ook gemakkelijk te onderhouden JavaScript is.

      Ik laat je achter met verschillende links die je mogelijk handig vindt als je doorgaat en goede JavaScript-code en goede tests schrijft.

      • QUnit - een eenheidstestsuite (zelfstudie over QUnit)
      • Jasmine - BDD-raamwerk (zelfstudie over jasmijn)
      • JavaScript testen met Assert
      • Testgedreven JavaScript (boek) (Voorbeeldhoofdstuk)