Testarea JavaScript de la zero

Probabil acest lucru nu este primul tutorial pe care l-ați văzut vreodată. Dar probabil că ați avut îndoieli cu privire la teste și nu ați avut niciodată timp să le citiți. La urma urmei, poate părea o muncă suplimentară fără niciun motiv.

Acest tutorial intenționează să vă schimbe opiniile. Vom începe de la bun început: ce este testarea și de ce ar trebui să o faceți? Apoi, vom vorbi pe scurt despre scrierea codului testabil, înainte de a ști, de fapt, să faceți niște teste! Hai să ajungem la asta!


Prefer un scenariu?

Partea 1


Partea 2


Partea 3



Definirea testelor

Foarte simplu, testarea este ideea de a avea un set de cerințe pe care o anumită bucată de cod trebuie să treacă pentru a fi suficient de robust pentru a fi utilizat în lumea reală. Adesea, vom scrie ceva JavaScript și apoi îl vom deschide în browser și vom face un click în jur pentru a ne asigura că totul funcționează așa cum ne-am fi așteptat. Deși acest lucru este uneori necesar, nu este tipul de testare despre care vorbim aici. De fapt, sper că acest tutorial vă va convinge că auto-testarea rapidă și murdară ar trebui să completeze o procedură de testare mai rigidă: auto-testare este bine, dar o listă amănunțită de cerințe este paramount.


Motive pentru testare

Așa cum ați putea ghici, problema cu testarea javascript-ului de refresh-and-click este dublă:

  1. S-ar putea să nu uităm să verificăm ceva; chiar dacă o facem, s-ar putea să nu re-verificăm atunci după coduri de cod.
  2. Pot exista anumite părți ale codului care nu sunt într-adevăr testabile în acest fel.

În scris teste care verifică tot ce trebuie să facă codul dvs., puteți verifica dacă codul dvs. este în cea mai bună formă înainte de a-l utiliza efectiv pe un site web. Până când ceva se execută într-un browser, probabil există mai multe puncte de eșec. Testele de scriere vă permit să vă concentrați pe fiecare parte testabilă individual; dacă fiecare piesă își face dreptul de lucru, lucrurile ar trebui să funcționeze împreună fără probleme (testarea pieselor individuale, cum ar fi acest lucru se numește testare unitate).


Scrierea codului de testare

Dacă sunteți un poliglot de programare), este posibil să fi făcut teste în alte limbi. Dar am găsit testarea în JavaScript a unei ființe diferite pentru a ucide. La urma urmei, nu construiți prea multe interfețe de utilizator în, de exemplu, PHP sau Ruby. De multe ori, facem munca DOM în JavaScript și cum anume încercați acest lucru?

Ei bine, munca DOM nu este ceea ce vrei să scrii teste; este logica. Evident, cheia aici este să vă separați logica și codul dvs. UI. Aceasta nu este întotdeauna ușurință; Mi-am scris partea mea echitabilă din interfața cu jQuery și poate deveni destul de dezordonată destul de repede. Nu numai că acest lucru face dificilă testarea, dar și logica interconectată și codul UI pot fi greu de modificat atunci când se schimbă comportamentul dorit. Am gasit folosind metodologii cum ar fi template-uri (de asemenea, template-uri) si pub / sub (de asemenea, pub / sub).

Încă un lucru, înainte să începem să codificăm: cum scriem testele noastre? Există numeroase biblioteci de testare pe care le puteți folosi (și multe tutoriale bune pentru a vă învăța să le folosiți, vedeți linkurile ca sfârșit). Cu toate acestea, vom construi o mică bibliotecă de testare de la zero. Nu va fi la fel de fantezie ca unele biblioteci, dar veți vedea exact ce se întâmplă.

Având în vedere acest lucru, să mergem la treabă!


Construirea unui test Mini-cadru

Vom construi o galerie de micro-fotografii: o listă simplă de miniaturi, cu o singură imagine aflată deasupra lor. Dar mai întâi, să construim funcția de testare.

Pe măsură ce aflați mai multe despre testarea și testarea bibliotecilor, veți găsi numeroase metode de testare pentru a testa tot felul de specificuri. Cu toate acestea, totul se poate reduce la faptul dacă două lucruri sunt egale sau nu: de exemplu,

  • Valoarea returnată din această funcție este egală cu ceea ce am așteptat să ne întoarcem?
  • Este această variabilă de tipul pe care ne-am așteptat să fie?
  • Această matrice are numărul așteptat de elemente?

Deci, iată metoda noastră de a testa pentru egalitate:

var TEST = areEqual: funcție (a, b, msg) var rezultat = (a === b); console.log ((rezultat? "PASS:": "FAIL:") + msg); rezultatul retur; ;

Este destul de simplu: metoda are trei parametri. Primele două sunt comparate, iar dacă sunt egale, testele trec. Al treilea parametru este un mesaj care descrie testul. În această bibliotecă simplă de testare, suntem doar de ieșire testele noastre la consola, dar puteți crea ieșire HTML cu styling CSS corespunzătoare, dacă doriți.

Iată-l areNotEqual (în același TEST obiect):

areNotEqual: funcția (a, b, msg) var rezultat = (a! == b); console.log ((rezultat? "PASS:": "FAIL:") + msg); rezultatul retur; 

Veți observa ultimele două linii sunt egale și areNotEqual la fel. Deci, le putem trage astfel:

var TEST = areEqual: funcția (a, b, msg) returnează acest._output (a === b, msg), areNotEqual: funcția (a, b, msg) return this._output b, msg); , _output: funcție (rezultat, msg) console [rezultat? "jurnal": "avertizați"] ((rezultat "PASS:": "FAIL:") + msg); rezultatul retur; ;

Grozav! Un lucru minunat este că putem adăuga alte metode de "zahăr" folosind metodele pe care le-am scris deja:

TEST.isTypeOf = funcția (obj, type, msg) returnați acest.Equal (typeof obj, type, msg); ; TEST.isAnInstanceOf = funcția (obj, type, msg) returnează acest._output (exemplu de tip, msg);  TEST.isGreaterThan = funcția (val, min, msg) returnează acest._output (val> min, msg); 

Puteți experimenta acest lucru pe cont propriu; după ce ați trecut prin acest tutorial, veți avea o idee bună despre cum să îl utilizați.


Pregătirea pentru galeria noastră

Deci, să creăm o galerie foto super-simplă, folosind mini-ul nostru TEST cadru pentru a crea câteva teste. Voi menționa aici că, în timp ce dezvoltarea bazată pe teste este o practică excelentă, nu o vom folosi în acest tutorial, în primul rând pentru că nu este ceva ce puteți învăța într-un singur tutorial; este nevoie de o mulțime de practică pentru a într-adevăr GROK. Când începeți, este mai ușor să scrieți un pic de cod și apoi să îl testați.

Asadar, hai sa incepem. Desigur, vom avea nevoie de HTML pentru galeria noastră. Vom păstra acest lucru destul de simplu:

     Testarea în JavaScript     

Există două lucruri principale care merită remarcate aici: în primul rând, avem un a

care deține marcajul foarte simplu pentru galeria noastră de imagini. Nu, probabil că nu este foarte robust, dar ne dă ceva de lucru. Apoi, observați că suntem trei >s: una este biblioteca noastră mică de testare, așa cum am găsit mai sus. Una este galeria pe care o vom crea. Ultimul face testele pentru galeria noastră. Observați, de asemenea, căile spre imagini: numele de fișiere miniatură au "-thumb" atașat la ele. Astfel vom găsi versiunea mai mare a imaginii.

Știu că mâncați pentru a obține codificare, deci aruncați asta într-un gallery.css fişier:

.galerie background: #ececec; overflow: ascuns; lățime: 620px;  .gallery> img margine: 20px 20px 0; umplutura: 0;  .gallery ul list-style-type: none; margine: 20 pixeli; umplutura: 0; overflow: ascuns;  .gallery li float: left; margine: 0 10px;  .gallery li: prima de tip margin-left: 0px;  .gallery li: ultimul tip margin-right: 0px; 

Acum, puteți încărca acest lucru în browser-ul dvs. și puteți vedea ceva de genul:

OKAY ALREADY! Să scriem niște JavaScript, trebuie noi?


Descrierea Galeriei noastre

Deschideți asta gallery.js fişier. Iată cum începem:

var Galerie = (function () var Galerie = , galleryPrototype = ; Galerie.create = functie (id) var gal = Object.create (galleryPrototype); ;

Vom adăuga mai multe, dar acesta este un început bun. Folosim o funcție anonimă (sau o expresie de funcții invocată imediat) pentru a păstra totul împreună. Noastre "interne" Galerie variabila va fi returnată și va fi valoarea publicului nostru Galerie variabil. După cum puteți vedea, sunați Gallery.create va crea un nou obiect galerie cu Object.create. Dacă nu sunteți familiarizați cu Object.create, doar creează un obiect nou folosind obiectul pe care îl transmiteți ca prototip nou de obiect (este destul de compatibil cu browserul). Vom completa acest prototip și vom adăuga la noi Gallery.create de asemenea. Dar, acum să scriem primul nostru test:

var gal = Galerie.create ("gal-1"); TEST.areEqual (tip gal, "obiect", "Galerie ar trebui să fie un obiect");

Începem prin crearea unei "instanțe" Galerie; apoi executați un test pentru a vedea dacă valoarea returnată este un obiect.

Puneți aceste două linii în noi galerie-test.js; acum, deschide-ne index.html într-un browser și deschideți o consolă JavaScript. Ar trebui să vedeți ceva de genul:


Grozav! Primul nostru test este trecerea!


Scriind constructorul

Apoi, vom completa informațiile noastre Gallery.create metodă. După cum veți vedea, nu ne facem griji să facem acest exemplu de cod super-robust, deci vom folosi câteva lucruri care nu sunt compatibile în fiecare browser creat vreodată. Și anume, document.querySelector / document.querySelectorAll; de asemenea, vom folosi numai manipularea evenimentelor moderne ale browserului. Simțiți-vă liber să înlocuiți biblioteca preferată dacă doriți.

Deci, să începem cu câteva declarații:

var gal = Object.create (galeriePrototype), 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 = [];

Patru variabile: în special, obiectul galeriei și galeria

    nod (vom folosi eu și Len Intr-un minut). Apoi, șase proprietăți ale noastre fată obiect:

    • gal.el este nodul "rădăcină" de pe markyp din galeria noastră.
    • gal.imgs este o listă cu
    • care ține miniaturile noastre.
    • gal.displayImage este imaginea mare în galeria noastră.
    • gal.idx este indexul, imaginea curentă fiind vizualizată.
    • gal.going este un boolean: este Adevărat dacă galeria se deplasează prin imagini.
    • gal.ids va fi o listă cu ID-urile pentru imaginile din galeria noastră. De exemplu, dacă miniatură este numită "dog-thumb.jpg", atunci "câine" este ID-ul și "dog.jpg" este imaginea dimensiunii afișajului.

    Observați că elementele DOM au querySelector și querySelectorAll și metodele. Putem folosi gal.el.querySelector pentru a ne asigura că selectăm numai elemente din marcajul acestei galerii.

    Acum, completați-vă gal.ids cu ID-urile imaginilor:

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

    Destul de direct, corect?

    În cele din urmă, hai să facem un handler de evenimente pe

      .

      ul.addEventListener ("click", funcția (e) var i = [] .indexOf.call (gal.imgs, e.target); dacă (i> -1) gal.set (i) fals);

      Începem prin a verifica dacă cel mai mic element care a primit clicul (e.target; nu ne facem griji despre suportul vechiIE aici) este în lista noastră de imagini; de cand NodeLists nu au un Index de metoda, vom folosi versiunea de matrice (dacă nu sunteți familiarizat cu apel și aplica în JavaScript, consultați sfatul nostru rapid cu privire la acest subiect.). Dacă este mai mare de -1, o vom trece la gal.set. Nu am scris încă această metodă, dar vom ajunge la ea.

      Acum, hai să ne întoarcem la noi galerie-test.js fișier și scrieți câteva teste pentru a ne asigura că suntem Galerie instanța are proprietățile corecte:

      TEST.areEqual (gal.el.id, "gal-1", "Gallery.el ar trebui să fie cea pe care am specificat-o"); TEST.areEqual (gal.idx, 0, "Indicele galeriei ar trebui să înceapă de la zero");

      Primul nostru test verifică faptul că constructorul galeriei a găsit elementul potrivit. Cel de-al doilea test verifică faptul că indexul începe la 0. Probabil ați putea scrie o grămadă de teste pentru a verifica dacă avem proprietățile potrivite, dar vom scrie teste pentru metodele care vor folosi aceste proprietăți, astfel încât acestea nu vor fi într-adevăr fi necesar.


      Construirea prototipului

      Acum, să ne întoarcem la asta galleryPrototype obiect care este în prezent gol. Aici vom găzdui toate metodele noastre Galerie "Instanțe". Să începem cu a stabilit : aceasta este metoda cea mai imporantă, deoarece este cea care schimbă de fapt imaginea afișată. Este nevoie de indicele imaginii sau de șirul id al imaginii.

      // în cadrul setului "galleryProtytype": funcția (i) if (typeof i === 'string') i = this.ids.indexOf (i);  this.displayImage.setAttribute ("src", "images /" + this.ids [i] + ".jpg"); retur (acest.idx = i); 

      Dacă metoda primește șirul de identificare, acesta va găsi numărul corect de index pentru acel cod. Apoi, am setat displayImage„s src la calea corectă a imaginii și returnați noul index curent în timp ce acesta este setat ca index curent.

      Acum, să testați această metodă (din nou în galerie-test.js):

      TEST.areEqual (gal.set (4), 4, "Galerie.set (cu număr) ar trebui să returneze același număr trecut"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images_24 / javascript-testing-from-scratch.jpg", "Gallery.set (cu număr) ar trebui să modifice imaginea afișată"); TEST.areEqual (gal.set ("post"), 3, "Galerie.set (cu șir) trebuie mutat la imaginea corespunzătoare"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images / post.jpg", "Gallery.set (cu șir) ar trebui să modifice imaginile afișate");

      Testăm metoda noastră de testare atât cu un număr cât și cu un parametru șir pentru a stabilit. În acest caz, putem verifica src pentru imagine și asigurați-vă că interfața de utilizare este ajustată corespunzător; nu este întotdeauna posibil sau necesar să vă asigurați că ceea ce utilizatorul văd răspunde în mod corespunzător (fără a folosi ceva de genul acesta); acesta este locul în care este util tipul de testare prin clic. Cu toate acestea, putem face acest lucru aici, așa vom face.

      Aceste teste ar trebui să treacă. De asemenea, trebuie să puteți face clic pe miniaturi și să aveți afișate versiunile mai mari. Arati bine!

      Deci, hai să trecem la câteva metode care se mișcă între imagini. Acestea ar putea fi utile dacă doriți să aveți butoanele "următor" și "anterioare" pentru a trece prin imagini (nu vom avea aceste butoane, dar vom pune în aplicare metodele).

      În cea mai mare parte, trecerea la imaginile următoare și anterioare nu este un lucru dificil. Piesele complicate merg la următoarea imagine când te afli la ultima sau la cea anterioară când ești la prima.

      // în interiorul 'galleryPrototype' următor: function () if (this.idx === this.imgs.length - 1) return this.set (0);  return this.set (acest.idx + 1); , prev: funcția () if (this.idx === 0) returnați acest set (this.imgs.length - 1);  return this.set (acest.idx - 1); , curr: function () return this.idx; ,

      Bine, deci nu este prea dificil. Ambele metode sunt metode de "zahăr" pentru utilizare a stabilit. Dacă suntem la ultima imagine (this.idx === this.imgs.length -1), noi set (0). Dacă suntem la prima (this.idx === 0), noi set (this.imgs.length -1). În caz contrar, trebuie doar să adăugați sau să scăpați unul din indexul curent. Nu uitați că ne întoarcem exact ceea ce este returnat de la a stabilit apel.

      De asemenea, avem și curr de asemenea. Nu este deloc complicat: doar returnează indicele curent. O vom testa un pic mai târziu.

      Deci, să testeze aceste metode.

      TEST.areEqual (gal.next (), 4, "Galerie ar trebui să avanseze pe .next ()"); TEST.areEqual (gal.prev (), 3, "Galerie ar trebui să revină la .prev ()");

      Acestea vin după testele noastre anterioare, astfel că 4 și 3 sunt valorile pe care ne-am aștepta. Și ei trec!

      Există doar o singură bucată: aceasta este fotografierea automată a fotografiilor. Vrem să fim capabili să sunăm gal.start () jucând prin imagini. Desigur, vor fi a gal.stop () metodă.

      // în interiorul galerieiPrototype 'începe: funcția (timpul) var thiz = this; timp = timp || 3000; this.interval = setInterval (funcția () thiz.next ();, timpul); this.going = adevărat; return true; , stop: funcția () clearInterval (this.interval); this.going = false; return true; ,

      Al nostru start metoda va avea parametru: numărul de milisecunde pe care o imagine este afișată; dacă nu este dat niciun parametru, suntem implicit la 3000 (3 secunde). Apoi, pur și simplu folosim setInterval pe o funcție care va apela Următor → la momentul potrivit. Desigur, nu putem uita să ne stabilim this.going la Adevărat. În cele din urmă, ne întoarcem Adevărat.

      Stop nu este prea dificil. Deoarece am salvat intervalul ca this.interval, putem folosi clearInterval pentru a termina. Apoi, am stabilit this.going să fals și să se întoarcă adevărat.

      Vom avea două funcții la îndemână pentru a ne da seama dacă galeria este în buclă:

      isGoing: funcția () return this.going; , isStopped: funcția () return! this.going; 

      Acum, să testăm această funcție.

      gal.set (0); TEST.areEqual (gal.start (), true, "Galerie ar trebui să fie looping"); TEST.areEqual (gal.curr (), 0, "Indicele curent al imaginii trebuie să fie 0"); setTimeout (funcția () TEST.areEqual (gal.curr (), 1, "Indicele curent al imaginii trebuie să fie 1"), TEST.areEqual (gal.isGoing (), true, "Galerie ar trebui să meargă"); sunt setate (funcția () TEST.areEqual (gal.cur (), 1, "Imaginea curentă ar trebui să fie încă 1"); TEST.areEqual ( gal.isStopped (), true, "Galerie ar trebui să fie oprită"); 3050); 3050);

      Este un pic mai complicat decât seturile anterioare de teste: începem să folosim gal.set (0) pentru a ne asigura că începem de la început. Apoi, sunăm gal.start () pentru a începe buclele. Apoi, vom testa asta gal.curr () returnează 0, adică vedem încă prima imagine. Acum vom folosi a setTimeout să așteptați 3050ms (puțin mai mult de 3 secunde) înainte de a continua testele. Înăuntru setTimeout, vom face altul gal.curr (); indexul ar trebui să fie acum 1. Atunci vom testa acest lucru gal.isGoing () este adevarat. În continuare, vom opri galeria gal.stop (). Acum folosim un altul setTimeout să aștepte încă aproape 3 secunde; dacă galeria sa oprit într-adevăr, imaginea nu va fi înclinată, deci gal.curr () ar trebui să fie încă 1; asta este ceea ce testează în intervalul de timp. În cele din urmă, ne asigurăm isStopped metoda funcționează.

      Dacă au trecut aceste teste, felicitări! Ne-am terminat Galerie și testele sale.


      Concluzie

      Dacă nu ați încercat înainte de testare, sper că ați văzut cât de simplu de testare poate fi în JavaScript. Așa cum am menționat la începutul acestui tutorial, testarea bună va necesita probabil să vă scrieți JavaScript puțin diferit decât v-ați obișnuit. Cu toate acestea, am constatat că JavaScript ușor de testat este, de asemenea, ușor de întreținut JavaScript.

      Te las cu mai multe link-uri pe care le poți găsi utile în timp ce iesi și scriu bune teste JavaScript și bune.

      • Qunit - o suită de testare unitate (Tutorial pe QUnit)
      • Jasmine - cadru BDD (Tutorial pe Jasmine)
      • Testarea JavaScript cu Assert
      • Test test JavaScript (carte) (exemple de capitol)
      Cod