Tests JavaScript à partir de zéro

Ce n'est probablement pas le premier tutoriel sur les tests que vous ayez jamais vu. Mais peut-être avez-vous eu des doutes sur les tests et n'avez jamais pris le temps de les lire. Après tout, cela peut sembler un travail supplémentaire sans raison.

Ce tutoriel a pour but de changer votre point de vue. Nous allons commencer au tout début: qu'est-ce qu'un test et pourquoi devriez-vous le faire? Ensuite, nous parlerons brièvement de l’écriture de code testable, avant de faire des tests! Allons-y!


Préférer un screencast?

Partie 1


Partie 2


Partie 3



Définir le test

Tout simplement, essai C’est l’idée d’avoir un ensemble d’exigences qu’un morceau de code donné doit satisfaire pour être suffisamment robuste pour être utilisé dans le monde réel. Souvent, nous écrivons du code JavaScript, puis nous l'ouvrons dans le navigateur et en cliquons un peu pour vérifier que tout fonctionne comme prévu. Bien que cela soit parfois nécessaire, ce n'est pas le type de test dont nous parlons ici. En fait, j'espère que ce didacticiel vous convaincra qu'un auto-test rapide et compliqué devrait compléter une procédure de test plus rigide: l'auto-test suffit, mais une liste complète des exigences est primordiale..


Raisons pour tester

Comme vous pouvez le deviner, le problème des tests JavaScript d'actualisation et de clic est double:

  1. Nous pourrions ne pas nous rappeler de vérifier quelque chose; même si nous le faisons, nous pourrions ne pas vérifier à nouveau, après ajustement du code.
  2. Il se peut que certaines parties du code ne soient pas vraiment testables de cette façon.

En écrivant tests Pour vérifier tout ce que votre code doit faire, vous pouvez vérifier que votre code est au meilleur de sa forme avant de l’utiliser sur un site Web. Au moment où quelque chose tourne réellement dans un navigateur, il y a probablement plusieurs points d'échec. L'écriture de tests vous permet de vous concentrer sur chaque partie testable individuellement; si chaque pièce fait son travail correctement, les choses devraient fonctionner ensemble sans problème (tester des pièces individuelles comme cela s'appelle des tests unitaires).


Écrire du code testable

Si vous êtes un polyglotte (programmeur), vous avez peut-être déjà testé dans d’autres langues. Mais j'ai trouvé en JavaScript une autre bête à vaincre. Après tout, vous ne construisez pas trop d'interfaces utilisateur dans, disons, PHP ou Ruby. Souvent, nous effectuons un travail DOM en JavaScript, et comment testez-vous cela exactement??

Eh bien, le travail DOM n’est pas ce pour quoi vous voulez écrire des tests; c'est la logique. Evidemment, la clé ici est de séparer votre logique et votre code d'interface utilisateur. Ce n'est pas toujours facile; J'ai écrit ma juste part d'interface utilisateur gérée par jQuery, et elle peut être assez compliquée assez rapidement. Cela rend non seulement difficile le test, mais la logique entrelacée et le code d'interface utilisateur peuvent également être difficiles à modifier lorsque le comportement souhaité change. J'ai découvert que l'utilisation de méthodologies telles que les modèles (aussi, modèles) et pub / sub (aussi, pub / sub) facilite l'écriture, le code plus testable..

Encore une chose, avant de commencer à coder: comment écrivons-nous nos tests? Vous pouvez utiliser de nombreuses bibliothèques de tests (et de nombreux bons tutoriels pour vous apprendre à les utiliser; voir les liens à la fin). Cependant, nous allons construire une petite bibliothèque de tests à partir de zéro. Ce ne sera pas aussi sophistiqué que certaines bibliothèques, mais vous verrez exactement ce qui se passe.

Dans cet esprit, au travail!


Construire un mini-framework de test

Nous allons créer une micro-galerie de photos: une simple liste de miniatures, avec une image en taille réelle au-dessus d’elles. Mais d'abord, construisons une fonction de test.

À mesure que vous en apprendrez plus sur les tests et les bibliothèques de tests, vous découvrirez de nombreuses méthodes de test permettant de tester toutes sortes de spécificités. Cependant, tout peut se résumer à savoir si deux choses sont égales ou non: par exemple,

  • La valeur renvoyée par cette fonction est-elle égale à ce que nous nous attendions à obtenir??
  • Est-ce que cette variable du type que nous espérions être?
  • Est-ce que ce tableau a le nombre prévu d'éléments?

Alors, voici notre méthode pour tester l'égalité:

var TEST = areEqual: function (a, b, msg) var result = (a === b); console.log ((résultat? "PASS:": "FAIL:") + msg); retourne le résultat; ;

C'est assez simple: la méthode prend trois paramètres. Les deux premiers sont comparés et, s’ils sont égaux, les tests réussissent. Le troisième paramètre est un message décrivant le test. Dans cette simple bibliothèque de tests, nous exportons simplement nos tests sur la console, mais vous pouvez créer une sortie HTML avec le style CSS approprié si vous le souhaitez..

Ici se trouve le areNotEqual méthode (dans la même TESTER objet):

areNotEqual: fonction (a, b, msg) résultat var = (a! == b); console.log ((résultat? "PASS:": "FAIL:") + msg); retourne le résultat; 

Vous remarquerez les deux dernières lignes de sont égaux et areNotEqual le même. Donc, nous pouvons tirer ceux-ci comme ceci:

var TEST = areEqual: function (a, b, msg) retourne this._output (a === b, msg), areNotEqual: fonction (a, b, msg) retournera this._output (a! == b, msg); , _output: function (result, msg) console [result? "log": "avertir"] ((résultat? "PASS:": "FAIL:") + msg); retourne le résultat; ;

Génial! La chose intéressante ici est que nous pouvons ajouter d'autres méthodes de «sucre» en utilisant les méthodes que nous avons déjà écrites:

TEST.isTypeOf = fonction (obj, type, msg) retour this.areEqual (typede obj, type, msg); ; TEST.isAnInstanceOf = fonction (obj, type, msg) retour this._output (obj instance du type, msg);  TEST.isGreaterThan = function (val, min, msg) renvoie this._output (val> min, msg); 

Vous pouvez expérimenter cela vous-même; après avoir suivi ce tutoriel, vous aurez une bonne idée de son utilisation..


Préparer notre galerie

Alors, créons une galerie photo très simple, en utilisant notre mini TESTER cadre pour créer des tests. Je mentionnerai ici que si le développement piloté par les tests est une excellente pratique, nous ne l’utiliserons pas dans ce didacticiel, principalement parce que ce n’est pas quelque chose que vous pouvez apprendre dans un seul tutoriel; il faut beaucoup de pratique pour vraiment grok. Lorsque vous débutez, il est plus facile d'écrire un peu de code, puis de le tester..

Alors commençons. Bien sûr, nous aurons besoin de HTML pour notre galerie. Nous garderons cette jolie base:

     Test en JavaScript     

Il y a deux choses principales à noter ici: premièrement, nous avons un

cela tient le balisage très simple pour notre galerie d'images. Non, ce n'est probablement pas très robuste, mais cela nous permet de travailler avec quelque chose. Ensuite, notez que nous connectons trois >s: one est notre petite bibliothèque de tests, comme ci-dessus. L'une est la galerie que nous allons créer. La dernière contient les tests pour notre galerie. Notez également les chemins d'accès aux images: les noms de fichiers des miniatures sont accompagnés de «-thumb». Voilà comment nous allons trouver la version plus grande de l'image.

Je sais que vous avez envie de coder, alors jetez ceci dans un galerie.css fichier:

.gallery background: #ececec; débordement caché; largeur: 620px;  .gallery> img margin: 20px 20px 0; rembourrage: 0;  .gallery ul list-style-type: none; marge: 20px; rembourrage: 0; débordement caché;  .gallery li float: left; marge: 0 10 px;  .gallery li: premier-de-type margin-left: 0px;  .gallery li: dernier du type margin-right: 0px; 

Maintenant, vous pouvez charger ceci dans votre navigateur et voir quelque chose comme ceci:

OK, déjà! Écrivons du JavaScript, allons-nous?


Décrire notre galerie

Ouvre ça galerie.js fichier. Voici comment nous commençons:

var Gallery = (fonction () var Gallery = , galleryPrototype = ; Gallery.create = fonction (id) var gal = Objet.create (galleryPrototype); return gal;; return Gallery; ()) ;

Nous en ajouterons d'autres, mais c'est un bon début. Nous utilisons une fonction anonyme auto-invoquante (ou une expression de fonction immédiatement invoquée) pour tout garder ensemble. Notre "interne" Galerie variable sera retournée et sera la valeur de notre public Galerie variable. Comme vous pouvez le voir, appeler Gallery.create va créer un nouvel objet de galerie avec Object.create. Si vous n'êtes pas familier avec Object.create, il crée simplement un nouvel objet en utilisant l'objet que vous lui transmettez en tant que prototype du nouvel objet (également compatible avec les navigateurs). Nous allons remplir ce prototype et ajouter à notre Gallery.create méthode aussi bien. Mais écrivons maintenant notre premier test:

var gal = Gallery.create ("gal-1"); TEST.areEqual (typeof gal, "objet", "La galerie doit être un objet");

Nous commençons par créer une “instance” de Galerie; ensuite, nous effectuons un test pour voir si la valeur renvoyée est un objet.

Mettez ces deux lignes dans notre gallery-test.js; maintenant, ouvrez notre index.html page dans un navigateur et ouvrez une console JavaScript. Vous devriez voir quelque chose comme ça:


Génial! Notre premier test passe!


Écrire le constructeur

Ensuite, nous allons remplir notre Gallery.create méthode. Comme vous le verrez, nous ne voulons pas rendre cet exemple de code extrêmement robuste, nous allons donc utiliser des éléments qui ne sont pas compatibles dans tous les navigateurs jamais créés. À savoir, document.querySelector / document.querySelectorAll; de plus, nous n'utiliserons que la gestion des événements par navigateur moderne. N'hésitez pas à substituer votre bibliothèque préférée si vous le souhaitez.

Commençons donc par quelques déclarations:

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

Quatre variables: notamment notre objet de galerie et celui de la galerie

    noeud (nous utiliserons je et len dans une minute). Ensuite, six propriétés sur notre fille objet:

    • gal.el est le nœud «racine» sur le markyp de notre galerie.
    • gal.imgs est une liste des
    • s qui tiennent nos vignettes.
    • gal.displayImage est la grande image dans notre galerie.
    • gal.idx est l'index, l'image en cours de visualisation.
    • gal.going est un booléen: c'est vrai si la galerie parcourt les images.
    • galids sera une liste des identifiants pour les images dans notre galerie. Par exemple, si la vignette s'appelle «dog-thumb.jpg», «dog» est l'identifiant et «dog.jpg» est l'image de la taille d'affichage..

    Notez que les éléments DOM ont querySelector et querySelectorAll méthodes aussi bien. On peut utiliser gal.el.querySelector pour s'assurer que nous ne sélectionnons que des éléments dans le balisage de cette galerie.

    Maintenant, remplissez galids avec les identifiants d'image:

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

    Assez simple, à droite?

    Enfin, connectons un gestionnaire d’événements sur le

      .

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

      Nous commençons par vérifier si l’élément le plus bas qui a reçu le clic (e.target; nous ne nous inquiétons pas du support oldIE ici) figure dans notre liste d’images; puisque NodeLists n'ont pas de Indice de méthode, nous utiliserons la version tableau (si vous n'êtes pas familier avec appel et appliquer en JavaScript, voir notre astuce sur ce sujet.). Si c'est supérieur à -1, nous le passerons à gal.set. Nous n'avons pas encore écrit cette méthode, mais nous y arriverons.

      Maintenant, retournons à notre gallery-test.js déposer et écrire des tests pour vous assurer que notre Galerie instance a les bonnes propriétés:

      TEST.areEqual (gal.el.id, "gal-1", "Gallery.el devrait être celui que nous avons spécifié"); TEST.areEqual (gal.idx, 0, "L'index de la galerie doit commencer à zéro");

      Notre premier test vérifie que notre constructeur de galerie a trouvé le bon élément. Le deuxième test vérifie que l'index commence à 0. Vous pourriez probablement écrire un tas de tests pour vérifier que nous possédons les bonnes propriétés, mais nous écrirons des tests pour les méthodes qui utiliseront ces propriétés être nécessaire.


      Construire le prototype

      Maintenant, revenons à cela galeriePrototype objet qui est actuellement vide. C’est là que nous allons abriter toutes les méthodes de notre Galerie «Instances». Commençons par le ensemble méthode: c'est la méthode la plus importante, car c'est celle qui modifie réellement l'image affichée. Il faut soit l'index de l'image, soit l'identifiant de l'image.

      // dans 'galleryProtytype', set: function (i) if (typeof i === 'chaîne') i = this.ids.indexOf (i);  this.displayImage.setAttribute ("src", "images /" + this.ids [i] + ".jpg"); return (this.idx = i); 

      Si la méthode obtient la chaîne d'ID, elle trouvera le numéro d'index correct pour cet ID. Ensuite, nous définissons le afficher l'imagede src dans le chemin d’image correct et renvoyer le nouvel index actuel tout en le définissant comme index actuel.

      Maintenant, testons cette méthode (de retour dans gallery-test.js):

      TEST.areEqual (gal.set (4), 4, "Gallery.set (avec un numéro) devrait renvoyer le même nombre transmis"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images_24 / javascript-testing-from-scratch.jpg", "Gallery.set (avec un numéro) devrait modifier l'image affichée"); TEST.areEqual (gal.set ("post"), 3, "Gallery.set (avec une chaîne) devrait être déplacé vers l'image appropriée"); TEST.areEqual (gal.displayImage.getAttribute ("src"), "images / post.jpg", "Gallery.set (avec une chaîne) devrait modifier les images affichées");

      Nous testons notre méthode de test avec un nombre et un paramètre de chaîne pour ensemble. Dans ce cas, nous sommes en mesure de vérifier la src pour l'image et assurez-vous que l'interface utilisateur est ajustée en conséquence; il n'est pas toujours possible ni nécessaire de s'assurer que ce que l'utilisateur voit réagit correctement (sans utiliser quelque chose comme cela); c'est là que le type de test par clic est utile. Cependant, nous pouvons le faire ici, alors nous allons.

      Ces tests devraient passer. Vous devriez également pouvoir cliquer sur les vignettes et avoir les versions plus grandes affichées. Ça a l'air bien!

      Passons donc à certaines méthodes qui se déplacent entre les images. Celles-ci pourraient être utiles si vous vouliez avoir les boutons «suivant» et «précédent» pour parcourir les images (nous n'aurons pas ces boutons, mais nous indiquerons les méthodes de support)..

      Pour la plupart, passer aux images suivantes et précédentes n'est pas une chose difficile. Les parties difficiles vont à la prochaine image lorsque vous êtes à la dernière, ou à la précédente lorsque vous êtes à la première.

      // à l'intérieur de 'galeriePrototype' next: function () if (this.idx === this.imgs.length - 1) return this.set (0);  return this.set (this.idx + 1); , prev: function () if (this.idx === 0) return this.set (this.imgs.length - 1);  return this.set (this.idx - 1); , curr: function () return this.idx; ,

      D'accord, ce n'est donc pas trop compliqué. Ces deux méthodes sont des méthodes «sucrées» pour utiliser ensemble. Si nous en sommes à la dernière image (this.idx === this.imgs.length -1), nous ensemble (0). Si nous sommes au premier (this.idx === 0), nous set (this.imgs.length -1). Sinon, ajoutez ou soustrayez-en un de l’index actuel. N'oubliez pas que nous retournons exactement ce qui est retourné du ensemble appel.

      Nous avons aussi le curr méthode, aussi. Ce n'est pas compliqué du tout: il retourne simplement l'index actuel. Nous allons le tester un peu plus tard.

      Alors testons ces méthodes.

      TEST.areEqual (gal.next (), 4, "La galerie doit avancer sur .next ()"); TEST.areEqual (gal.prev (), 3, "La Galerie doit revenir sur .prev ()");

      Celles-ci viennent après nos tests précédents, donc 4 et 3 sont les valeurs que nous attendions. Et ils passent!

      Il ne reste plus qu'une pièce: c'est le cycle automatique de la photo. Nous voulons pouvoir appeler gal.start () jouer à travers les images. Bien sûr, ils seront un gal.stop () méthode.

      // à l'intérieur de 'galleryPrototype' start: function (time) var thiz = this; heure = heure || 3000; this.interval = setInterval (function () thiz.next ();, heure); this.going = true; retourne vrai; , stop: function () clearInterval (this.interval); this.going = false; retourne vrai; ,

      Notre début méthode prendra paramètre: le nombre de millisecondes pendant lesquelles une image est affichée; si aucun paramètre n’est donné, la valeur par défaut est 3000 (3 secondes). Ensuite, nous utilisons simplement setInterval sur une fonction qui appellera suivant au moment opportun. Bien sûr, nous ne pouvons pas oublier de définir this.going à vrai. Enfin, nous revenons vrai.

      Arrêtez n'est pas trop difficile. Depuis que nous avons enregistré l'intervalle comme this.interval, on peut utiliser clearInterval pour y mettre fin. Ensuite, nous mettons this.going de faux et de retour vrai.

      Nous aurons deux fonctions pratiques pour indiquer si la galerie est en boucle:

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

      Maintenant, testons cette fonctionnalité.

      gal.set (0); TEST.areEqual (gal.start (), true, "La galerie devrait être en boucle"); TEST.areEqual (gal.curr (), 0, "L'indice d'image actuel doit être 0"); setTimeout (function () TEST.areEqual (gal.curr (), 1, "L'index de l'image actuelle doit être égal à 1"); TEST.areEqual (gal.isGoing (), true, "Galerie devrait être en cours"); TEST. areEqual (gal.stop (), true, "la Galerie devrait être arrêtée"); setTimeout (function () TEST.areEqual (gal.curr (), 1, "L'image actuelle doit toujours être 1"); TEST.areEqual ( gal.isStopped (), true, "La galerie doit toujours être arrêtée");, 3050);, 3050);

      C’est un peu plus compliqué que nos précédentes séries de tests: nous commençons par utiliser gal.set (0) pour nous assurer que nous commençons au début. Ensuite, nous appelons gal.start () pour commencer la boucle. Ensuite, nous testons cela gal.curr () renvoie 0, ce qui signifie que nous regardons toujours la première image. Maintenant, nous allons utiliser un setTimeout attendre 3050ms (un peu plus de 3 secondes) avant de continuer nos tests. À l'intérieur de cela setTimeout, nous ferons un autre gal.curr (); l'indice devrait maintenant être 1. Ensuite, nous allons tester que gal.isGoing () est vrai. Ensuite, nous allons arrêter la galerie gal.stop (). Maintenant, nous utilisons un autre setTimeout attendre encore près de 3 secondes; si la galerie s’est vraiment arrêtée, l’image ne sera pas en boucle, donc gal.curr () devrait toujours être 1; c'est ce que nous testons à l'intérieur du délai d'attente. Enfin, nous nous assurons que notre est arrêté la méthode fonctionne.

      Si ces tests ont réussi, félicitations! Nous avons terminé notre Galerie et ses tests.


      Conclusion

      Si vous n'avez pas encore essayé de tester, j'espère que vous avez compris à quel point les tests peuvent être simples en JavaScript. Comme je l'ai mentionné au début de ce didacticiel, de bons tests nécessiteront probablement que vous écriviez votre code JavaScript un peu différemment par rapport à vos habitudes. Cependant, j'ai trouvé que JavaScript facilement testable est également facilement maintenable. JavaScript.

      Je vous laisserai plusieurs liens qui pourraient vous être utiles au fur et à mesure que vous avancez et écrivez un bon JavaScript et de bons tests..

      • QUnit - une suite de tests unitaires (tutoriel sur QUnit)
      • Jasmine - Framework BDD (Tutoriel sur Jasmine)
      • Test JavaScript avec Assert
      • Test Driven JavaScript (book) (exemple de chapitre)