Prova il tuo JavaScript con Jasmine

Sappiamo tutti che dovremmo testare il nostro codice, ma in realtà non lo facciamo. Immagino sia giusto dire che la maggior parte di noi lo rimprovera perché, nove volte su dieci, significa imparare un altro concetto. In questo tutorial, ti presenterò un ottimo framework per testare il tuo codice JavaScript con facilità.

A proposito, sapevi che puoi correggere i tuoi errori JavaScript velocemente e facilmente da un esperto di Envato Studio?

ThemeManiac, ad esempio, risolverà errori JavaScript o problemi di compatibilità del browser sul proprio sito Web o applicazione Web. Le correzioni possono essere completate molto velocemente, in base alla complessità e alle informazioni disponibili. Può anche riorganizzare i tuoi script e creare un'esperienza utente completamente nuova. Ha completato più di 1.000 posti di lavoro su Envato Studio, con il 99% dei clienti che lo consigliano.


Passaggio 0: Comprensione del BDD

Oggi impareremo a conoscere il framework di test BDS per Jasmine. Ma ci fermiamo qui per una prima deviazione, per parlare molto brevemente di BDD e TDD. Se non hai familiarità con questi acronimi, rappresentano Sviluppo guidato dal comportamento e Sviluppo guidato dai test. Sono nel bel mezzo dell'apprendimento su ciò che ciascuno di questi è in pratica e su come sono diversi, ma ecco alcune delle differenze fondamentali:

BDD e TDD? stare per Sviluppo guidato dal comportamento e Sviluppo guidato dai test.

TDD nella sua forma più semplice è proprio questo:

  1. Scrivi i tuoi test
  2. Guardali fallire
  3. Falli passare
  4. Refactor
  5. Ripetere

È abbastanza facile da capire, eh?

Il BDD è un po 'più complesso: come ho capito ora, non penso che tu o io come singolo sviluppatore possiamo effettivamente esercitarlo pienamente; è più una questione di squadra. Ecco alcune delle pratiche di BDD:

  • Stabilire gli obiettivi delle diverse parti interessate necessarie per una visione da attuare
  • Coinvolgere le parti interessate nel processo di implementazione attraverso lo sviluppo di software esterni
  • Utilizzo di esempi per descrivere il comportamento dell'applicazione o di unità di codice
  • Automatizzare questi esempi per fornire feedback rapidi e test di regressione

Per saperne di più, puoi leggere l'ampio articolo di Wikipedia (da cui sono stati presi quei punti).

Tutto questo per dire che, mentre Jasmine si autofinanzia come un framework BDD, lo useremo in un modo più TDD. Ciò non significa che lo stiamo usando male, però. Una volta che abbiamo finito, sarai in grado di testare il tuo JavaScript con facilità? e mi aspetto che tu lo faccia!


Passaggio 1: apprendimento della sintassi

Jasmine prende molti spunti da Rspec.

Se hai familiarità con Rspec, il di fatto Quadro BDD, vedrai che Jasmine prende molti spunti da Rspec. I test al gelsomino sono principalmente due parti: descrivere blocchi e esso blocchi. Vediamo come funziona.

Vedremo alcuni test più da vicino in pochi, ma per ora, lo terremo semplice:

descrivere ('JavaScript addition operator', function () it ('aggiunge due numeri insieme', function () expect (1 + 2) .toEqual (3);););

Sia il descrivere e esso le funzioni prendono due parametri: una stringa di testo e una funzione. La maggior parte dei framework di test cerca di leggere il più possibile l'inglese, e puoi vederlo con Jasmine. Innanzitutto, nota che la stringa è passata a descrivere e la stringa passata a esso forma una frase (di sorta):? L'operatore di aggiunta di JavaScript aggiunge due numeri insieme? Quindi, continuiamo a mostrare come.

Dentro quello esso blocco, è possibile scrivere tutto il codice di installazione necessario per il test. Non ne abbiamo bisogno per questo semplice esempio. Una volta che sei pronto per scrivere il codice di prova effettivo, inizierai con aspettarsi funzione, passandola qualunque cosa tu stia testando. Notate come questo forma anche una frase: ci aspettiamo che 1 + 2 sia uguale a 3.?

Ma sto andando oltre. Come ho detto, qualunque valore tu passi aspettarsi sarà testato Il metodo che chiami, fuori dal valore restituito aspettarsi, sarà determinato da quale test verrà eseguito. Questo gruppo di metodi è chiamato 'matcher', e ne esamineremo alcuni oggi. In questo caso, stiamo usando il toEqual matcher, che controlla per vedere se il valore è passato a aspettarsi e il valore è passato a toEqual sono lo stesso valore.

Penso che tu sia pronto per portarlo al livello successivo, quindi impostiamo un semplice progetto usando Jasmine.


Passaggio 2: impostazione di un progetto

Il gelsomino può essere usato da solo; oppure puoi integrarlo con un progetto Rails. Faremo il primo. Mentre Jasmine può funzionare fuori dal browser (pensa Nodo, tra gli altri posti), possiamo ottenere un piccolo template davvero bello con il download.

Quindi, vai alla pagina di download indipendente e ottieni l'ultima versione. Dovresti ottenere qualcosa del genere:

Troverai i file del framework Jasmine nel file lib cartella. Se preferisci strutturare i tuoi progetti in modo diverso, ti preghiamo di farlo; ma lo terremo per ora.

C'è in realtà qualche codice di esempio cablato in questo modello di progetto. L'attuale? JavaScript (il codice che vogliamo testare) può essere trovato nel src sottodirectory; metteremo i nostri a breve. Il codice di prova: il Specifiche-andare nel spec cartella. Non preoccuparti per il SpecHelper.js file appena ancora; torneremo a quello.

Quello SpecRunner.html il file è ciò che esegue i test in un browser. Aprilo (e controlla la casella di controllo "passata" nell'angolo in alto a destra) e dovresti vedere qualcosa di simile a questo:

Questo ci mostra che tutti i test per il progetto di esempio stanno passando. Una volta completato questo tutorial, ti consiglio di aprire il spec / PlayerSpec.js file e sfoglia quel codice. Ma in questo momento, proviamo questo test di scrittura.

  • Creare convert.js nel src cartella.
  • Creare convertSpec.js nel spec cartella,
  • Copia il SpecRunner.html file e rinominarlo SpecRunner.original.html.
  • Rimuovere i collegamenti ai file di progetto di esempio in SpecRunner.html e aggiungi queste righe:

Ora siamo pronti per creare una mini-libreria che convertirà tra unità di misura. Inizieremo scrivendo i test per la nostra mini-biblioteca.


Passaggio 3: scrivere i test

Quindi, scriviamo i nostri test, dobbiamo farlo?

define ("Converti libreria", function () describe ("convertitore di distanza", function () ); describe ("volume converter", function () ););

Iniziamo da questo; stiamo testando il nostro Convertire biblioteca. Noterai che stiamo nidificando descrivere dichiarazioni qui. Questo è perfettamente legale. In realtà è un ottimo modo per testare frammenti di funzionalità separati della stessa base di codice. Invece di due separati descrivere chiede Convertire conversioni a distanza e conversioni di volume della biblioteca, possiamo avere una suite di test più descrittiva come questa.

Ora, sui test reali. Ripeterò l'interno descrivere chiama qui per la vostra convenienza.

descrivere ("convertitore di distanza", function () it ("converte pollici in centimetri", function () expect (Convert (12, "in"). a ("cm")). toEqual (30.48);) ; ("converte i centimetri in metri", function () expect (Convert (2000, "cm"). a ("yards")). a Equal (21,87);););

Ecco i nostri test per le conversioni a distanza. È importante notare qualcosa qui: non abbiamo scritto un granello di codice per il nostro Convertire libreria ancora, quindi in questi test stiamo facendo molto più che controllare per vedere se funziona: in realtà stiamo decidendo come verrà usato (e quindi implementato). Ecco come abbiamo deciso di fare le nostre conversioni:

Convertire(, ).a();

Sì, sto prendendo spunto dal modo in cui Jasmine ha implementato i suoi test, ma penso che sia un bel formato. Quindi, in questi due test, ho effettuato personalmente le conversioni (ok, con una calcolatrice) per vedere quali dovrebbero essere i risultati delle nostre chiamate. Stiamo usando il toEqual matcher per vedere se i nostri test passano.

Ecco i test del volume:

descrivere ("volume converter", function () it ("converte litri in galloni", function () expect (Convert (3, "litri"). a ("gallons")). toEqual (0,79);) ; ("converte galloni in tazze", function () expect (Converti (2, "gallons"). in ("cups")). in Equal (32);););

E aggiungerò altri due test nel nostro livello superiore descrivere chiamata:

("genera un errore quando passa una sconosciuta dall'unità", function () var testFn = function () Convert (1, "dollar"). a ("yens"); expect (testFn) .toThrow ( nuovo errore ("non riconosciuto dall'unità"));); ("getta un errore quando viene passato a un'unità sconosciuta", function () var testFn = function () Convert (1, "cm"). a ("furlongs"); expect (testFn) .toThrow ( nuovo errore ("non riconosciuto to-unit")););

Questi controllano gli errori che dovrebbero essere lanciati quando unità sconosciute vengono passate nel Convertire funzione o il a metodo. Noterai che sto completando la conversione effettiva in una funzione e passando a quella aspettarsi funzione. Questo perché non possiamo chiamare la funzione come aspettarsi parametro; abbiamo bisogno di consegnare una funzione e lasciare che chiami la funzione stessa. Dal momento che abbiamo bisogno di passare un parametro a quello a funzione, possiamo farlo in questo modo.

L'altra cosa da notare è che sto introducendo un nuovo matcher: gettare, che accetta un oggetto errore. Vedremo presto altri corrieri.

Ora, se apri SpecRunner.html in un browser, otterrai questo:

Grande! I nostri test stanno fallendo. Ora, apriamo il nostro convert.js file e fare un po 'di lavoro:

funzione Converti (numero, daUnità) var conversioni = distanza: metri: 1, cm: 0.01, piedi: 0.3048, pollici: 0.0254, iarde: 0.9144, volume: litri: 1, galloni: 3.785411784, tazze: 0.236588236 , betweenUnit = false, type, unit; for (digita conversioni) if (conversions (type)) if ((unit = conversions [type] [fromUnit])) betweenUnit = number * unit * 1000;  return to: function (toUnit) if (betweenUnit) for (type in conversions) if (conversions.hasOwnProperty (type)) if ((unit = conversions [type] [toUnit])) return fix (betweenUnit / (unit * 1000));  lancia un nuovo errore ("non riconosciuto all'unità");  else throw new Error ("non riconosciuto dall'unità");  function fix (num) return parseFloat (num.toFixed (2)); ; 

Non discuteremo di questo, perché stiamo imparando Jasmine qui. Ma qui ci sono i punti principali:

  • Stiamo facendo le conversioni memorizzando la conversione in un oggetto; i numeri di conversione sono classificati per tipo (distanza, volume, aggiungi il tuo). Per ogni campo di misura, abbiamo un valore base (metri o litri, qui) a cui tutto si converte. Quindi quando vedi iarde: 0,9144, sai che è quanti metri ci sono in un metro. Quindi, per convertire i cantieri, ad esempio, in centimetri, ci moltiplichiamo cantieri dal primo parametro (per ottenere il numero di metri) e quindi dividere il prodotto per centimetro, il numero di metri in un centimetro. In questo modo, non dobbiamo memorizzare i tassi di conversione per ogni coppia di valori. Ciò facilita anche l'aggiunta di nuovi valori in seguito.
  • Nel nostro caso, ci aspettiamo che le unità passate siano le stesse che usiamo nella tabella di conversione. Se questa fosse una vera libreria, vorremmo supportare più formati, come "in", "pollici" e "pollici", e quindi vorremmo aggiungere qualche logica per abbinare il fromUnit alla chiave giusta.
  • Alla fine di Convertire funzione, memorizziamo il valore intermedio in betweenUnit, che è inizializzato a falso. In questo modo, se non abbiamo il fromUnit, betweenUnit sarà falso entrare nel a metodo, quindi viene generato un errore.
  • Se non abbiamo il toUnit, verrà generato un errore diverso. Altrimenti, divideremo come necessario e restituire il valore convertito.

Ora, torna a SpecRunner.html e ricaricare la pagina. Ora dovresti vedere questo (dopo aver controllato? Show passed?):

Ecco qua! I nostri test stanno passando. Se stessimo sviluppando un vero progetto qui, scriveremmo test per una certa porzione di funzionalità, li faremo passare, scrivere test per un altro controllo, renderli pass, ecc. Ma poiché questo era un semplice esempio, lo abbiamo appena fatto tutto in un colpo solo.

E ora che hai visto questo semplice esempio di utilizzo di Jasmine, diamo un'occhiata ad alcune altre funzionalità che ti offre.


Step 4: Apprendimento dei Matchers

Finora, abbiamo usato due giocatori: toEqual e gettare. Ci sono, naturalmente, molti altri. Ecco alcuni che probabilmente troverai utili; puoi vedere l'intera lista sul wiki.

toBeDefined / toBeUndefined

Se vuoi solo assicurarti che una variabile o una proprietà sia definita, c'è un corrispondente per quello. C'è anche uno per confermare che una variabile o proprietà è non definito.

("è definito", function () var name = "Andrew"; expect (name) .toBeDefined ();) it ("non è definito", function () var name; expect (name) .toBeUndefined (););

toBeTruthy / toBeFalsy

Se qualcosa dovrebbe essere vero o falso, questi matchers lo faranno.

("è vero", function () expect (Lib.isAWeekDay ()). toBeTruthy ();); ("è falso", function () expect (Lib.finishedQuiz) .toBeFalsy (););

toBeLessThan / toBeGreaterThan

Per tutti voi persone numero. Sai come funzionano questi:

("è minore di 10", function () expect (5) .toBeLessThan (10);); ("è maggiore di 10", function () expect (20) .toBeGreaterThan (10););

toMatch

Hai un testo di output che dovrebbe corrispondere a un'espressione regolare? Il toMatch il matcher è pronto e disponibile.

("restituisce il testo corretto", function () expect (cart.total ()). toMatch (/ \ $ \ d *. \ d \ d /););

contenere

Questo è abbastanza utile. Controlla se un array o una stringa contiene un elemento o una sottostringa.

("dovrebbe contenere arance", function () expect (["apples", "arance", "pears"]). toContain ("arance"););

Ci sono anche altri giocatori che puoi trovare nella wiki. Ma cosa succede se vuoi un matcher che non esiste? In realtà, dovresti essere in grado di fare qualsiasi cosa con qualche codice di impostazione e gli abbinamenti di Jasmine, ma a volte è meglio astrarre parte di quella logica per avere un test più leggibile. Serendipitosamente (beh, in realtà no), Jasmine ci consente di creare i nostri matchers. Ma per fare questo, prima dovremo imparare qualcos'altro.

Passaggio 5: copertura prima e dopo

Spesso, quando si verifica una base di codice, si desidera eseguire alcune righe di codice di impostazione per ogni test di una serie. Sarebbe doloroso e prolisso copiarlo per tutti esso chiama, quindi Jasmine ha una piccola funzionalità utile che ci consente di designare il codice da eseguire prima o dopo ogni test. Vediamo come funziona:

define ("MyObject", function () var obj = new MyObject (); beforeEach (function () obj.setState ("clean");); it ("cambia stato", function () obj.setState ("dirty"); expect (obj.getState ()). toEqual ("dirty");) it ("aggiunge stati", function () obj.addState ("packaged"); expect (obj.getState ( )). toEqual (["clean", "packaged"]);));

In questo esempio forzato, puoi vedere come, prima dell'esecuzione di ogni test, lo stato di obj è impostato su? clean ?. Se non lo facessimo, la modifica apportata a un oggetto in un test precedente verrà mantenuta per il test successivo per impostazione predefinita. Naturalmente, potremmo anche fare qualcosa di simile con il Dopo ogni funzione:

define ("MyObject", function () var obj = new MyObject ("clean"); // imposta lo stato iniziale afterEach (function () obj.setState ("clean");); it ("cambia stato") , function () obj.setState ("dirty"); expect (obj.getState ()). toEqual ("dirty");) it ("aggiunge stati", function () obj.addState ("impacchettato") ); expect (obj.getState ()). toEqual (["clean", "packaged"]);));

Qui, stiamo impostando l'oggetto per cominciare, e quindi averlo corretto dopo ogni test. Se vuoi il MyObject funziona in modo che tu possa provare questo codice, puoi farlo qui in un GistHub.

Passaggio 6: scrittura di Matchers personalizzati

Come abbiamo detto prima, gli abbinamenti con i clienti sarebbero probabilmente utili a volte. Quindi scriviamone uno. Possiamo aggiungere un matcher in uno a beforeeach chiama o un esso chiama (beh, immagino che tu poteva fallo in un Dopo ogni chiama, ma non avrebbe molto senso). Ecco come si avvia:

beforeEach (function () this.addMatchers (););

Piuttosto semplice, eh? Noi chiamiamo this.addMatchers, passandogli un parametro oggetto. Ogni chiave in questo oggetto diventerà il nome di un corrispondente e la funzione associata (il valore) sarà come viene eseguita. Diciamo che vogliamo creare un matcher che controlli per vedere se un numero è tra altri due. Ecco cosa scriveresti:

beforeEach (function () this.addMatchers (toBeBetween: function (rangeFloor, rangeCeiling) if (rangeFloor> rangeCeiling) var temp = rangeFloor; rangeFloor = rangeCeiling; rangeCeiling = temp; return this.actual> rangeFloor && this. effettivo < rangeCeiling;  ); );

Prendiamo semplicemente due parametri, assicurandoci che il primo sia più piccolo del secondo, e restituiamo un'istruzione booleana che si considera vera se le nostre condizioni sono soddisfatte. La cosa importante da notare qui è come otteniamo il valore che è stato passato al aspettarsi funzione: this.actual.

("è tra 5 e 30", function () expect (10) .toBeBetween (5, 30);); ("è tra 30 e 500", function () expect (100) .toBeBetween (500, 30););

Questo è ciò che SpecHelper.js il file fa; ha un beforeeach chiamata che aggiunge il matcher tobePlaying (). Controlla!


Conclusione: divertirti!

C'è molto di più che puoi fare con Jasmine: abbinamenti di funzioni, spie, specifiche asincrone e altro. Ti consiglio di esplorare il wiki se sei interessato. Ci sono anche alcune librerie di accompagnamento che semplificano i test nel DOM: Jasmine-jQuery e Jasmine-fixture (che dipende da Jasmine-jQuery).

Quindi, se non stai testando il tuo JavaScript finora, ora è un ottimo momento per iniziare. Come abbiamo visto, la sintassi veloce e semplice di Jasmine rende il test piuttosto semplice. Non c'è ragione per non farlo, ora, c'è?

Se vuoi migliorare ulteriormente lo sviluppo di JavaScript, perché non controllare la gamma di elementi JavaScript su Envato Market? Ci sono migliaia di script, app e snippet di codice per aiutarti.