Fermer

mai 13, 2019

Comment créer des scripts JavaScript exécutables Spécifications –


La programmation ne consiste pas seulement à donner à l’ordinateur des instructions sur la manière d’accomplir une tâche, mais aussi à communiquer des idées de manière précise avec d’autres personnes, voire avec votre futur. Une telle communication peut avoir plusieurs objectifs, par exemple partager des informations ou permettre des modifications plus faciles. Il est difficile de changer quelque chose si vous ne le comprenez pas ou si vous ne vous souvenez pas de ce que vous avez fait il y a longtemps. La documentation est essentielle, qu'il s'agisse de simples commentaires dans votre code ou de documents complets décrivant l'ensemble des fonctionnalités d'un programme.

 Fichier de spécification JavaScript simple

Lorsque nous écrivons dans un logiciel, nous devons également nous assurer que le code a le code voulu. fonctionnalité. Bien qu'il existe des méthodes formelles pour définir la sémantique le moyen le plus simple et le plus rapide (mais moins rigoureux) consiste à utiliser cette fonctionnalité pour voir si elle produit les résultats escomptés.

pratiques: la documentation de code sous forme de commentaires expliquant l'objectif d'un bloc de code et une série de tests permettant de s'assurer que les fonctions donnent le résultat souhaité.

Mais, en règle générale, la documentation et les tests s'effectuent en différentes étapes. En unifiant ces pratiques, nous pouvons offrir une meilleure expérience à toute personne impliquée dans le développement d'un projet. Cet article explore une implémentation simple d'un programme permettant d'exécuter des spécifications JavaScript fonctionnant à la fois pour la documentation et pour les tests.

Nous allons créer une interface de ligne de commande permettant de rechercher tous les fichiers de spécification dans un répertoire et d'extraire toutes les assertions trouvées à l'intérieur. chaque spécification et évalue leur résultat, indiquant enfin les résultats des assertions qui ont échoué et celles qui ont abouti.

Le format de spécification

Chaque fichier de spécification exporte une seule chaîne à partir d'un modèle littéral . La première ligne peut être considérée comme le titre de la spécification. Le modèle de modèle nous permettra d'incorporer des expressions JS entre la chaîne et chaque expression représentera une assertion. Pour identifier chaque assertion, nous pouvons commencer la ligne avec un caractère distinctif. Dans ce cas, nous pouvons utiliser la combinaison du caractère de barre ( | ) et d'un tiret ( - ). ressemble à un symbole de tourniquet qui peut parfois être trouvé comme représentation symbolique d'assertions logiques.

Voici un exemple avec son explication:

 const dependency = require ('./ dependency ')

module.exports = `
  Exemple de fichier de spécification

  Ce projet permet de tester des programmes JavaScript à l'aide de fichiers de spécification.
  Chaque fichier * .spec.js exporte un seul modèle de modèle, qui comprend un général
  explication du fichier spécifié. Chaque fichier représente une logique
  composant d'un plus grand système. Chaque composant logique est composé de plusieurs
  unités de fonctionnalité pouvant être testées pour certaines propriétés.
  Chacune de ces unités de fonctionnalité peut avoir un ou plusieurs
  assertions. Chaque assertion est indiquée par une ligne comme suit:

  | - $ {dependency} La dépendance a été chargée et la première assertion a
  été évalué.

  Plusieurs assertions peuvent être faites pour chaque fichier:

  | - $ {false} Cette assertion échouera.

  | - $ {2 + 2 === 4} Cette assertion aboutira.

  La combinaison de | et - formera une ligature de tourniquet (| -) en utilisant le
  Police de caractère. Le code Fira est recommandé. Un symbole de tourniquet a été utilisé par Gottlob Frege
  au début des sentiments étant affirmés comme vrais.

  L'utilisation prévue est pour un logiciel conforme aux spécifications. Où le programmeur
  définit la structure de haut niveau d'un programme en termes de spécification,
  puis construit progressivement les pièces conformes à cette spécification jusqu'à ce que tous
  les tests sont réussis. Un effet secondaire souhaité est d'avoir un moyen simple de générer
  documentation à jour en dehors du code pour les consommateurs d'API.
`

Passons maintenant à la structure de haut niveau de notre programme

La structure de notre programme

Toute la structure de notre programme peut être définie dans quelques lignes de code, sans dépendance autre que deux nœuds. Les bibliothèques .js pour travailler avec le système de fichiers ( fs ) et les chemins de répertoire ( chemin (19459010)). Dans cette section, nous définissons uniquement la structure de notre programme, les définitions de fonctions apparaissent dans les sections suivantes.

 #! / Usr / bin / env node

const fs = require ('fs')
const path = require ('path')

const specRegExp = /.spec.js$/
const target = path.join (process.cwd (), process.argv [2])

// Récupère tous les chemins de fichiers de spécification
// Si un fichier de spécification est fourni, il suffit de tester ce fichier.
// Sinon trouver tous les fichiers de spécification dans le répertoire cible
chemins const = specRegExp.test (cible)
  ? [ target ]
  : findSpecifications (target, specRegExp) .filter (x => x)

// Récupère le contenu de chaque fichier de spécification
// Récupère les assertions de chaque fichier de spécification
const assertionGroups = getAssertions (getSpecifications (chemins))

// enregistre toutes les assertions
logAssertions (assertionGroups)

// Vérifier les assertions ayant échoué et renvoyer un code de sortie approprié
process.exitCode = checkAssertions (assertionGroups)

Etant donné que c'est également le point d'entrée de notre interface de ligne de commande CLI ( ), nous devons ajouter la première ligne, le shebang qui indique que ce fichier doit être exécuté par le programme node . Il n'est pas nécessaire d'ajouter une bibliothèque spécifique pour gérer les options de commande, car nous ne sommes intéressés que par un seul paramètre. Cependant, vous pouvez envisager d'autres options si vous envisagez d'étendre considérablement ce programme.

Pour obtenir le fichier ou le répertoire de test cible, nous devons rejoindre le chemin où la commande a été exécutée (à l'aide de process.cwd () ) avec l'argument fourni par l'utilisateur en tant que premier argument lors de l'exécution de la commande (à l'aide de process.argv [2]). Vous pouvez trouver une référence à ces valeurs dans la documentation Node.js pour l'objet processus . De cette manière, nous obtenons le chemin absolu du répertoire / fichier cible.

Maintenant, la première chose à faire est de trouver tous les fichiers de spécification JavaScript. Comme on le voit à la ligne 12, nous pouvons utiliser l'opérateur conditionnel pour offrir davantage de flexibilité: si l'utilisateur fournit un fichier de spécification comme cible, nous utilisons simplement ce chemin de fichier directement, sinon, si l'utilisateur fournit un répertoire chemin, nous devons trouver tous les fichiers correspondant à notre modèle tel que défini par la constante specRegExp . Pour ce faire, nous utilisons une fonction findSpecifications que nous définirons plus tard. Cette fonction renvoie un tableau de chemins pour chaque fichier de spécification dans le répertoire cible.

La ligne 18 définit la constante assertionGroups comme résultat de la combinaison de deux fonctions getSpecifications () et getAssertions () . Nous obtenons d’abord le contenu de chaque fichier de spécification, puis nous en extrayons les assertions. Nous définirons ces deux fonctions plus tard, pour le moment notons simplement que nous utilisons la sortie de la première fonction comme paramètre de la seconde, simplifiant ainsi la procédure et établissant une connexion directe entre ces deux fonctions. Bien que nous puissions avoir une seule fonction, en les séparant, nous pouvons obtenir un meilleur aperçu du processus réel, mais rappelez-vous qu’un programme doit être clair à comprendre; Il ne suffit pas de le faire fonctionner.

La structure de la constante du groupe d'assertions serait la suivante:

d'assertionGroup [specification] [assertion] [assertion]

Ensuite, nous enregistrons toutes ces assertions à l'utilisateur comme moyen de rapporter les résultats à l'aide de la fonction logAssertions () . Chaque assertion contiendra le résultat ( true ou false ) et une petite description. Nous pouvons utiliser cette information pour donner une couleur spéciale à chaque type de résultat.

Enfin, nous définir le code de sortie en fonction des résultats des assertions. Cela donne au processus des informations sur la fin du programme: le processus a-t-il abouti ou a-t-il échoué? Un code de sortie de 0 signifie que le processus s'est terminé avec succès, ou 1 en cas d'échec ou, dans notre cas, lorsque au moins une assertion a échoué.

] Recherche de tous les fichiers de spécification

Pour rechercher tous les fichiers de spécification JavaScript, nous pouvons utiliser une fonction récursive qui traverse le répertoire indiqué par l'utilisateur en tant que paramètre de la CLI. Pendant la recherche, chaque fichier doit être vérifié avec l'expression régulière que nous avons définie au début du programme ( / . Spec .js $ / ), ce qui correspond à tous les chemins de fichiers se terminant par .spec.js .

 function findSpecifications (dir, matchPattern) {
  retourne fs.readdirSync (dir)
    .map (filePath => path.join (dir, filePath))
    .filter (filePath => matchPattern.test (filePath) && fs.statSync (filePath) .isFile ())
}

Notre fonction findSpecifications prend un répertoire cible ( dir ) et une expression régulière qui identifie le fichier de spécification ( matchPattern ).

. Obtention du contenu

Comme nous exportons des littéraux de modèle, obtenir le contenu et les assertions évaluées est simple, nous devons importer chaque fichier et, lorsqu'il est importé, toutes les assertions sont évaluées automatiquement.

 function getSpecifications (chemins) {
  return paths.map (path => require (path))
}

En utilisant la fonction map () nous remplaçons le chemin du tableau par le contenu du fichier utilisant la fonction du nœud du nœud.

À ce stade, nous avons un tableau. avec le contenu de chaque fichier de spécification et leurs assertions déjà évaluées. Nous utilisons l'indicateur de tourniquet ( | - ) pour rechercher toutes ces assertions et les extraire.

 function getAssertions (specifications) {
  return specifications.map (spécification => ({
    titre: spécification.split (' n  n', 1) [0] .trim (),
    assertions: specification.match (/ ^ (|  t) * ( | -) (. |  n) *?  ./ gm) .map (assertion => {
      const assertionFragments = / (?:  | -) ( w *) ((?:. |  n) *) /. exec (assertion)

      revenir {
        valeur: assertionFragments [1],
        description: assertionFragments [2] .replace (/  n /, '')
      }
    })
  }))
}

Cette fonction renverra un tableau similaire, mais remplacera le contenu de chaque spécification par un objet ayant la structure suivante:

 {
  titre: ,
  affirmations: [
    {
      valeur: ,
      description: 
    }
  ]
}

Le titre est défini avec la première ligne de la chaîne de spécification. Ensuite, chaque assertion est stockée sous forme de tableau dans la clé d'assertions . La valeur représente le résultat de l'affirmation sous la forme d'un booléen . Nous allons utiliser cette valeur pour savoir si l’affirmation a réussi ou non. En outre, la description sera présentée à l'utilisateur comme un moyen d'identifier les assertions réussies et celles ayant échoué. Nous utilisons des expressions régulières dans chaque cas.

Journalisation des résultats

Le tableau que nous avons construit le long du programme contient maintenant une série de fichiers de spécification JavaScript contenant une liste des assertions trouvées, ainsi que leur résultat et description. faire autre chose que de rapporter les résultats à l'utilisateur.

 function logAssertions (assertionGroups) {
  // Méthodes pour consigner du texte avec des couleurs
  const ansiColor = {
    blue: text => console.log (` x1b [1m  x1b [34m $ {text}}  x1b [39m  x1b [22m`),
    green: text => console.log (` x1b [32m ✔ $ {text}  x1b [39m`),
    rouge: texte => console.log (` x1b [31m $ {texte}  x1b [39m`)
  }

  // enregistre les résultats
  assertionGroups.forEach (groupe => {
    ansiColor.blue (group.title)

    group.assertions.forEach (assertion => {
      assertion.value === 'true'
        ? ansiColor.green (assertion.description)
        : ansiColor.red (assertion.description)
    })
  })

  console.log (' n')
}

Nous pouvons formater notre entrée avec des couleurs en fonction des résultats. Pour afficher les couleurs sur le terminal, nous devons ajouter les codes d'échappement ANSI . Pour simplifier leur utilisation dans le bloc suivant, nous avons enregistré chaque couleur en tant que méthode d’un objet ansiColor .

Nous souhaitons tout d’abord afficher le titre de la spécification. Rappelez-vous que nous utilisons la première dimension de la tableau pour chaque spécification, que nous avons nommée groupe (d'assertions.) Ensuite, nous enregistrons toutes les assertions en fonction de leur valeur en utilisant leur couleur respective: vert pour les assertions évaluées comme true et rouge pour les assertions ayant une autre valeur. Notez la comparaison, nous vérifions que true soit une chaîne car nous recevons des chaînes de chaque fichier.

Vérification des résultats

Enfin, la dernière étape consiste à: vérifie si tous les tests ont réussi ou non.

 function checkAssertions (assertionGroups) {
  retour assertionGroups.some (
    group => group.assertions.some (assertion => assertion.value === 'false')
  )? dix
}

Nous vérifions chaque groupe d'assertions (spécifications) pour voir si au moins une valeur est '`false'`' en utilisant la méthode some () de tableau . Nous avons imbriqué deux d'entre elles parce que nous avons un tableau à deux dimensions.

Exécution de notre programme

À ce stade, notre interface de ligne de commande doit être prête à exécuter certaines spécifications JavaScript et à voir si les assertions sont collectées et évaluées. Dans un répertoire test vous pouvez copier l'exemple de spécification depuis le début de cet article et coller la commande suivante dans votre fichier package.json :

 "scripts": {
  "test": "node index.js test"
}

… où test est le nom du répertoire dans lequel vous avez inclus le fichier de spécification d'échantillon.

Lors de l'exécution de la commande npm test vous devriez voir les résultats avec leur couleurs respectives.

Derniers mots

Nous avons mis en place un programme de ligne de commande très simple mais utile qui peut nous aider à concevoir de meilleurs logiciels.

  • Les logiciels peuvent être à la fois simples et utiles
  • Nous pouvons créer nos propres outils si nous voulons quelque chose de différent, il n’ya aucune raison de nous conformer.
  • Le logiciel ne consiste pas seulement à «faire fonctionner», mais aussi à communiquer des idées.
  • Parfois, nous pouvons améliorer quelque chose simplement en modifiant le point de vue. Dans ce cas, le format des fichiers de spécification: une simple chaîne!

Un exemple de flux de travail pour ce programme serait de placer un fichier .spec.js dans vos projets, en le décrivant dans détailler la fonctionnalité et les propriétés prévues que le programme devrait avoir sous la forme d'assertions. Vous pouvez ainsi esquisser l’idée d’un nouveau projet et l’améliorer continuellement jusqu’à ce que toutes les affirmations soient passées.

Vous pouvez trouver le code source utilisé dans cet article ici .




Source link