Tester votre JavaScript avec Jasmine

Nous savons tous que nous devrions tester notre code, mais nous ne le faisons pas réellement. Je suppose qu’il est juste de dire que la plupart d’entre nous l’a remis parce que, neuf fois sur dix, cela signifie qu’il faut apprendre un autre concept. Dans ce tutoriel, je vais vous présenter un excellent petit framework pour tester facilement votre code JavaScript..

Au fait, saviez-vous que vos erreurs JavaScript peuvent être corrigées rapidement et facilement par un expert d’Envato Studio?

ThemeManiac, par exemple, corrige les erreurs Java ou les problèmes de compatibilité du navigateur sur votre site Web ou votre application Web. Les corrections peuvent être effectuées très rapidement, en fonction de la complexité et des informations disponibles. Il peut également réorganiser vos scripts et créer une expérience utilisateur totalement nouvelle. Il a effectué plus de 1 000 travaux sur Envato Studio et 99% de ses clients l'ont recommandé..


Étape 0: Comprendre BDD

Aujourd'hui, nous allons en apprendre davantage sur le cadre de test Jasmine BDD. Mais nous nous arrêtons ici pour faire un détour d’abord, pour parler très brièvement, de BDD et de TDD. Si vous ne connaissez pas ces acronymes, ils signifient Développement axé sur le comportement et Développement piloté par les tests. Je suis en train de comprendre en quoi consiste chacune de ces pratiques et en quoi elles sont différentes, mais voici quelques-unes des différences fondamentales:

BDD et TDD? représenter Développement axé sur le comportement et Développement piloté par les tests.

TDD dans sa forme la plus simple est juste ceci:

  1. Ecrivez vos tests
  2. Les regarder échouer
  3. Les faire passer
  4. Refactor
  5. Répéter

C'est assez facile à comprendre, hein?

BDD est un peu plus complexe: si je comprends bien, je ne pense pas que vous ou moi, en tant que développeur unique, puissions le pratiquer pleinement. c'est plus une affaire d'équipe. Voici quelques-unes des pratiques de BDD:

  • Établir les objectifs des différentes parties prenantes nécessaires à la mise en œuvre d'une vision
  • Impliquer les parties prenantes dans le processus de mise en œuvre via le développement logiciel externe
  • Utilisation d'exemples pour décrire le comportement de l'application ou d'unités de code
  • Automatiser ces exemples pour fournir des commentaires rapides et des tests de régression

Pour en savoir plus, vous pouvez lire le vaste article Wikipedia (à partir duquel ces points ont été tirés).

Tout cela pour dire que, bien que Jasmine se présente comme un framework BDD, nous allons l’utiliser de manière plus TDD. Cela ne signifie pas que nous l'utilisons mal, cependant. Une fois que nous aurons terminé, vous pourrez tester votre JavaScript facilement? et j'espère que vous le ferez!


Étape 1: apprendre la syntaxe

Jasmine prend beaucoup de repères de Rspec.

Si vous connaissez Rspec, le de facto Avec le framework BDD, vous verrez que Jasmine prend beaucoup de signaux de Rspec. Les tests au jasmin sont principalement composés de deux parties: décrire des blocs et il des blocs. Voyons comment cela fonctionne.

Nous examinerons quelques tests plus proches de la vie réelle, mais pour le moment, nous allons rester simples:

describe ('opérateur d'addition JavaScript', function () it ('ajoute deux nombres ensemble', function () expect (1 + 2) .toEqual (3);););

Les deux décrire et il les fonctions prennent deux paramètres: une chaîne de texte et une fonction. La plupart des frameworks de test essaient de lire autant que possible l'anglais, et vous pouvez le voir avec Jasmine. Tout d'abord, notez que la chaîne passée à décrire et la chaîne passée à il forme une phrase (de sorte):? L'opérateur d'addition JavaScript ajoute deux nombres ensemble.? Ensuite, nous montrons comment.

À l'intérieur de cela il bloquer, vous pouvez écrire tout le code de configuration dont vous avez besoin pour votre test. Nous n'en avons pas besoin pour cet exemple simple. Une fois que vous êtes prêt à écrire le code de test, vous commencerez par le attendre fonction, en passant ce que vous testez. Remarquez comment cela forme aussi une phrase: on s'attend à ce que 1 + 2 soit égal à 3.?

Mais je devance nous-mêmes. Comme je l'ai dit, quelle que soit la valeur que vous transmettez attendre sera testé. La méthode que vous appelez, en dehors de la valeur renvoyée par attendre, sera déterminé par quel test est exécuté. Ce groupe de méthodes s'appelle les «correspondants» et nous en examinerons plusieurs aujourd'hui. Dans ce cas, nous utilisons le égaler matcher, qui vérifie que la valeur passée à attendre et la valeur passée à égaler sont la même valeur.

Je pense que vous êtes prêt à passer au niveau supérieur, alors mettons en place un projet simple utilisant Jasmine.


Étape 2: Configuration d'un projet

Le jasmin peut être utilisé seul; ou vous pouvez l'intégrer à un projet Rails. Nous ferons le premier. Bien que Jasmine puisse fonctionner en dehors du navigateur (pensez à Node, entre autres), nous pouvons obtenir un très joli petit modèle avec le téléchargement..

Alors, rendez-vous sur la page de téléchargement autonome et obtenez la dernière version. Vous devriez obtenir quelque chose comme ça:

Vous trouverez les fichiers de framework Jasmine dans le répertoire lib dossier. Si vous préférez structurer vos projets différemment, veuillez le faire; mais nous allons garder cela pour l'instant.

En fait, un exemple de code est câblé dans ce modèle de projet. L'actuel? JavaScript (le code que nous voulons tester) peut être trouvé dans le src sous-répertoire; nous y mettrons le nôtre sous peu. Le code de test-le spécifications-aller dans le spec dossier. Ne t'inquiète pas pour le SpecHelper.js déposer pour l'instant; on y reviendra.

Cette SpecRunner.html fichier est ce qui exécute les tests dans un navigateur. Ouvrez-le (et cochez la case "passé" dans le coin supérieur droit), et vous devriez voir quelque chose comme ça:

Cela nous montre que tous les tests du projet exemple sont réussis. Une fois ce didacticiel terminé, je vous recommande d’ouvrir la spec / PlayerSpec.js déposer et parcourir ce code. Mais pour le moment, essayons cet essai d'écriture test.

  • Créer convert.js dans le src dossier.
  • Créer convertSpec.js dans le spec dossier,
  • Copier le SpecRunner.html déposer et renommer SpecRunner.original.html.
  • Supprimez les liens vers les exemples de fichiers de projet dans SpecRunner.html et ajoutez ces lignes:

Nous sommes maintenant prêts à créer une mini-bibliothèque qui convertira les unités de mesure. Nous allons commencer par écrire les tests pour notre mini-bibliothèque.


Étape 3: Écriture des tests

Alors, écrivons nos tests, allons-nous?

describe ("Convertir une bibliothèque", function () decrire ("convertisseur de distance", function () ); decrire ("convertisseur de volume", function () ););

Nous commençons par ceci; nous testons notre Convertir bibliothèque. Vous remarquerez que nous imbriquons décrire déclarations ici. Ceci est parfaitement légal. C'est en fait un excellent moyen de tester différentes fonctionnalités du même code. Au lieu de deux séparés décrire appels pour Convertir Les conversions de distance et de volume de la bibliothèque, nous pouvons avoir une suite de tests plus descriptive comme celle-ci.

Maintenant, sur les tests réels. Je vais répéter l'intérieur décrire appelle ici pour votre commodité.

describe ("convertisseur de distance", function () it ("convertit les pouces en centimètres", function () expect (Convert (12, "in"). en ("cm")). toEqual (30.48);) ; it ("convertit des centimètres en verges", function () expect (Convert (2000, "cm"). en ("verges")). toEqual (21.87);));

Voici nos tests pour les conversions à distance. Il est important de noter quelque chose ici: nous n’avons pas écrit un grain de code pour notre Convertir bibliothèque pour le moment, alors dans ces tests, nous faisons plus que vérifier pour voir si cela fonctionne: nous décidons en fait de la manière dont elle sera utilisée (et donc implémentée). Voici comment nous avons décidé d'effectuer nos conversions:

Convertir(, ).à()

Oui, je me fie à la façon dont Jasmine a mis en œuvre ses tests, mais je pense que c'est un format agréable. Ainsi, dans ces deux tests, j’ai moi-même effectué les conversions (avec une calculatrice) pour voir quels devraient être les résultats de nos appels. Nous utilisons le égaler matcher pour voir si nos tests réussissent.

Voici les tests de volume:

describe ("convertisseur de volume", function () it ("convertit les litres en gallons", function () expect (Converti (3, "litres").. en ("gallons")). toEqual (0.79);) ; it ("convertit des gallons en tasses", function () expect (Converti (2, "gallons"). en ("tasses")). toEqual (32);));

Et je vais ajouter deux autres tests dans notre plus haut niveau décrire appel:

it ("génère une erreur lorsqu’on transmet une unité from inconnue", function () var testFn = function () Convertir (1, "dollar"). en ("yens"); expect (testFn) .toThrow ( new Error ("non reconnu par l'unité"));); it ("génère une erreur lorsqu’on transmet une unité inconnue", function () var testFn = function () Convertir (1, "cm"). en ("furlongs"); expect (testFn) .toThrow ( nouvelle erreur ("unité non reconnue")););

Ils vérifient les erreurs qui devraient être émises lorsque des unités inconnues sont passées dans le Convertir fonction ou la à méthode. Vous remarquerez que je suis en train d'envelopper la conversion réelle dans une fonction et de le transmettre à la attendre une fonction. C'est parce que nous ne pouvons pas appeler la fonction en tant que attendre paramètre; nous devons lui donner une fonction et le laisser appeler la fonction elle-même. Puisque nous avons besoin de passer un paramètre à celui à fonction, nous pouvons le faire de cette façon.

L'autre chose à noter est que je présente un nouveau matcher: lancer, qui prend un objet d'erreur. Nous allons bientôt regarder d'autres joueurs.

Maintenant, si vous ouvrez SpecRunner.html dans un navigateur, vous obtiendrez ceci:

Génial! Nos tests échouent. Maintenant, ouvrons notre convert.js déposer et faire du travail:

function Convert (nombre, fromUnit) conversions var = distance: mètres: 1, cm: 0,01, pieds: 0,3048, pouces: 0,0254, yards: 0,9144, volume: litres: 1, gallons: 3,785411784, tasses: 0,236588236 , betweenUnit = false, type, unité; for (saisissez les conversions) if (conversions (type)) if ((unité = conversions [type] [fromUnit])) betweenUnit = number * unit * 1000;  return to: function (toUnit) if (betweenUnit) for (saisissez les conversions) if (conversions.hasOwnProperty (type)) if ((unit = conversions [type] [toUnit])) retour fix (betweenUnit / (unit * 1000));  jeter une nouvelle erreur ("unité non reconnue");  else lance new Error ("non reconnu par l'unité");  function fix (num) return parseFloat (num.toFixed (2)); ; 

Nous n'allons pas vraiment en discuter, parce que nous apprenons Jasmine ici. Mais voici les points principaux:

  • Nous effectuons les conversions en stockant la conversion dans un objet; Les numéros de conversion sont classés par type (distance, volume, ajoutez le vôtre). Pour chaque champ de mesure, nous avons une valeur de base (mètres ou litres, ici) à laquelle tout est converti. Alors quand tu vois verges: 0,9144, vous savez que c'est combien de mètres il y a dans un mètre. Ensuite, pour convertir des verges en centimètres, par exemple, nous multiplions cours par le premier paramètre (pour obtenir le nombre de mètres) puis diviser le produit par cm, le nombre de mètres en centimètre. De cette façon, nous n'avons pas à stocker les taux de conversion pour chaque paire de valeurs. Cela facilite également l'ajout ultérieur de nouvelles valeurs.
  • Dans notre cas, nous nous attendons à ce que les unités entrées soient les mêmes que les clés que nous utilisons dans la table de conversion. S'il s'agissait d'une vraie bibliothèque, nous voudrions prendre en charge plusieurs formats, tels que 'in', 'inch' et 'inches' - et, par conséquent, nous voudrions ajouter une logique correspondant à la deUnit à la bonne clé.
  • À la fin de Convertir fonction, nous stockons la valeur intermédiaire dans entre les unités, qui est initialisé à faux. De cette façon, si nous n'avons pas le deUnit, entre les unités sera faux entrer dans le à méthode, et donc une erreur avec être jetée.
  • Si nous n'avons pas le toUnit, une erreur différente sera levée. Sinon, nous diviserons comme nécessaire et renverrons la valeur convertie.

Maintenant, retournez à SpecRunner.html et rechargez la page. Vous devriez maintenant voir ceci (après vérification? Afficher passé?):

Voilà! Nos tests passent. Si nous développions un projet réel ici, nous écrivions des tests pour une certaine fonctionnalité, les passions, des tests pour un autre contrôle, les passions, etc. Mais comme il s’agissait d’un exemple simple, nous venons de le faire. tout d'un coup swoop.

Et maintenant que vous avez vu cet exemple simple d'utilisation de Jasmine, examinons quelques autres fonctionnalités proposées..


Étape 4: apprendre les jumeleurs

Jusqu'à présent, nous avons utilisé deux matcheurs: égaler et lancer. Il y en a bien sûr beaucoup d'autres. En voici quelques-unes que vous trouverez probablement utiles. vous pouvez voir la liste complète sur le wiki.

toBeDefined / toBeUndefined

Si vous voulez juste vous assurer qu'une variable ou une propriété est définie, il y a un indicateur pour cela. Il y a aussi un pour confirmer qu'une variable ou une propriété est indéfini.

it ("est défini", fonction () var nom = "Andrew"; attend (nom) .toBeDefined ();) it ("n'est pas défini", fonction () nom var.; attend (nom) .toBeUndefined (););

toBeTruthy / toBeFalsy

Si quelque chose devait être vrai ou faux, ces joueurs le feront.

it ("est vrai", function () expect (Lib.isAWeekDay ()). toBeTruthy ();); it ("est faux", function () expect (Lib.finishedQuiz) .toBeFalsy (););

toBeLessThan / toBeGreaterThan

Pour tous vous numérotez les gens. Vous savez comment cela fonctionne:

it ("est inférieur à 10", function () expect (5) .toBeLessThan (10);); it ("est supérieur à 10", function () expect (20) .toBeGreaterThan (10););

correspondre

Vous avez du texte en sortie qui devrait correspondre à une expression régulière? le correspondre matcher est prêt et disposé.

it ("affiche le texte correct", function () expect (cart.total ()). toMatch (/ \ $ \ d *. \ d \ d /););

contenir

Celui-ci est très utile. Il vérifie si un tableau ou une chaîne contient un élément ou une sous-chaîne.

it ("devrait contenir des oranges", function () expect (["pommes", "oranges", "poires"])). toContain ("oranges"););

Il y a aussi quelques autres correspondants que vous pouvez trouver dans le wiki. Mais si vous voulez un matcher qui n'existe pas? Vraiment, vous devriez être capable de faire à peu près n'importe quoi avec du code d'installation et les correspondants fournis par Jasmine, mais il est parfois plus agréable de résumer une partie de cette logique pour obtenir un test plus lisible. Heureusement (enfin, pas du tout), Jasmine nous permet de créer nos propres matchers. Mais pour ce faire, nous devons d'abord apprendre un petit quelque chose.

Étape 5: Couvrir avant et après

Souvent, lorsque vous testez une base de code, vous souhaitez exécuter quelques lignes de code de configuration pour chaque test d'une série. Il serait douloureux et prolixe de devoir copier cela pour chaque il Jasmine a donc une petite fonctionnalité pratique qui nous permet de désigner le code à exécuter avant ou après chaque test. Voyons comment cela fonctionne:

describe ("MyObject", function () var obj = new MyObject (); beforeEach (function () obj.setState ("clean");); it ("change d'état", function () obj.setState ("dirty"); expect (obj.getState ()). toEqual ("dirty");) it ("ajoute des états", function () obj.addState ("packaged"); expect (obj.getState ( )). toEqual (["clean", "emballé"]);));

Dans cet exemple artificiel, vous pouvez voir comment, avant chaque test, l’état de obj est réglé pour? nettoyer ?. Si nous ne le faisions pas, la modification apportée à un objet lors d'un test précédent persisterait au test suivant par défaut. Bien sûr, nous pourrions aussi faire quelque chose de similaire avec le Après chaque une fonction:

describe ("MyObject", function () var obj = new MyObject ("clean"); // définit l'état initial afterEach (function () obj.setState ("clean");); it ("change d'état" , function () obj.setState ("dirty"); expect (obj.getState ()). toEqual ("dirty");) il ("ajoute des états", function () obj.addState ("emballé" ); expect (obj.getState ()). toEqual (["clean", "packaged"]);));

Ici, nous configurons l’objet pour commencer, puis nous le corrigeons après chaque test. Si tu veux le MyObject afin que vous puissiez essayer ce code, vous pouvez le trouver ici dans un gist GitHub.

Étape 6: Écrire des correspondants personnalisés

Comme nous l'avons dit précédemment, les correspondants clients seraient probablement utiles par moments. Alors écrivons-en un. Nous pouvons ajouter un matcher soit dans un Avant chaque appeler ou un il appelez (eh bien, je suppose que vous pourrait le faire dans un Après chaque appeler, mais cela n’aurait pas beaucoup de sens). Voici comment vous commencez:

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

Assez simple, hein? Nous appelons this.addMatchers, en lui passant un paramètre d'objet. Chaque clé de cet objet deviendra le nom d'un matcher et la fonction associée (la valeur) indiquera comment elle est exécutée. Supposons que nous voulions créer un correcteur qui vérifie si un nombre est compris entre deux autres. Voici ce que vous écririez:

beforeEach (function () this.addMatchers (toBeBetween: function (rangeFloor, rangeCeiling)) if (rangeFloor> rangeCeiling) var temp = rangeFloor; rangeFloor = rangeCeiling; rangeCeiling = temp; retourne ceci. réel < rangeCeiling;  ); );

Nous prenons simplement deux paramètres, nous nous assurons que le premier est plus petit que le second et renvoyons une instruction boolean qui vaut true si nos conditions sont remplies. La chose importante à noter ici est la façon dont nous obtenons la valeur qui a été transmise à la attendre une fonction: this.actual.

it ("est compris entre 5 et 30", function () expect (10) .toBeBetween (5, 30);); il ("est compris entre 30 et 500", function () s'attendre à (100) .toBeBetween (500, 30););

C'est ce que le SpecHelper.js le fichier fait; il a un avant chaque appel qui ajoute le matcher tobePlaying (). Vérifiez-le!


Conclusion: s'amuser!

Jasmine offre bien d’autres possibilités: adaptateurs, espions, spécifications asynchrones, etc. Je vous recommande d'explorer le wiki si cela vous intéresse. Il existe également quelques bibliothèques d'accompagnement facilitant les tests dans le DOM: Jasmine-jQuery et Jasmine-fixture (qui dépend de Jasmine-jQuery)..

Donc, si vous ne testez pas votre JavaScript jusqu'à présent, le moment est idéal pour commencer. Comme nous l'avons vu, la syntaxe simple et rapide de Jasmine simplifie les tests. Il n'y a aucune raison pour que vous ne le fassiez pas, maintenant, y at-il?

Si vous souhaitez approfondir votre développement JavaScript, pourquoi ne pas consulter la gamme d'articles JavaScript sur Envato Market? Il existe des milliers de scripts, d'applications et d'extraits de code pour vous aider.