One place for hosting & domains

      Comment

      Comment enregistrer et partager des sessions de terminal en utilisant Terminalizer sur Ubuntu 18.04


      L’auteur a choisi Electronic Frontier Foundation pour recevoir un don dans le cadre du programme Write for DOnations.

      Introduction

      Terminalizer est une application d’enregistrement de terminal qui vous permet d’enregistrer votre session de terminal en temps réel, puis de la lire ultérieurement. Elle fonctionne de la même manière qu’un enregistreur d’écran de bureau, mais fonctionne dans votre terminal.

      L’enregistrement de votre session de terminal est utile si vous voulez revoir une activité particulière, ou pour aider à déboguer une erreur particulièrement délicate. Les enregistrements réalisés avec Terminalizer peuvent également être exportés sous forme de GIF animés, ce qui est idéal pour le partage en ligne ou l’ajout de contenu marketing pour votre logiciel.

      Dans ce tutoriel, vous allez installer Terminalizer, l’utiliser pour enregistrer et lire des sessions de terminal, personnaliser vos enregistrements, puis les exporter pour les partager en ligne.

      Conditions préalables

      Pour suivre ce tutoriel, vous aurez besoin de :

      Si vous souhaitez partager vos enregistrements en ligne, vous aurez également besoin de :

      Une fois que tout cela est prêt, connectez-vous à votre serveur en tant qu’utilisateur non root pour commencer.

      Étape 1 – Installation de Terminalizer

      Dans cette étape, vous allez télécharger et installer Terminalizer sur votre système. Terminalizer est écrit en utilisant Node.js, et peut être installé en utilisant le gestionnaire de paquets npm.

      Pour installer Terminalizer sur votre système, exécutez la commande suivante :

      • sudo npm install --global --allow-root --unsafe-perm=true terminalizer

      Terminalizer utilise le framework d’application Electron pour exporter des sessions de terminal enregistrées en format GIF. L’argument de commande --unsafe-perms=true est nécessaire afin d’installer Electron sur votre système d’une manière globale.

      Une fois Terminalizer installé, vous verrez une sortie semblable à ce qui suit :

      Output

      . . . /usr/local/lib └── terminalizer@0.7.1

      Ensuite, vérifiez l’installation de Terminalizer en exécutant :

      Une sortie semblable à celle-ci s’affichera :

      Output

      0.7.1

      Enfin, générez un fichier de configuration Terminalizer par défaut, que vous pouvez utiliser pour la personnalisation avancée de Terminalizer (plus détaillée à l’étape 4) :

      Cela donnera un résultat similaire à ce qui suit :

      Output

      The global config directory is created at /home/user/.terminalizer

      Maintenant que vous avez installé Terminalizer, vous pouvez effectuer votre premier enregistrement de terminal.

      Étape 2 – Enregistrement et Lecture d’une session de terminal

      Dans cette étape, vous allez enregistrer et lire une session de terminal.

      Pour commencer, configurez un nouvel enregistrement Terminalizer en utilisant le nom de votre choix :

      • terminalizer record your-recording

      Cela donnera la sortie suivante pour indiquer que l’enregistrement a démarré :

      Output

      The recording session has started Press Ctrl+D to exit and save the recording

      Vous pouvez maintenant continuer à faire tout ce que vous voulez dans votre terminal. Chaque pression de touche et commande sera enregistrée en temps réel par Terminalizer.

      Par exemple :

      • pwd
      • date
      • whoami
      • echo "Hello, world!"

      Lorsque vous souhaitez arrêter l’enregistrement, appuyez sur CTRL+D. Terminalizer sauvegardera ensuite l’enregistrement dans le fichier spécifié au format YAML, par exemple, your-recording.yml.

      Output

      Successfully Recorded The recording data is saved into the file: /home/user/your-recording.yml

      Vous pouvez être invité par Terminalizer à partager votre enregistrement en ligne. Appuyez simplement sur CTRL+C pour annuler ce partage pour l’instant, car vous pouvez lire l’enregistrement de terminal localement pour commencer.

      Ensuite, lisez la session de terminal enregistrée avec la commande suivante :

      • terminalizer play your-recording

      Cela rejouera la session enregistrée en temps réel dans votre terminal :

      Output

      user@droplet:~$ pwd /home/user user@droplet:~$ date Sun Mar 8 14:55:36 UTC 2020 user@droplet:~$ whoami user user@droplet:~$ echo "Hello, world!" Hello, world! user@droplet:~$ logout

      Vous pouvez également ajuster la vitesse de lecture de votre enregistrement en utilisant l’option --speed-factor.

      Par exemple, ce qui suit jouera votre enregistrement deux fois plus lentement (50 % de la vitesse) :

      • terminalizer play your-recording --speed-factor 2

      Vous pouvez aussi lire votre enregistrement deux fois plus vite (200 % de la vitesse) :

      • terminalizer play your-recording --speed-factor 0.5

      Vous avez enregistré et lu votre session de terminal. Maintenant, vous pouvez partager en ligne une session de terminal enregistrée.

      Étape 3 – Partage d’une session de terminal enregistrée

      Dans cette étape, vous allez partager votre session de terminal enregistrée sur la page Terminalizer Explore.

      Commencez par sélectionner la session enregistrée que vous souhaitez partager :

      • terminalizer share your-recording

      Vous serez ensuite invité à fournir quelques métadonnées basiques sur votre enregistrement, telles que son titre et sa description :

      Output

      Please enter some details about your recording ? Title Title of Your Recording ? Description Description of Your Recording ? Tags such as git,bash,game Comma-separated Tags for Your Recording

      Attention : les enregistrements de Terminalizer sont partagés publiquement par défaut, assurez-vous qu’il n’y a pas de détails d’identification personnelle ou confidentiels que vous ne voulez pas partager dans votre enregistrement de terminal.

      Si c’est la première fois que vous partagez une session enregistrée à l’aide de Terminalizer, vous devrez lier votre compte Terminalizer. Terminalizer affichera un lien de vérification si nécessaire :

      Output

      Open the following link in your browser and login into your account https://terminalizer.com/token?token=your-token When you do it, press any key to continue

      Attention : assurez-vous que votre jeton Terminalizer est bien privé, car il permettra à quiconque en sa possession d’accéder à votre compte Terminalizer.

      Une fois que vous avez visité le lien dans votre navigateur Web et que vous vous êtes connecté à votre compte Terminalizer, appuyez sur n’importe quelle touche pour continuer.

      Terminalizer va maintenant télécharger votre enregistrement et vous fournir le lien pour le visualiser :

      Output

      Successfully Uploaded The recording is available on the link: https://terminalizer.com/view/your-recording-id

      Visiter le lien dans un navigateur Web de bureau vous permettra de visualiser votre enregistrement partagé :

      Une capture d'écran du site Web de Terminalizer, montrant un exemple d'enregistrement de terminal partagé

      Vous avez partagé une session de terminal enregistrée sur le site Web de Terminalizer et l’avez consultée dans votre navigateur Web.

      Étape 4 – Définition de la configuration avancée de Terminalizer

      Maintenant que vous connaissez un peu Terminalizer, vous pouvez commencer à utiliser certaines des options de personnalisation les plus avancées, telles que la possibilité d’ajuster les couleurs et le style de l’affichage.

      Chaque enregistrement hérite de la configuration par défaut du fichier de configuration global de Terminalizer, qui est situé dans ~/.terminalizer/config.yml. Cela signifie que vous pouvez modifier la configuration pour les enregistrements individuels directement en modifiant le fichier d’enregistrement (par exemple  : your-recording.yml). Vous pouvez également modifier la configuration globale, ce qui aura un impact sur tous les nouveaux enregistrements.

      Dans cet exemple, vous allez modifier le fichier de configuration globale, mais ces instructions s’appliquent également aux fichiers de configuration d’enregistrements individuels.

      Commencez par ouvrir le fichier de configuration globale de Terminalizer dans votre éditeur de texte, par exemple nano :

      • nano ~/.terminalizer/config.yml

      Chacune des options de configuration disponibles dans le fichier est commentée afin d’expliquer ce qu’elle fait.

      Il existe plusieurs options de configuration courantes que vous souhaiterez peut-être adapter à votre convenance :

      • cols : fixe explicitement le nombre de colonnes de terminal utilisées pour votre enregistrement.
      • rows : fixe explicitement le nombre de lignes de terminal utilisées pour votre enregistrement.
      • frameDelay : ignore le délai entre chaque frappe pendant la lecture.
      • maxIdleTime : spécifie un temps maximum entre les frappes pendant la lecture.
      • cursorStyle : précise le style de curseur de terminal par défaut entre block, bar et underline.
      • fontFamily : spécifie une liste de polices de lecture préférées, dans l’ordre de préférence.
      • theme : ajuste le schéma de couleurs de la lecture, par exemple pour créer un terminal noir sur blanc, etc.

      Par exemple, vous pouvez réaliser un affichage de terminal blanc sur noir en configurant les options suivantes :

      config.yml

      . . .
      theme:
        background: "white"
        foreground: "black"
      . . .
      

      Cela donnera un résultat similaire à ce qui suit :

      Une capture d'écran du site Web de Terminalizer, montrant un exemple d'enregistrement avec un thème noir sur blanc

      Vous pouvez ajuster le style du curseur pour rendre l’enregistrement plus facile à comprendre, par exemple en remplaçant le curseur de style block par défaut par un curseur souligné de type underline :

      config.yml

      . . .
      cursorStyle: underline
      . . .
      

      Cela donnera un résultat semblable à ce qui suit :

      Une capture d'écran du site Web de Terminalizer, montrant un exemple d'enregistrement avec un curseur de type underline

      Une fois que vous avez apporté les modifications souhaitées, enregistrez le fichier et retournez dans votre terminal.

      Si vous avez modifié la configuration globale de Terminalizer, ces paramètres s’appliqueront à tous les nouveaux enregistrements à venir. Si vous modifiez une configuration d’enregistrement spécifique, Terminalizer appliquera immédiatement les changements à cet enregistrement en particulier.

      Notez que le style de lecture personnalisé ne s’applique qu’aux sessions d’enregistrement partagées. Si vous lisez un enregistrement directement dans votre terminal, vous utiliserez toujours le style et la palette de couleurs de votre terminal par défaut.

      Dans cette dernière étape, vous avez passé en revue certaines des options de configuration avancées pour Terminalizer.

      Conclusion

      Dans cet article, vous avez utilisé Terminalizer pour enregistrer et partager une session de terminal. Vous avez maintenant les connaissances nécessaires pour créer des démonstrations enregistrées de votre logiciel à utiliser dans du contenu marketing ou pour partager des astuces en ligne de commande avec des amis.

      Si vous souhaitez rendre et exporter des enregistrements Terminalizer au format GIF, vous pouvez installer Terminalizer sur une machine avec une interface utilisateur ou un bureau graphique et utiliser les fonctions de rendu intégrées :

      Vous pouvez également naviguer sur le site Web de Terminalizer pour voir les sessions de terminal enregistrées et partagées par d’autres utilisateurs :



      Source link

      Comment tester un module Node.js avec Mocha et Assert


      L’auteur a choisi le Open Internet/Free Speech Fund pour recevoir un don dans le cadre du programme Write for DOnations.

      Introduction

      Les tests font partie intégrante du développement de logiciels. Il est courant pour les programmeurs d’exécuter un code qui teste leur application lorsqu’ils apportent des modifications, afin de confirmer qu’elle se comporte comme ils le souhaitent. Avec la bonne configuration de test, ce processus peut même être automatisé, ce qui permet de gagner beaucoup de temps. L’exécution régulière de tests après l’écriture d’un nouveau code permet de s’assurer que les modifications ne cassent pas les fonctionnalités préexistantes. Cela permet d’accroître la confiance qu’ont les développeurs dans leur base de code, surtout lorsqu’elle est déployée en production pour que les utilisateurs puissent interagir avec elle.

      Un framework de test structure la manière dont nous créons les cas de test. Mocha est un framework de test JavaScript populaire qui organise nos cas de test et les exécute pour nous. Cependant, Mocha ne vérifie pas le comportement de notre code. Pour comparer les valeurs dans un test, nous pouvons utiliser le module Node.js assert.

      Dans cet article, vous allez écrire des tests pour un module de liste TODO (À faire) de Node.js. Vous configurerez et utiliserez le framework de test Mocha pour structurer vos tests. Ensuite, vous utiliserez le module Node.js assert pour créer les tests eux-mêmes. En ce sens, vous utiliserez Mocha comme constructeur de plan, et assert pour implémenter le plan.

      Conditions préalables

      Étape 1 — Écriture d’un module Node

      Commençons cet article par l’écriture du module Node.js que nous aimerions tester. Ce module permet de gérer une liste d’éléments TODO. Grâce à ce module, nous pourrons dresser la liste de toutes les choses à faire que nous suivons, ajouter de nouveaux éléments et marquer certains comme terminés. De plus, nous pourrons exporter une liste d’éléments TODO vers un fichier CSV. Si vous souhaitez un rappel sur l’écriture des modules Node.js, vous pouvez lire notre article Comment créer un module Node.js.

      Tout d’abord, nous devons configurer l’environnement de codage. Créez un dossier avec le nom de votre projet dans votre terminal. Ce tutoriel utilisera le nom todos :

      Entrez ensuite dans ce dossier :

      Initialisez maintenant npm, car nous utiliserons plus tard sa fonctionnalité CLI pour effectuer les tests :

      Nous n’avons qu’une seule dépendance, Mocha, que nous utiliserons pour organiser et exécuter nos tests. Pour télécharger et installer Mocha, utilisez ce qui suit :

      • npm i request --save-dev mocha

      Nous installons Mocha comme une dépendance dev, car il n’est pas requis par le module dans un environnement de production. Si vous souhaitez en savoir plus sur les packages Node.js ou sur npm, consultez notre guide Comment utiliser les modules Node.js avec npm et package.json.

      Enfin, créons le fichier qui contiendra le code de notre module :

      Une fois que cela est fait, nous sommes prêts à créer notre module. Ouvrez index.js dans un éditeur de texte comme nano :

      Commençons par définir la classe Todos. Cette classe contient toutes les fonctions dont nous avons besoin pour gérer notre liste TODO. Ajoutez les lignes de code suivantes à index.js :

      todos/index.js

      class Todos {
          constructor() {
              this.todos = [];
          }
      }
      
      module.exports = Todos;
      

      Nous commençons le fichier en créant une classe Todos. Sa fonction constructor() ne prend aucun argument, donc nous n’avons pas besoin de fournir de valeurs pour instancier un objet pour cette classe. Tout ce que nous faisons lorsque nous initialisons un objet Todos est de créer une propriété todos qui est un tableau vide.

      La ligne modules permet aux autres modules Node.js d’exiger notre classe Todos. Si nous n’exportons pas explicitement la classe, le fichier de test que nous créerons plus tard ne pourra pas l’utiliser.

      Ajoutons une fonction pour retourner le tableau des todos que nous avons stocké. Écrivez les lignes surlignées suivantes :

      todos/index.js

      class Todos {
          constructor() {
              this.todos = [];
          }
      
          list() {
              return [...this.todos];
          }
      }
      
      module.exports = Todos;
      

      Notre fonction list() renvoie une copie du tableau qui est utilisé par la classe. Elle réalise une copie du tableau en utilisant la syntaxe de déstructuration de JavaScript. Nous faisons une copie du tableau de sorte que les modifications apportées par l’utilisateur au tableau renvoyé par list() n’affectent pas le tableau utilisé par l’objet Todos.

      Remarque : les tableaux JavaScript sont des types de références. Cela signifie que pour toute affectation de variable à un tableau ou toute invocation de fonction avec un tableau comme paramètre, JavaScript se réfère au tableau original qui a été créé. Par exemple, si nous avons un tableau avec trois éléments appelés x, et que nous créons une nouvelle variable y telle que y = x, y et x se réfèrent tous deux à la même chose. Toute modification apportée au tableau avec y a une incidence sur la variable x et vice versa.

      Écrivons maintenant la fonction add(), qui ajoute un nouvel élément TODO :

      todos/index.js

      class Todos {
          constructor() {
              this.todos = [];
          }
      
          list() {
              return [...this.todos];
          }
      
          add(title) {
              let todo = {
                  title: title,
                  completed: false,
              }
      
              this.todos.push(todo);
          }
      }
      
      module.exports = Todos;
      

      Notre fonction add() prend une chaîne et la place dans la propriété title d’un nouvel objet JavaScript. Le nouvel objet a également une propriété completed, qui est réglée sur false par défaut. Nous ajoutons ensuite ce nouvel objet à notre tableau de TODO.

      L’une des fonctionnalités importantes dans un gestionnaire TODO est la capacité de marquer les éléments comme étant terminés. Pour cette implémentation, nous passerons en boucle sur notre tableau todos pour trouver l’élément TODO que l’utilisateur recherche. Si l’élément est trouvé, nous le marquerons comme terminé. Si l’élément n’est pas trouvé, nous lancerons une erreur.

      Ajoutez la fonction complete() comme ceci :

      todos/index.js

      class Todos {
          constructor() {
              this.todos = [];
          }
      
          list() {
              return [...this.todos];
          }
      
          add(title) {
              let todo = {
                  title: title,
                  completed: false,
              }
      
              this.todos.push(todo);
          }
      
          complete(title) {
              let todoFound = false;
              this.todos.forEach((todo) => {
                  if (todo.title === title) {
                      todo.completed = true;
                      todoFound = true;
                      return;
                  }
              });
      
              if (!todoFound) {
                  throw new Error(`No TODO was found with the title: "${title}"`);
              }
          }
      }
      
      module.exports = Todos;
      

      Sauvegardez le fichier et quittez l’éditeur de texte.

      Nous avons maintenant un gestionnaire TODO de base avec lequel nous pouvons expérimenter. Ensuite, nous allons tester manuellement notre code pour voir si l’application fonctionne.

      Étape 2 — Test manuel du code

      Dans cette étape, nous allons exécuter les fonctions de notre code et observer les résultats pour nous assurer qu’ils correspondent à nos attentes. C’est ce qu’on appelle le test manuel. Il s’agit probablement de la méthode de test la plus couramment appliquée par les programmeurs. Même si nous automatiserons nos tests plus tard avec Mocha, nous commencerons par tester manuellement notre code pour mieux comprendre la différence entre les tests manuels et les frameworks de test.

      Ajoutons deux éléments TODO à notre application et marquons-en un comme étant terminé. Lancez le Node.js REPL dans le même dossier que le fichier index.js :

      Vous verrez l’invite > dans le REPL qui nous indique que nous pouvons entrer le code JavaScript. Saisissez ce qui suit à l’invite :

      • const Todos = require('./index');

      Avec require(), nous chargeons le module TODO dans une variable Todos. Rappelons que notre module renvoie la classe Todos par défaut.

      Maintenant, instancions un objet pour cette classe. Dans le REPL, ajoutez cette ligne de code :

      • const todos = new Todos();

      Nous pouvons utiliser l’objet todos pour vérifier que notre implémentation fonctionne. Ajoutons notre premier élément TODO :

      Jusqu’à présent, nous n’avons pas vu de sortie dans notre terminal. Vérifions que nous avons stocké notre élément TODO "run code" en obtenant une liste de tous nos éléments TODO :

      Vous verrez cette sortie dans votre REPL :

      Output

      [ { title: 'run code', completed: false } ]

      Ceci est le résultat attendu : nous avons un élément TODO dans notre tableau des TODO, et il n’est pas terminé par défaut.

      Ajoutons un autre élément TODO :

      • todos.add("test everything");

      Marquez le premier élément TODO comme étant terminé :

      • todos.complete("run code");

      Notre objet todos va maintenant gérer deux éléments : "run code" et "test everything". Le TODO "run code" sera également terminé. Confirmons cela en appelant une nouvelle fois list() :

      Le REPL produira la sortie :

      Output

      [ { title: 'run code', completed: true }, { title: 'test everything', completed: false } ]

      Maintenant, quittez le REPL avec ce qui suit :

      Nous avons confirmé que notre module se comporte comme nous nous y attendions. Bien que nous n’ayons pas mis notre code dans un fichier de test ou utilisé une bibliothèque de test, nous avons testé notre code manuellement. Malheureusement, cette façon de tester prend beaucoup de temps à chaque fois que nous effectuons une modification. Utilisons maintenant les tests automatisés dans Node.js et voyons si nous pouvons résoudre ce problème avec le framework de test Mocha.

      Étape 3 – Écriture de votre premier test avec Mocha et Assert

      Dans l’étape précédente, nous avons testé notre application manuellement. Cela fonctionne pour les cas d’utilisation individuels, mais à mesure que notre module évolue, cette méthode devient moins viable. Lorsque nous testons de nouvelles fonctions, nous devons nous assurer que la fonctionnalité ajoutée n’a pas créé de problèmes dans l’ancienne fonctionnalité. Nous souhaitons tester chaque fonction pour chaque modification du code, mais le faire manuellement demanderait beaucoup d’efforts et serait sujet à des erreurs.

      Mettre en place des tests automatisés constitue une pratique plus efficace. Il s’agit de scripts de test écrits comme tout autre bloc de code. Nous exécutons nos fonctions avec des entrées définies et inspectons leurs effets pour nous assurer qu’elles se comportent comme nous l’attendons. Au fur et à mesure que notre base de code s’accroît, nos tests automatisés s’étendent également. Lorsque nous écrivons de nouveaux tests pour les nouvelles fonctionnalités, nous pouvons vérifier que l’ensemble du module fonctionne toujours, sans avoir à nous rappeler à chaque fois comment utiliser chaque fonction.

      Dans ce tutoriel, nous utilisons le framework de test Mocha avec le module Node.js assert. Voyons en pratique comment ils fonctionnent ensemble.

      Pour commencer, créez un fichier pour stocker notre code test :

      Utilisez maintenant votre éditeur de texte préféré pour ouvrir le fichier test. Vous pouvez utiliser nano comme auparavant :

      Dans la première ligne du fichier texte, nous chargerons le module TODO comme nous l’avons fait dans le shell Node.js. Nous chargerons ensuite le module assert lorsque nous écrirons nos tests. Ajoutez les lignes suivantes :

      todos/index.test.js

      const Todos = require('./index');
      const assert = require('assert').strict;
      

      La propriété strict du module assert nous permettra d’utiliser des tests d’égalité spéciaux recommandés par Node.js, qui nous aideront à préparer l’avenir, car ils prennent en compte un plus grand nombre de cas d’utilisation.

      Avant de commencer à écrire les tests, voyons comment Mocha organise notre code. Les tests structurés en Mocha suivent généralement ce modèle :

      describe([String with Test Group Name], function() {
          it([String with Test Name], function() {
              [Test Code]
          });
      });
      

      Remarquez deux fonctions clés : describe() et it(). La fonction describe() est utilisée pour regrouper des tests similaires. Mocha n’a pas besoin de cette fonction pour exécuter les tests, mais le regroupement des tests rend notre code de test plus facile à maintenir. Il est recommandé de regrouper vos tests de manière à pouvoir mettre à jour facilement les tests similaires.

      La fonction it() contient notre code de test. C’est là que nous pouvons interagir avec les fonctions de notre module et utiliser la bibliothèque assert. De nombreuses fonctions it() peuvent être définies dans une fonction describe().

      Notre but dans cette section est d’utiliser Mocha et assert pour automatiser notre test manuel. Nous allons procéder étape par étape, en commençant par notre bloc de description. Ajoutez ce qui suit à votre fichier après les lignes de module :

      todos/index.test.js

      ...
      describe("integration test", function() {
      });
      

      Avec ce bloc de code, nous avons créé un regroupement pour nos tests intégrés. Les tests unitaires permettent de tester une fonction à la fois. Les tests d’intégration permettent de vérifier le bon fonctionnement global des fonctions au sein des modules ou entre les modules. Lorsque Mocha effectuera notre test, tous les tests de ce bloc de description seront effectués dans le groupe "integration test".

      Ajoutons une fonction it() pour pouvoir commencer à tester le code de notre module :

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
          });
      });
      

      Remarquez à quel point nous avons rendu le nom du test descriptif. Si quelqu’un exécute notre test, il saura immédiatement ce qui a réussi ou échoué. Une application bien testée est généralement une application bien documentée, et les tests peuvent parfois constituer un type de documentation efficace.

      Pour notre premier test, nous allons créer un nouvel objet Todos et vérifier qu’il ne contient aucun élément :

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              assert.notStrictEqual(todos.list().length, 1);
          });
      });
      

      La première nouvelle ligne de code a instancié un nouvel objet Todos comme nous le ferions dans le REPL Node.js ou un autre module. Dans la deuxième nouvelle ligne, nous utilisons le module assert.

      À partir du module assert, nous utilisons la méthode notStrictEqual(). Cette fonction prend deux paramètres : la valeur que nous voulons tester (appelée actual, valeur réelle) et la valeur que nous nous attendons à obtenir (appelée expected, valeur attendue). Si les deux arguments sont identiques, notStrictEqual() lance une erreur pour faire échouer le test.

      Enregistrez et quittez index.test.js.

      Le cas de base sera vrai car la longueur doit être de 0, ce qui n’est pas 1. Confirmons cela en exécutant Mocha. Pour ce faire, nous devons modifier notre fichier package.json. Ouvrez votre fichier package.json avec votre éditeur de texte :

      Maintenant, dans votre propriété scripts, changez le contenu pour qu’il ressemble à ceci :

      todos/package.json

      ...
      "scripts": {
          "test": "mocha index.test.js"
      },
      ...
      

      Nous venons de modifier le comportement de la commande test de la CLI de npm. Lorsque nous exécuterons npm test, npm passera en revue la commande que nous venons d’entrer dans package.json. Il cherchera la bibliothèque Mocha dans notre dossier node_modules et exécutera la commande mocha avec notre fichier test.

      Enregistrez et quittez package.json.

      Voyons ce qui se passe lorsque nous exécutons notre test. Dans votre terminal, entrez :

      La commande produira la sortie suivante :

      Output

      > [email protected] test your_file_path/todos > mocha index.test.js integrated test ✓ should be able to add and complete TODOs 1 passing (16ms)

      Cette sortie nous montre d’abord le groupe de tests qui va être exécuté. Pour chaque test individuel au sein d’un groupe, le cas de test est en retrait. Nous voyons notre nom de test tel que nous l’avons décrit dans la fonction it(). La coche sur le côté gauche du cas de test indique que le test a réussi.

      En bas, nous obtenons un résumé de tous nos tests. Dans notre cas, notre test unique a réussi et a été effectué en 16 ms (la durée varie d’un ordinateur à l’autre).

      Nos tests ont commencé avec succès. Cependant, ce cas de test peut permettre des faux positifs. Un faux positif est un cas de test qui réussit alors qu’il devrait échouer.

      Nous vérifions actuellement que la longueur du tableau n’est pas égale à 1. Modifions le test pour que cette condition se vérifie alors qu’elle ne devrait pas. Ajoutez les lignes suivantes à index.test.js :

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              todos.add("get up from bed");
              todos.add("make up bed");
              assert.notStrictEqual(todos.list().length, 1);
          });
      });
      

      Enregistrez et quittez le fichier.

      Nous avons ajouté deux éléments TODO. Exécutons le test pour voir ce qu’il se passe :

      Cela donnera le résultat :

      Output

      ... integrated test ✓ should be able to add and complete TODOs 1 passing (8ms)

      Il réussit conformément aux attentes, puisque la longueur est supérieure à 1, mais il va à l’encontre de l’objectif initial de ce premier test. Le premier test est destiné à confirmer que nous partons avec un objet vide. Un meilleur test permettra de confirmer cela dans tous les cas.

      Modifions le test pour qu’il ne réussisse que si nous n’avons absolument aucun TODO en stock. Apportez les modifications suivantes à index.test.js :

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              todos.add("get up from bed");
              todos.add("make up bed");
              assert.strictEqual(todos.list().length, 0);
          });
      });
      

      Vous avez modifié notStrictEqual() en strictEqual(), une fonction qui vérifie l’égalité entre son argument réel et son argument attendu. L’égalité stricte échouera si nos arguments ne sont pas exactement les mêmes.

      Enregistrez et quittez, puis exécutez le test pour que nous puissions voir ce qu’il se passe :

      Cette fois-ci, la sortie affichera une erreur :

      Output

      ... integration test 1) should be able to add and complete TODOs 0 passing (16ms) 1 failing 1) integration test should be able to add and complete TODOs: AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B: + expected - actual - 2 + 0 + expected - actual -2 +0 at Context.<anonymous> (index.test.js:9:10) npm ERR! Test failed. See above for more details.

      Ce texte nous sera utile pour comprendre les raisons de l’échec du test. Notez que puisque le test a échoué, il n’y avait pas de coche avant le cas de test.

      Notre résumé de test n’est plus au bas de la sortie, mais juste après l’affichage de notre liste de cas de test :

      ...
      0 passing (29ms)
        1 failing
      ...
      

      La suite de la sortie nous fournit des données sur nos tests échoués. Tout d’abord, nous voyons quel cas de test a échoué :

      ...
      1) integrated test
             should be able to add and complete TODOs:
      ...
      

      Ensuite, nous voyons pourquoi notre test a échoué :

      ...
            AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
      + expected - actual
      
      - 2
      + 0
            + expected - actual
      
            -2
            +0
      
            at Context.<anonymous> (index.test.js:9:10)
      ...
      

      Une erreur AssertionError est lancée lorsque strictEqual() échoue. Nous voyons que la valeur expected, 0, est différente de la valeur actual, 2.

      Nous voyons alors dans notre fichier test la ligne où le code échoue. Dans ce cas, c’est la ligne 10.

      Nous avons ainsi vu par nous-mêmes que notre test échouera si nous nous attendons à des valeurs incorrectes. Remettons notre cas de test à sa juste valeur. Tout d’abord, ouvrez le fichier :

      Retirez ensuite les lignes todos.add pour que votre code ressemble à ce qui suit :

      todos/index.test.js

      ...
      describe("integration test", function () {
          it("should be able to add and complete TODOs", function () {
              let todos = new Todos();
              assert.strictEqual(todos.list().length, 0);
          });
      });
      

      Enregistrez et quittez le fichier.

      Exécutez-le une fois de plus pour confirmer qu’il réussit sans faux positif potentiel :

      La sortie sera la suivante :

      Output

      ... integration test ✓ should be able to add and complete TODOs 1 passing (15ms)

      Nous avons désormais bien amélioré la résilience de notre test. Continuons avec notre test d’intégration. L’étape suivante consiste à ajouter un nouvel élément TODO à index.test.js

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              assert.strictEqual(todos.list().length, 0);
      
              todos.add("run code");
              assert.strictEqual(todos.list().length, 1);
              assert.deepStrictEqual(todos.list(), [{title: "run code", completed: false}]);
          });
      });
      

      Après avoir utilisé la fonction add(), nous confirmons que nous avons maintenant un TODO géré par notre objet todos avec strictEqual(). Notre prochain test confirme les données dans les todos avec deepStrictEqual(). La fonction deepStrictEqual() teste récursivement que nos objets attendus et réels ont les mêmes propriétés. Dans le cas présent, elle vérifie que les tableaux que nous attendons ont tous deux un objet JavaScript en leur sein. Elle vérifie ensuite que leurs objets JavaScript ont les mêmes propriétés, c’est-à-dire que leurs propriétés title sont toutes deux "run code" et que les deux propriétés completed sont false.

      Nous terminons ensuite les tests restants en utilisant ces deux contrôles d’égalité selon les besoins, en ajoutant les lignes surlignées suivantes :

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              assert.strictEqual(todos.list().length, 0);
      
              todos.add("run code");
              assert.strictEqual(todos.list().length, 1);
              assert.deepStrictEqual(todos.list(), [{title: "run code", completed: false}]);
      
              todos.add("test everything");
              assert.strictEqual(todos.list().length, 2);
              assert.deepStrictEqual(todos.list(),
                  [
                      { title: "run code", completed: false },
                      { title: "test everything", completed: false }
                  ]
              );
      
              todos.complete("run code");
              assert.deepStrictEqual(todos.list(),
                  [
                      { title: "run code", completed: true },
                      { title: "test everything", completed: false }
                  ]
          );
        });
      });
      

      Enregistrez et quittez le fichier.

      Notre test reproduit maintenant notre test manuel. Grâce à ces tests programmatiques, nous n’avons pas besoin de vérifier continuellement les sorties si nos tests réussissent lorsque nous les effectuons. Il est conseillé de tester chaque aspect de l’utilisation pour vous assurer que le code est correctement testé.

      Exécutons une fois encore notre npm test pour obtenir cette sortie familière :

      Output

      ... integrated test ✓ should be able to add and complete TODOs 1 passing (9ms)

      Vous avez désormais configuré un test intégré avec le framework Mocha et la bibliothèque assert.

      Imaginons une situation dans laquelle nous avons partagé notre module avec d’autres développeurs et que ceux-ci nous donnent leur avis maintenant. Une bonne partie de nos utilisateurs voudraient que la fonction complete() renvoie une erreur si aucun TODO n’a encore été ajouté. Ajoutons cette fonctionnalité à notre fonction complete().

      Ouvrez index.js dans votre éditeur de texte :

      Ajoutez ce qui suit à la fonction :

      todos/index.js

      ...
      complete(title) {
          if (this.todos.length === 0) {
              throw new Error("You have no TODOs stored. Why don't you add one first?");
          }
      
          let todoFound = false
          this.todos.forEach((todo) => {
              if (todo.title === title) {
                  todo.completed = true;
                  todoFound = true;
                  return;
              }
          });
      
          if (!todoFound) {
              throw new Error(`No TODO was found with the title: "${title}"`);
          }
      }
      ...
      

      Enregistrez et quittez le fichier.

      Ajoutons maintenant un nouveau test pour cette nouvelle fonctionnalité. Nous voulons vérifier que si nous appelons la fonction complete sur un objet Todos qui n’a pas d’éléments, il nous renverra notre erreur spéciale.

      Retournez dans index.test.js :

      À la fin du fichier, ajoutez le code suivant :

      todos/index.test.js

      ...
      describe("complete()", function() {
          it("should fail if there are no TODOs", function() {
              let todos = new Todos();
              const expectedError = new Error("You have no TODOs stored. Why don't you add one first?");
      
              assert.throws(() => {
                  todos.complete("doesn't exist");
              }, expectedError);
          });
      });
      

      Nous utilisons describe() et it() comme auparavant. Notre test commence par la création d’un nouvel objet todos. Nous définissons ensuite l’erreur que nous nous attendons à recevoir lorsque nous appelons la fonction complete().

      Ensuite, nous utilisons la fonction throws() du module assert. Cette fonction a été créée pour que nous puissions vérifier les erreurs qui sont lancées dans notre code. Son premier argument est une fonction qui contient le code qui lance l’erreur. Le deuxième argument est l’erreur que nous nous attendons à recevoir.

      Dans votre terminal, exécutez les tests avec npm test une fois encore et vous verrez maintenant le résultat suivant :

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs 2 passing (25ms)

      Cette sortie met en évidence l’intérêt de faire des tests automatisés avec Mocha et assert. Grâce à nos scripts de tests, chaque fois que nous exécutons npm test, nous vérifions que tous nos tests sont réussis. Nous n’avons pas eu besoin de vérifier manuellement si l’autre code fonctionne toujours : nous savons que c’est le cas, parce que le test a encore réussi.

      Jusqu’à présent, nos tests ont permis de vérifier les résultats de code synchrone. Voyons comment nous devrions adapter nos nouvelles habitudes de test pour travailler avec du code asynchrone.

      Étape 4 — Test de code asynchrone

      L’une des caractéristiques que nous voulons dans notre module TODO est une fonction d’exportation CSV. Cela permettra d’imprimer dans un fichier tous les TODO que nous avons ainsi que leur état d’avancement. Pour cela, nous devons utiliser le module fs, un module Node.js intégré pour travailler avec le système de fichiers.

      Écrire dans un fichier est une opération asynchrone. Il existe de nombreuses façons d’écrire dans un fichier dans Node.js. Nous pouvons utiliser les rappels, les promesses ou les mots-clés async/await. Dans cette section, nous allons voir comment nous écrivons des tests pour ces différentes méthodes.

      Rappels

      Une fonction de rappel ou callback est une fonction qui sert d’argument à une fonction asynchrone. Elle est appelée quand l’opération asynchrone est terminée.

      Ajoutons une fonction appelée saveToFile() à notre classe Todos. Cette fonction permet de construire une chaîne en passant en boucle tous nos articles TODO et en écrivant cette chaîne dans un fichier.

      Ouvrez votre fichier index.js :

      Dans ce fichier, ajoutez le code surligné suivant :

      todos/index.js

      const fs = require('fs');
      
      class Todos {
          constructor() {
              this.todos = [];
          }
      
          list() {
              return [...this.todos];
          }
      
          add(title) {
              let todo = {
                  title: title,
                  completed: false,
              }
              this.todos.push(todo);
          }
      
          complete(title) {
              if (this.todos.length === 0) {
                  throw new Error("You have no TODOs stored. Why don't you add one first?");
              }
      
              let todoFound = false
              this.todos.forEach((todo) => {
                  if (todo.title === title) {
                      todo.completed = true;
                      todoFound = true;
                      return;
                  }
              });
      
              if (!todoFound) {
                  throw new Error(`No TODO was found with the title: "${title}"`);
              }
          }
      
          saveToFile(callback) {
              let fileContents = 'Title,Completedn';
              this.todos.forEach((todo) => {
                  fileContents += `${todo.title},${todo.completed}n`
              });
      
              fs.writeFile('todos.csv', fileContents, callback);
          }
      }
      
      module.exports = Todos;
      

      Nous devons d’abord importer le module fs dans notre fichier. Nous ajoutons ensuite notre nouvelle fonction saveToFile(). Notre fonction prend une fonction de rappel qui sera utilisée une fois l’opération d’écriture du fichier terminée. Dans cette fonction, nous créons une variable fileContents qui stocke toute la chaîne que nous voulons enregistrer en tant que fichier. Elle est initialisée avec les en-têtes CSV. Nous passons ensuite en boucle chaque élément TODO avec la méthode forEach() du tableau interne. Au fur et à mesure de l’itération, nous ajoutons les propriétés title et completed des objets todos individuels.

      Enfin, nous utilisons le module fs pour écrire le fichier avec la fonction writeFile(). Notre premier argument est le nom du fichier : todos.csv. Le second est le contenu du fichier, dans ce cas, notre variable fileContents. Notre dernier argument est notre fonction de rappel, qui gère toute erreur d’écriture de fichier.

      Enregistrez et quittez le fichier.

      Nous allons maintenant écrire un test pour notre fonction saveToFile(). Notre test aura deux objectifs : confirmer l’existence du fichier, puis vérifier qu’il a le bon contenu.

      Ouvrez le fichier index.test.js :

      Commençons par charger le module fs en haut du fichier, car nous l’utiliserons pour tester nos résultats :

      todos/index.test.js

      const Todos = require('./index');
      const assert = require('assert').strict;
      const fs = require('fs');
      ...
      

      Maintenant, à la fin du fichier, ajoutons notre nouveau cas de test :

      todos/index.test.js

      ...
      describe("saveToFile()", function() {
          it("should save a single TODO", function(done) {
              let todos = new Todos();
              todos.add("save a CSV");
              todos.saveToFile((err) => {
                  assert.strictEqual(fs.existsSync('todos.csv'), true);
                  let expectedFileContents = "Title,Completednsave a CSV,falsen";
                  let content = fs.readFileSync("todos.csv").toString();
                  assert.strictEqual(content, expectedFileContents);
                  done(err);
              });
          });
      });
      

      Comme auparavant, nous utilisons describe() pour grouper notre test séparément des autres car il implique de nouvelles fonctionnalités. La fonction it() est légèrement différente de nos autres fonctions. Habituellement, la fonction de rappel que nous utilisons n’a pas d’arguments. Cette fois-ci, nous avons l’argument done. Nous avons besoin de cet argument lorsque nous testons des fonctions avec des rappels. La fonction de rappel done() est utilisée par Mocha pour savoir quand une fonction asynchrone est terminée.

      Toutes les fonctions de rappel testées dans Mocha doivent appeler le rappel done(). Sinon, Mocha ne saurait jamais quand la fonction est terminée et serait coincé dans l’attente d’un signal.

      En continuant, nous créons notre instance Todos et y ajoutons un seul élément. Nous appelons ensuite la fonction saveToFile(), avec un rappel qui capture une erreur d’écriture de fichier. Notez que notre test pour cette fonction réside dans le rappel. Si notre code de test était en dehors du rappel, il échouerait tant que le code serait appelé avant que l’écriture du fichier ne soit terminée.

      Dans notre fonction de rappel, nous vérifions d’abord que notre fichier existe :

      todos/index.test.js

      ...
      assert.strictEqual(fs.existsSync('todos.csv'), true);
      ...
      

      La fonction fs.existsSync() renvoie true si le chemin du fichier dans son argument existe, sinon, elle renvoie false.

      Remarque : les fonctions du module fs sont asynchrones par défaut. Cependant, pour les fonctions clés, elles font des contreparties synchrones. Ce test est plus simple en utilisant des fonctions synchrones, car nous n’avons pas besoin d’imbriquer le code asynchrone pour nous assurer qu’il fonctionne. Dans le module fs, les fonctions synchrones se terminent généralement par "Sync" à la fin de leur nom.

      Nous créons ensuite une variable pour stocker notre valeur attendue :

      todos/index.test.js

      ...
      let expectedFileContents = "Title,Completednsave a CSV,falsen";
      ...
      

      Nous utilisons readFileSync() du module fs pour lire le fichier de manière synchrone :

      todos/index.test.js

      ...
      let content = fs.readFileSync("todos.csv").toString();
      ...
      

      Nous fournissons maintenant à readFileSync() le bon chemin pour le fichier : todos.csv. Comme readFileSync() renvoie un objet Buffer, qui stocke des données binaires, nous utilisons sa méthode toString() afin de pouvoir comparer sa valeur avec la chaîne que nous nous attendons à avoir sauvegardée.

      Comme auparavant, nous utilisons le module strictEqual d’assert pour faire une comparaison :

      todos/index.test.js

      ...
      assert.strictEqual(content, expectedFileContents);
      ...
      

      Nous terminons notre test en appelant le rappel done(), nous assurant ainsi que Mocha sait qu’il doit arrêter de tester ce cas :

      todos/index.test.js

      ...
      done(err);
      ...
      

      Nous fournissons l’objet err à done() pour que Mocha puisse indiquer faire échouer le test dans le cas où une erreur se produirait.

      Enregistrez et quittez index.test.js.

      Exécutons ce test avec npm test comme auparavant. Votre console affichera cette sortie :

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO 3 passing (15ms)

      Vous avez désormais testé votre première fonction asynchrone avec Mocha en utilisant des rappels. Mais au moment de la rédaction de ce tutoriel, les promesses sont plus fréquentes que les rappels dans le nouveau code Node.js, comme expliqué dans notre article Comment écrire du code asynchrone dans Node.js. Maintenant, apprenons à les tester avec Mocha également.

      Promesses

      Une promesse ou Promise est un objet JavaScript qui renverra finalement une valeur. Lorsqu’une promesse est réussie, elle est résolue. Lorsqu’elle rencontre une erreur, elle est rejetée.

      Modifions la fonction saveToFile() pour qu’elle utilise des promesses au lieu de rappels. Ouvrez index.js :

      Tout d’abord, nous devons modifier la façon dont le module fs est chargé. Dans votre fichier index.js, modifiez l’instruction require() en haut du fichier pour qu’elle ressemble à ceci :

      todos/index.js

      ...
      const fs = require('fs').promises;
      ...
      

      Nous venons d’importer le module fs qui utilise les promesses au lieu des rappels. Maintenant, nous devons apporter quelques modifications à la fonction saveToFile() pour qu’elle fonctionne avec les promesses.

      Dans votre éditeur de texte, apportez les modifications suivantes à la fonction saveToFile() pour supprimer les rappels :

      todos/index.js

      ...
      saveToFile() {
          let fileContents = 'Title,Completedn';
          this.todos.forEach((todo) => {
              fileContents += `${todo.title},${todo.completed}n`
          });
      
          return fs.writeFile('todos.csv', fileContents);
      }
      ...
      

      La première différence est que notre fonction n’accepte plus aucun argument. Avec les promesses, nous n’avons pas besoin d’une fonction de rappel. Le deuxième changement concerne la manière dont le fichier est rédigé. Nous retournons maintenant le résultat de la promesse writeFile().

      Enregistrez et fermez index.js.

      Nous allons maintenant adapter notre test pour qu’il fonctionne avec les promesses. Ouvrez index.test.js :

      Modifiez ainsi le test saveToFile() :

      todos/index.js

      ...
      describe("saveToFile()", function() {
          it("should save a single TODO", function() {
              let todos = new Todos();
              todos.add("save a CSV");
              return todos.saveToFile().then(() => {
                  assert.strictEqual(fs.existsSync('todos.csv'), true);
                  let expectedFileContents = "Title,Completednsave a CSV,falsen";
                  let content = fs.readFileSync("todos.csv").toString();
                  assert.strictEqual(content, expectedFileContents);
              });
          });
      });
      

      La première modification que nous devons apporter consiste à supprimer le rappel done() de ses arguments. Si Mocha passe l’argument done(), il doit être appelé ou il lancera une erreur comme celle-ci :

      1) saveToFile()
             should save a single TODO:
           Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/home/ubuntu/todos/index.test.js)
            at listOnTimeout (internal/timers.js:536:17)
            at processTimers (internal/timers.js:480:7)
      

      Lorsque vous testez les promesses, n’incluez pas le rappel done() à it().

      Pour tester notre promesse, nous devons mettre notre code d’assertion dans la fonction then(). Notez que nous retournons cette promesse dans le test, et que nous n’avons pas de fonction catch() à attraper lorsque la Promise est rejetée.

      Nous retournons la promesse de manière à ce que toutes les erreurs qui sont lancées dans la fonction then() soient reportées dans la fonction it(). Si les erreurs ne ressortent pas, Mocha ne fera pas échouer le cas type. Lorsque vous testez des promesses, vous devez utiliser return sur la Promise testée. Sinon, vous courez le risque d’obtenir un faux-positif.

      Nous omettons également la clause catch() car Mocha peut détecter quand une promesse est rejetée. Si elle est rejetée, Mocha fait automatiquement échouer le test.

      Maintenant que notre test est en place, sauvegardez et quittez le fichier, puis exécutez Mocha avec npm test pour confirmer que nous avons obtenu un résultat positif :

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO 3 passing (18ms)

      Nous avons modifié notre code et nos tests pour utiliser les promesses, et nous savons maintenant avec certitude que cela fonctionne. Mais les modèles asynchrones les plus récents utilisent les mots-clés async/await, de sorte que nous n’avons pas à créer plusieurs fonctions then() pour gérer les résultats. Voyons comment nous pouvons tester avec async/await.

      async/await

      Les mots-clés async/await rendent le travail avec les promesses moins verbeux. Une fois que nous avons défini une fonction comme étant asynchrone avec le mot-clé async, nous pouvons obtenir tout résultat futur dans cette fonction avec le mot-clé await. De cette façon, nous pouvons utiliser les promesses sans avoir à utiliser les fonctions then() ou catch().

      Nous pouvons simplifier notre test saveToFile() basé sur une promesse avec async/await. Dans votre éditeur de texte, effectuez ces modifications mineures au test saveToFile() dans index.test.js :

      todos/index.test.js

      ...
      describe("saveToFile()", function() {
          it("should save a single TODO", async function() {
              let todos = new Todos();
              todos.add("save a CSV");
              await todos.saveToFile();
      
              assert.strictEqual(fs.existsSync('todos.csv'), true);
              let expectedFileContents = "Title,Completednsave a CSV,falsen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      });
      

      Le premier changement est que la fonction utilisée par la fonction it() a maintenant le mot-clé async lorsqu’elle est définie. Cela nous permet d’utiliser le mot-clé await dans son corps.

      Le second changement apparait lorsque nous appelons saveToFile(). Le mot-clé await est utilisé avant d’appeler cette fonction. Maintenant, Node.js sait qu’il faut attendre que cette fonction soit résolue avant de poursuivre le test.

      Notre code de fonction est plus facile à lire maintenant que nous avons déplacé le code qui était dans la fonction then() vers le corps de la fonction it(). L’exécution de ce code avec npm test produit cette sortie :

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO 3 passing (30ms)

      Nous pouvons désormais tester les fonctions asynchrones en utilisant l’un des trois paradigmes asynchrones de manière appropriée.

      Nous avons déjà couvert beaucoup de sujets en testant le code synchrone et asynchrone avec Mocha. Découvrons maintenant d’un peu plus près d’autres fonctionnalités offertes par Mocha pour améliorer notre expérience de test, en particulier la façon dont les hooks peuvent changer les environnements de test.

      Étape 5 — Utilisation des hooks pour améliorer les cas de test

      Les hooks sont une fonctionnalité utile de Mocha qui nous permet de configurer l’environnement avant et après un test. Nous ajoutons généralement des hooks dans un bloc de fonction describe(), car ils contiennent une logique de configuration et de démontage spécifique à certains cas de test.

      Mocha fournit quatre hooks que nous pouvons utiliser dans nos tests :

      • before : ce hook est exécuté avant que le premier test commence.
      • beforeEach : ce hook est exécuté avant chaque cas de test.
      • after : ce hook est exécuté après que le dernier cas de test est terminé.
      • afterEach : ce hook est exécuté après chaque cas de test.

      Lorsque nous testons une fonction ou une fonctionnalité plusieurs fois, les hooks sont utiles, car ils nous permettent de séparer le code de configuration du test (comme la création de l’objet todos) du code d’assertion du test.

      Pour évaluer la valeur des hooks, ajoutons d’autres tests à notre bloc de test saveToFile().

      Bien que nous ayons confirmé que nous pouvons enregistrer nos éléments TODO dans un fichier, nous n’en avons enregistré qu’un seul. En outre, l’élément n’a pas été marqué comme étant terminé. Ajoutons d’autres tests pour nous assurer que les différents aspects de notre module fonctionnent.

      Tout d’abord, ajoutons un second test pour confirmer que notre fichier est correctement enregistré lorsque nous avons un élément TODO terminé. Ouvrez le fichier index.test.js dans votre éditeur de texte :

      Remplacez le dernier test par le suivant :

      todos/index.test.js

      ...
      describe("saveToFile()", function () {
          it("should save a single TODO", async function () {
              let todos = new Todos();
              todos.add("save a CSV");
              await todos.saveToFile();
      
              assert.strictEqual(fs.existsSync('todos.csv'), true);
              let expectedFileContents = "Title,Completednsave a CSV,falsen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      
          it("should save a single TODO that's completed", async function () {
              let todos = new Todos();
              todos.add("save a CSV");
              todos.complete("save a CSV");
              await todos.saveToFile();
      
              assert.strictEqual(fs.existsSync('todos.csv'), true);
              let expectedFileContents = "Title,Completednsave a CSV,truen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      });
      

      Le test est semblable à celui que nous avions auparavant. Les principales différences sont que nous appelons la fonction complete() avant d’appeler saveToFile(), et que nos expectedFileContents ont maintenant la valeur true au lieu de false pour colonne completed.

      Enregistrez et quittez le fichier.

      Exécutons notre nouveau test, et tous les autres, avec npm test :

      Cela donnera le résultat :

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO ✓ should save a single TODO that's completed 4 passing (26ms)

      Il fonctionne comme prévu. Il est toutefois possible de faire mieux. Ils doivent tous deux instancier un objet Todos au début du test. Au fur et à mesure que nous ajoutons des cas de test, cela devient rapidement répétitif et gourmand en mémoire. De plus, à chaque fois que nous effectuons le test, il crée un fichier. Cela peut être confondu avec une sortie réelle par une personne moins familière avec le module. Ce serait bien que nous nettoyions nos fichiers de sortie après le test.

      Faisons ces améliorations en utilisant des hooks de test. Nous utiliserons le hook beforeEach() pour configurer notre fixture de test des objets TODO. Une fixture de test est tout état cohérent utilisé dans un test. Dans notre cas, notre fixture de test est un nouvel objet todos auquel un élément TODO a déjà été ajouté. Nous utiliserons ensuite afterEach() pour supprimer le fichier créé par le test.

      Dans index.test.js, apportez les changements suivants à votre dernier test pour saveToFile() :

      todos/index.test.js

      ...
      describe("saveToFile()", function () {
          beforeEach(function () {
              this.todos = new Todos();
              this.todos.add("save a CSV");
          });
      
          afterEach(function () {
              if (fs.existsSync("todos.csv")) {
                  fs.unlinkSync("todos.csv");
              }
          });
      
          it("should save a single TODO without error", async function () {
              await this.todos.saveToFile();
      
              assert.strictEqual(fs.existsSync("todos.csv"), true);
              let expectedFileContents = "Title,Completednsave a CSV,falsen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      
          it("should save a single TODO that's completed", async function () {
              this.todos.complete("save a CSV");
              await this.todos.saveToFile();
      
              assert.strictEqual(fs.existsSync('todos.csv'), true);
              let expectedFileContents = "Title,Completednsave a CSV,truen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      });
      

      Décomposons tous les changements que nous avons apportés. Nous avons ajouté un bloc beforeEach() au bloc de test :

      todos/index.test.js

      ...
      beforeEach(function () {
          this.todos = new Todos();
          this.todos.add("save a CSV");
      });
      ...
      

      Ces deux lignes de code créent un nouvel objet Todos qui sera disponible dans chacun de nos tests. Avec Mocha, l’objet this dans beforeEach() fait référence au mêmeobjet this dans it(). this est similaire pour chaque bloc de code à l’intérieur du bloc describe(). Pour plus d’informations sur this, consultez notre tutoriel Comprendre This, Bind, Call et Apply en JavaScript.

      Ce puissant partage de contexte est la raison pour laquelle nous pouvons rapidement créer des fixtures de test qui fonctionnent pour nos deux tests.

      Nous nettoyons ensuite notre fichier CSV dans la fonction afterEach() :

      todos/index.test.js

      ...
      afterEach(function () {
          if (fs.existsSync("todos.csv")) {
              fs.unlinkSync("todos.csv");
          }
      });
      ...
      

      Si notre test a échoué, il se peut qu’il n’ait pas créé de fichier. C’est pourquoi nous vérifions si le fichier existe avant d’utiliser la fonction unlinkSync() pour le supprimer.

      Les modifications restantes font passer la référence de todos, qui était précédemment créée dans la fonction it(), à this.todos qui est disponible dans le contexte Mocha. Nous avons également supprimé les lignes qui instanciaient auparavant todos dans les cas de test individuels.

      Maintenant, exécutons ce fichier pour confirmer que nos tests fonctionnent toujours. Entrez npm test dans votre terminal pour obtenir :

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO without error ✓ should save a single TODO that's completed 4 passing (20ms)

      Les résultats sont les mêmes, et en outre, nous avons légèrement réduit le temps de préparation des nouveaux tests pour la fonction saveToFile() et trouvé une solution au problème du fichier CSV résiduel.

      Conclusion

      Dans ce tutoriel, vous avez écrit un module Node.js pour gérer les éléments TODO et vous avez testé le code manuellement à l’aide du REPL Node.js. Vous avez ensuite créé un fichier de test et utilisé le framework Mocha pour effectuer des tests automatisés. Avec le module assert, vous avez pu vérifier que votre code fonctionne. Vous avez également testé des fonctions synchrones et asynchrones avec Mocha. Enfin, vous avez créé des hooks avec Mocha qui rendent l’écriture de plusieurs cas de test liés beaucoup plus lisible et facile à mettre à jour.

      Muni de ces compétences, mettez-vous au défi d’écrire des tests pour les nouveaux modules Node.js que vous créez. Pouvez-vous réfléchir aux entrées et sorties de votre fonction et écrire votre test avant d’écrire votre code ?

      Si vous souhaitez obtenir plus d’informations sur le framework de test Mocha, consultez la documentation officielle Mocha. Si vous souhaitez continuer à apprendre le fonctionnement de Node.js, vous pouvez retourner à la page de la série Comment coder en Node.js.



      Source link

      Comment ajouter Sidekiq et Redis à une application Ruby on Rails


      Introduction

      Lors du développement d’une application Ruby on Rails, il se peut que certaines tâches d’application doivent être exécutées de manière asynchrone. Le traitement des données, l’envoi d’e-mails par lots ou l’interaction avec des API externes sont autant d’exemples de travaux qui peuvent être effectués de manière asynchrone avec des travaux en arrière-plan. L’utilisation de travaux en arrière-plan peut améliorer les performances de votre application en déchargeant des tâches potentiellement longues dans une file d’attente de traitement en arrière-plan, libérant ainsi le cycle original de requête/réponse.

      Sidekiq est l’un des frameworks de travail en arrière-plan les plus utilisés que vous pouvez implémenter dans une application Rails. Il est soutenu par Redis, un magasin clé-valeur en mémoire connu pour sa flexibilité et ses performances. Sidekiq utilise Redis comme un magasin de gestion de travaux pour traiter des milliers de travaux par seconde.

      Dans ce tutoriel, vous allez ajouter Redis et Sidekiq à une application Rails existante. Vous allez créer un ensemble de classes de travailleurs Sidekiq et de méthodes à manipuler :

      • Un téléchargement par lots d’informations sur les requins menacés dans la base de données de l’application à partir d’un fichier CSV dans le référentiel du projet.
      • La suppression de ces données.

      Lorsque vous aurez terminé, vous aurez une application de démonstration qui utilise les travailleurs et les travaux pour traiter les tâches de manière asynchrone. Ce sera une bonne base pour vous permettre d’ajouter des travailleurs et des travaux à votre propre application, en utilisant ce tutoriel comme point de départ.

      Conditions préalables

      Pour suivre ce tutoriel, vous aurez besoin de :

      Étape 1 – Clonage du projet et installation des dépendances

      Notre première étape consistera à cloner le référentiel rails-bootstrap à partir du compte GitHub de DigitalOcean Community. Ce référentiel comprend le code de la configuration décrite dans le tutoriel Comment ajouter Bootstrap à une application Ruby on Rails, qui explique comment ajouter Bootstrap à un projet Rails 5 existant.

      Clonez le référentiel dans un répertoire appelé rails-sidekiq :

      • git clone https://github.com/do-community/rails-bootstrap.git rails-sidekiq

      Naviguez vers le répertoire rails-sidekiq :

      Pour travailler avec le code, vous devrez d’abord installer les dépendances du projet, qui sont listées dans son Gemfile. Vous devrez également ajouter le gem sidekiq au projet pour travailler avec Sidekiq et Redis.

      Ouvrez le Gemfile du projet pour le modifier en utilisant nano ou votre éditeur préféré :

      Ajoutez le gem n’importe où dans les dépendances principales du projet (au-dessus des dépendances de développement) :

      ~/rails-sidekiq/Gemfile

      . . .
      # Reduces boot times through caching; required in config/boot.rb
      gem 'bootsnap', '>= 1.1.0', require: false
      gem 'sidekiq', '~>6.0.0'
      
      group :development, :test do
      . . .
      

      Enregistrez et fermez le fichier lorsque vous avez terminé d’ajouter le gem.

      Utilisez la commande suivante pour installer les gems :

      Vous verrez dans la sortie que le gem redis est également installé, cela étant exigé pour sidekiq.

      Ensuite, vous allez installer vos dépendances Yarn. Étant donné que ce projet Rails 5 a été modifié pour servir des ressources avec webpack, ses dépendances JavaScript sont maintenant gérées par Yarn. Cela signifie qu’il est nécessaire d’installer et de vérifier les dépendances listées dans le fichier package.json.

      Exécutez yarn install pour installer ces dépendances :

      Ensuite, exécutez vos migrations de base de données :

      Une fois vos migrations terminées, vous pouvez tester l’application pour vous assurer qu’elle fonctionne comme prévu. Démarrez votre serveur dans le contexte de votre bundle local avec la commande suivante si vous travaillez localement :

      Si vous travaillez sur un serveur de développement, vous pouvez démarrer l’application avec :

      • bundle exec rails s --binding=your_server_ip

      Naviguez vers localhost:3000 ou http://your_server_ip:3000. Vous verrez la page d’accueil suivante :

      Page d'accueil de l'application

      Pour créer un requin, cliquez sur le bouton Get Shark Info (Obtenir des informations sur les requins), qui vous amènera à la route sharks/index :

      Route sharks/index

      Pour vérifier que l’application fonctionne, nous pouvons y ajouter quelques informations de démonstration. Cliquez sur New Shark (Nouveau Requin). Un nom d’utilisateur (sammy) et un mot de passe (shark) vous seront demandés, grâce aux paramètres d’authentification du projet.

      Dans la page New Shark (Nouveau Requin), entrez « Great White » (Grand Blanc) dans le champ Name (Nom) et « Scary » (Effrayant) dans le champ Facts (Faits) :

      Créer un requin

      Cliquez sur le bouton Create Shark (Créer le requin) pour créer le requin. Une fois que vous voyez que votre requin a été créé, vous pouvez tuer le serveur avec CTRL+C.

      Vous avez désormais installé les dépendances nécessaires pour votre projet et testé sa fonctionnalité. Ensuite, vous pouvez apporter quelques modifications à l’application Rails pour travailler avec vos ressources concernant les requins menacés.

      Étape 2 – Génération d’un contrôleur pour les ressources concernant les requins menacés

      Pour travailler avec nos ressources sur les requins menacés, nous allons ajouter un nouveau modèle à l’application ainsi qu’un contrôleur qui contrôlera la manière dont les informations sur les requins menacés sont présentées aux utilisateurs. Notre objectif ultime est de permettre aux utilisateurs de télécharger vers le serveur un grand nombre d’informations sur les requins menacés sans bloquer la fonctionnalité générale de notre application, et de supprimer ces informations lorsqu’ils n’en ont plus besoin.

      D’abord, créons un modèle Endangered (Menacés) pour nos requins menacés. Nous allons inclure un champ de chaîne dans notre table de base de données pour le nom du requin, et un autre champ de chaîne pour les catégories de l’Union internationale pour la conservation de la nature (UICN) qui déterminent le degré de risque pour chaque requin.

      En fin de compte, la structure de notre modèle correspondra aux colonnes du fichier CSV que nous utiliserons pour créer notre téléchargement par lots. Ce fichier est situé dans le répertoire db, et vous pouvez vérifier son contenu avec la commande suivante :

      Le fichier contient une liste de 73 requins en voie de disparition et leurs statuts : vu pour vulnérables, en pour menacés et cr pour en voie de disparition.

      Notre modèle Endangered sera en corrélation avec ces données, ce qui nous permettra de créer de nouvelles instances Endangered à partir de ce fichier CSV. Créez le modèle avec la commande suivante :

      • rails generate model Endangered name:string iucn:string

      Ensuite, générez un contrôleur Endangered avec une action index :

      • rails generate controller endangered index

      Cela nous donnera un point de départ pour développer les fonctionnalités de notre application, mais nous devrons également ajouter des méthodes personnalisées au fichier de contrôleur que Rails a généré pour nous.

      Maintenant, ouvrez ce fichier :

      • nano app/controllers/endangered_controller.rb

      Rails nous a fourni un schéma de base que nous pouvons commencer à remplir.

      Tout d’abord, nous devons déterminer les routes nécessaires pour travailler avec nos données. Grâce à la commande generate controller, nous disposons d’une méthode index pour commencer. Cela correspondra à une vue index, où nous présenterons aux utilisateurs la possibilité de télécharger vers le serveur des requins menacés.

      Toutefois, nous voudrons également traiter les cas où les utilisateurs ont déjà téléchargé les requins et n’ont donc pas besoin d’une option de téléchargement. Nous devrons d’une manière ou d’une autre évaluer combien d’instances de la classe Endangered existent déjà, puisque plus d’une instance indique que le téléchargement par lots a déjà eu lieu.

      Commençons par créer une méthode set_endangered private qui récupérera chaque instance de notre classe Endangered dans la base de données. Ajoutez le code suivant au fichier :

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      class EndangeredController < ApplicationController
        before_action :set_endangered, only: [:index, :data]
      
        def index
        end
      
        private
      
          def set_endangered
            @endangered = Endangered.all
          end
      
      end
      

      Notez que le filtre before_action fera en sorte que la valeur de @endangered ne soit définie que pour les routes index et data, qui seront les endroits où nous traiterons les données sur les requins menacés.

      Ensuite, ajoutez le code suivant à la méthode index afin de déterminer le chemin correct pour les utilisateurs qui visitent cette partie de l’application :

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      class EndangeredController < ApplicationController
        before_action :set_endangered, only: [:index, :data]
      
        def index          
          if @endangered.length > 0
            redirect_to endangered_data_path
          else
            render 'index'
          end
        end
      . . .
      

      S’il y a plus de 0 instance de notre classe Endangered, nous redirigerons les utilisateurs vers la route data, où ils pourront consulter des informations sur les requins qu’ils ont créés. Sinon, ils verront la vue index.

      Ensuite, sous la méthode index, ajoutez une méthode data, qui correspondra à une vue data :

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      . . .
        def index          
          if @endangered.length > 0
            redirect_to endangered_data_path
          else
            render 'index'
          end
        end
      
        def data
        end
      . . .
      

      Ensuite, nous ajouterons une méthode pour gérer le téléchargement des données lui-même. Nous appellerons cette méthode upload, et elle fera appel à une classe de travailleurs Sidekiq et à une méthode pour effectuer le téléchargement des données à partir du fichier CSV. Nous allons créer la définition de cette classe de travailleurs, AddEndangeredWorker, dans la prochaine étape.

      Pour l’instant, ajoutez le code suivant au fichier pour appeler le travailleur Sidekiq afin d’effectuer le téléchargement :

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      . . .
        def data
        end
      
        def upload
          csv_file = File.join Rails.root, 'db', 'sharks.csv'   
          AddEndangeredWorker.perform_async(csv_file)
          redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
        end
      . . .
      

      En appelant la méthode perform_async sur la classe AddEndangeredWorker et en utilisant le fichier CSV comme argument, ce code garantit que les données du requin et le travail de téléchargement sont transmis à Redis. Les travailleurs de Sidekiq que nous mettrons en place surveilleront la file d’attente des travaux et réagiront lorsque de nouveaux travaux se présenteront.

      Après avoir appelé perform_async, notre méthode upload redirige vers le chemin data, où les utilisateurs pourront voir les requins téléchargés.

      Ensuite, nous ajouterons une méthode destroy pour détruire les données. Ajoutez le code suivant sous la méthode upload :

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      . . .
        def upload
          csv_file = File.join Rails.root, 'db', 'sharks.csv'   
          AddEndangeredWorker.perform_async(csv_file)
          redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
        end
      
        def destroy
          RemoveEndangeredWorker.perform_async
          redirect_to root_path
        end
      . . .
      

      Comme notre méthode upload, notre méthode destroy comprend un appel à perform_async sur une classe RemoveEndangeredWorker – l’autre travailleur Sidekiq que nous allons créer. Après avoir appelé cette méthode, elle redirige les utilisateurs vers le chemin root de l’application.

      Le fichier terminé ressemblera à ceci :

      ~/rails-sidekiq/app/controllers/endangered_controller.rb

      class EndangeredController < ApplicationController
        before_action :set_endangered, only: [:index, :data]
      
        def index          
          if @endangered.length > 0
            redirect_to endangered_data_path
          else
            render 'index'
          end
        end
      
        def data
        end
      
        def upload
          csv_file = File.join Rails.root, 'db', 'sharks.csv'   
          AddEndangeredWorker.perform_async(csv_file)
          redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
        end
      
        def destroy
          RemoveEndangeredWorker.perform_async
          redirect_to root_path
        end
      
        private
      
          def set_endangered
            @endangered = Endangered.all
          end
      
      end
      

      Enregistrez et fermez le fichier lorsque vous avez terminé de le modifier.

      Pour terminer la consolidation des routes de notre application, nous allons modifier le code dans config/routes.rb, le fichier où se trouvent nos déclarations de routes.

      Maintenant, ouvrez ce fichier :

      Actuellement, le fichier ressemble à ceci :

      ~/rails-sidekiq/config/routes.rb

      Rails.application.routes.draw do
        get 'endangered/index'
        get 'home/index'
        resources :sharks do
                resources :posts
        end
        root 'home#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      Nous devrons mettre à jour le fichier pour inclure les routes que nous avons définies dans notre contrôleur : data, upload et destroy. Notre route data correspondra à une requête GET pour récupérer les données sur les requins, tandis que nos itinéraires upload et destroy redirigeront vers des requêtes POST qui téléchargeront et détruiront ces données.

      Ajoutez le code suivant au fichier pour définir ces routes :

      ~/rails-sidekiq/config/routes.rb

      Rails.application.routes.draw do
        get 'endangered/index'
        get 'endangered/data', to: 'endangered#data'
        post 'endangered/upload', to: 'endangered#upload'
        post 'endangered/destroy', to: 'endangered#destroy'
        get 'home/index'
        resources :sharks do
                resources :posts
        end
        root 'home#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      Enregistrez et fermez le fichier lorsque vous avez terminé de le modifier.

      Maintenant que vous avez mis en place votre modèle Endangered et votre contrôleur, vous pouvez passer à l’étape de définition des classes de travailleurs Sidekiq.

      Étape 3 – Définition des travailleurs Sidekiq

      Nous avons appelé des méthodes perform_async sur nos travailleurs Sidekiq dans notre contrôleur, mais nous devons encore créer les travailleurs eux-mêmes.

      Tout d’abord, créez un répertoire workers pour les travailleurs :

      Ouvrez un fichier pour le travailleur AddEndangeredWorker :

      • nano app/workers/add_endangered_worker.rb

      Dans ce fichier, nous ajouterons un code qui nous permettra de travailler avec les données dans notre fichier CSV. Premièrement, ajoutez du code au fichier qui créera la classe, incluez la bibliothèque CSV Ruby et assurez-vous que cette classe fonctionne comme un travailleur Sidekiq :

      ~/rails-sidekiq/app/workers/add_endangered_worker.rb

      class AddEndangeredWorker
        require 'csv'
        include Sidekiq::Worker
        sidekiq_options retry: false
      
      end
      

      Nous incluons également l’option retry: false pour nous assurer que Sidekiq ne réessaie pas de relancer le téléchargement si celui-ci échoue.

      Ensuite, ajoutez le code pour la fonction perform :

      ~/rails-sidekiq/app/workers/add_endangered_worker.rb

      class AddEndangeredWorker
        require 'csv'
        include Sidekiq::Worker
        sidekiq_options retry: false
      
        def perform(csv_file)
          CSV.foreach(csv_file, headers: true) do |shark|
          Endangered.create(name: shark[0], iucn: shark[1])
        end
       end
      
      end
      

      La méthode perform reçoit des arguments de la méthode perform_async définie dans le contrôleur, il est donc important que les valeurs d’argument correspondent. Ici, nous transmettons csv_file, la variable que nous avons définie dans le contrôleur, et nous utilisons la méthode foreach à partir de la bibliothèque CSV pour lire les valeurs dans le fichier. Définir headers:true pour cette boucle vous garantit que la première ligne du fichier sera traitée comme une ligne d’en-têtes.

      Le bloc lit ensuite les valeurs du fichier dans les colonnes que nous avons définies pour notre modèle Endangered : name et iucn. Exécuter cette boucle créera des instances Endangered pour chacune des entrées dans notre fichier CSV.

      Une fois que vous avez terminé de le modifier, enregistrez et fermez le fichier.

      Ensuite, nous allons créer un travailleur pour gérer la suppression de ces données. Ouvrez un fichier pour la classe RemoveEndangeredWorker :

      • nano app/workers/remove_endangered_worker.rb

      Ajoutez le code pour définir la classe, et pour vous assurer qu’il utilise la bibliothèque CSV et fonctionne comme un travailler Sidekiq :

      ~/rails-sidekiq/app/workers/remove_endangered_worker.rb

      class RemoveEndangeredWorker
        include Sidekiq::Worker
        sidekiq_options retry: false
      
      end
      

      Ensuite, ajoutez une méthode perform pour gérer la destruction des données sur les requins menacés :

      ~/rails-sidekiq/app/workers/remove_endangered_worker.rb

      class RemoveEndangeredWorker
        include Sidekiq::Worker
        sidekiq_options retry: false
      
        def perform
          Endangered.destroy_all
        end
      
      end
      

      La méthode perform appelle destroy_all sur la classe Endangered, elle supprimera toutes les instances de cette classe de la base de données.

      Enregistrez et fermez le fichier lorsque vous avez terminé de le modifier.

      Une fois vos travailleurs en place, vous pouvez passer à la création d’une mise en page pour vos vues endangered, et de modèles pour vos vues index et data, afin que les utilisateurs puissent télécharger et visualiser les requins menacés.

      Étape 4 – Ajout de mises en page et de modèles de vues

      Pour que les utilisateurs puissent profiter de leurs informations sur les requins menacés, nous devrons nous occuper de deux choses : la présentation des vues définies dans notre contrôleur endangered, et les modèles de vue pour les vues index et data.

      Actuellement, notre application utilise une mise en page pour l’ensemble de l’application, située à l’adresse app/views/layouts/application.html.erb, un fichier partiel pour la navigation et une mise en page pour les vues sharks. La mise en page de l’application vérifie la présence d’un bloc de contenu, ce qui nous permet de charger différentes mises en page en fonction de la partie de l’application à laquelle notre utilisateur s’intéresse : pour la page home index, il verra une mise en page, et pour toute vue relative à un requin en particulier, il en verra une autre.

      Nous pouvons réadapter la mise en page sharks à nos vues endangered, car ce format permettra également de présenter les données sur les requins en vrac.

      Copiez le fichier de configuration sharks pour créer une mise en page endangered :

      • cp app/views/layouts/sharks.html.erb app/views/layouts/endangered.html.erb

      Ensuite, nous allons travailler à créer les modèles de vue pour nos vues index et data.

      Ouvrez le modèle index en premier :

      • nano app/views/endangered/index.html.erb

      Supprimez le code standard et ajoutez à la place le code suivant, qui donnera aux utilisateurs des informations générales sur les catégories menacées et leur offrira la possibilité de télécharger des informations sur les requins menacés :

      ~/rails-sidekiq/app/views/endangered/index.html.erb

      <p id="notice"><%= notice %></p>
      
      <h1>Endangered Sharks</h1>
      
      <p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
      
      <br>
      
        <%= form_tag endangered_upload_path do %>
        <%= submit_tag "Import Endangered Sharks" %>
        <% end %>
      
        <br>
      
      <%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
      

      Un form_tag rend possible le téléchargement de données en pointant une action post sur endangered_upload_path – la route que nous avons définie pour nos téléchargements. Un bouton créé avec submit_tag invite les utilisateurs à "Import Endangered Sharks" (Importer les requins menacés).

      En plus de ce code, nous avons inclus quelques informations générales sur les codes ICUN, afin que les utilisateurs puissent interpréter les données qu’ils verront.

      Enregistrez et fermez le fichier lorsque vous avez terminé de le modifier.

      Ensuite, ouvrez un fichier pour la vue data :

      • nano app/views/endangered/data.html.erb

      Ajoutez le code suivant, qui ajoutera un tableau avec les données sur les requins menacés :

      ~/rails-sidekiq/app/views/endangered/data.html.erb

      <p id="notice"><%= notice %></p>
      
      <h1>Endangered Sharks</h1>
      
      <p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
      
      <div class="table-responsive">
      <table class="table table-striped table-dark">
        <thead>
          <tr>
            <th>Name</th>
            <th>IUCN Status</th>
            <th colspan="3"></th>
          </tr>
        </thead>
      
        <tbody>
          <% @endangered.each do |shark| %>
            <tr>
              <td><%= shark.name %></td>
              <td><%= shark.iucn %></td>
            </tr>
          <% end %>
        </tbody>
      </table>
      </div>
      
      <br>
      
        <%= form_tag endangered_destroy_path do %>
        <%= submit_tag "Delete Endangered Sharks" %>
        <% end %>
      
        <br>
      
      <%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
      

      Ce code comprend les codes de statut ICUN, une fois de plus, et un tableau Bootstrap pour les données produites. En bouclant notre variable @endangered, nous affichons le nom et le statut ICUN de chaque requin dans le tableau.

      Sous le tableau, nous avons un autre ensemble de form_tags et de submit_tags, qui s’affichent sur le chemin destroy en offrant aux utilisateurs la possibilité de "Delete Endangered Sharks" (Supprimer les requins menacés).

      Enregistrez et fermez le fichier lorsque vous avez terminé de le modifier.

      La dernière modification que nous apporterons à nos vues sera dans la vue index associée à notre contrôleur home. Vous vous souvenez peut-être que cette vue est définie comme la racine de l’application dans le fichier config/routes.rb.

      Ouvrez ce fichier pour le modifier :

      • nano app/views/home/index.html.erb

      Trouvez la colonne dans la ligne qui indique Sharks are ancient (les requins sont anciens) :

      ~/rails-sidekiq/app/views/home/index.html.erb

      . . .
              <div class="col-lg-6">
                  <h3>Sharks are ancient</h3>
                  <p>There is evidence to suggest that sharks lived up to 400 million years ago.
                  </p>
              </div>
          </div>
      </div>
      

      Ajoutez le code suivant au fichier :

      ~/rails-sidekiq/app/views/home/index.html.erb

      . . .
              <div class="col-lg-6">
                  <h3>Sharks are ancient and SOME are in danger</h3>
                  <p>There is evidence to suggest that sharks lived up to 400 million years ago. Without our help, some could disappear soon.</p>
                  <p><%= button_to 'Which Sharks Are in Danger?', endangered_index_path, :method => :get,  :class => "btn btn-primary btn-sm"%>
                  </p>
              </div>
          </div>
      </div>
      

      Nous avons inclus un appel à l’action pour que les utilisateurs en apprennent davantage sur les requins menacés, en partageant d’abord un message fort, puis en ajoutant un assistant button_to qui soumet une requête GET à notre route endangered index, donnant aux utilisateurs l’accès à cette partie de l’application. À partir de là, ils pourront télécharger et visualiser les informations sur les requins menacés.

      Enregistrez et fermez le fichier lorsque vous avez terminé de le modifier.

      Avec votre code en place, vous êtes prêt à démarrer l’application et à télécharger quelques requins vers le serveur !

      Étape 5 – Démarrage de Sidekiq et test de l’application

      Avant de démarrer l’application, nous devrons gérer les migrations sur notre base de données et démarrer Sidekiq pour activer nos travailleurs. Redis devrait déjà être en marche sur le serveur, mais nous pouvons vérifier pour en être sûr. Avec tout cela, nous serons prêts à tester l’application.

      D’abord, vérifiez que Redis fonctionne :

      Vous devriez voir une sortie similaire à la suivante :

      Output

      ● redis-server.service - Advanced key-value store Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2019-11-12 20:37:13 UTC; 1 weeks 0 days ago

      Ensuite, exécutez vos migrations de base de données :

      Vous pouvez maintenant démarrer Sidekiq dans le contexte de notre bundle de projet actuel en utilisant la commande bundle exec sidekiq :

      Vous verrez une sortie comme ceci, vous indiquant que Sidekiq est prêt à traiter les travaux :

      Output

      m, `$b .ss, $$: .,d$ `$$P,d$P' .,md$P"' ,$$$$$b/md$$$P^' .d$$$$$$/$$$P' $$^' `"/$$$' ____ _ _ _ _ $: ,$$: / ___|(_) __| | ___| | _(_) __ _ `b :$$ ___ | |/ _` |/ _ |/ / |/ _` | $$: ___) | | (_| | __/ <| | (_| | $$ |____/|_|__,_|___|_|__|__, | .d$$ |_| 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Running in ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux] 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: See LICENSE and the LGPL-3.0 for licensing details. 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org 2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Booting Sidekiq 6.0.3 with redis options {:id=>"Sidekiq-server-PID-17621", :url=>nil} 2019-11-19T21:43:00.543Z pid=17621 tid=gpiqiesdl INFO: Starting processing, hit Ctrl-C to stop

      Ouvrez une deuxième fenêtre de terminal, naviguez vers le répertoire rails-sidekiq, et démarrez votre serveur d’application.

      Si vous exécutez l’application localement, utilisez la commande suivante :

      Si vous travaillez avec un serveur de développement, exécutez ce qui suit :

      • bundle exec rails s --binding=your_server_ip

      Naviguez vers localhost:3000 ou http://your_server_ip:3000 dans le navigateur. Vous verrez la page d’accueil suivante :

      Accueil Sidekiq App

      Cliquez sur le bouton Which Sharks Are in Danger?​​​​​​ (Quels sont les requins menacés ?) . Comme vous n’avez pas téléchargé de requin menacé, cela vous amènera à la vue endangered index :

      Vue Endangered Index

      Cliquez sur Import Endangered Sharks​​​ (​​Importer des requins menacés) pour importer les requins. Vous verrez un message vous indiquant que les requins ont été importés :

      Commencer l'importation

      Vous verrez également le début de l’importation. Rafraîchissez votre page pour voir le tableau complet :

      Rafraîchir le tableau

      Grâce à Sidekiq, notre important téléchargement par lots de requins menacés a réussi sans verrouiller le navigateur ni interférer avec les autres fonctionnalités de l’application.

      Cliquez sur le bouton Home (Accueil) en bas de la page, ce qui vous ramènera à la page principale de l’application :

      Accueil Sidekiq App

      De là, cliquez à nouveau sur Which Sharks Are in Danger? . Cela vous amènera maintenant directement à la vue data, puisque vous avez déjà téléchargé les requins.

      Pour tester la fonctionnalité de suppression, cliquez sur le bouton Delete Endangered Sharks (Supprimer les requins menacés) sous le tableau. Vous devriez être redirigé vers la page d’accueil de l’application une fois encore. Cliquez sur Which Sharks Are in Danger?​​​​​ une dernière fois vous renverra à la vue index, où vous aurez la possibilité de télécharger à nouveau des requins :

      Vue Endangered Index

      Votre application fonctionne maintenant avec les travailleurs Sidekiq en place, qui sont prêts à traiter les travaux et à s’assurer que les utilisateurs ont une bonne expérience en travaillant avec votre application.

      Conclusion

      Vous disposez désormais d’une application Rails fonctionnelle avec Sidekiq activé, ce qui vous permettra de décharger des opérations coûteuses dans une file d’attente de travail gérée par Sidekiq et soutenue par Redis. Cela vous permettra d’améliorer la vitesse et la fonctionnalité de votre site au fur et à mesure de son développement.

      Si vous souhaitez en savoir plus sur Sidekiq, les documents sont un bon point de départ.

      Pour en savoir plus sur Redis, consultez notre bibliothèque de ressources Redis. Vous pouvez également en savoir plus sur le fonctionnement d’un cluster Redis géré sur DigitalOcean en consultant la documentation du produit.



      Source link