Maison  >  Article  >  interface Web  >  Une explication détaillée de la façon de créer une spécification JavaScript exécutable

Une explication détaillée de la façon de créer une spécification JavaScript exécutable

青灯夜游
青灯夜游avant
2020-10-23 17:57:181748parcourir

Une explication détaillée de la façon de créer une spécification JavaScript exécutable

La programmation ne consiste pas seulement à donner des instructions à un ordinateur sur la façon d'accomplir une tâche, il s'agit également de communiquer des idées de manière précise à d'autres personnes, même à votre futur moi. Une telle communication peut avoir plusieurs objectifs, par exemple partager des informations ou simplement faciliter la révision, ce qui est difficile à faire si vous ne comprenez pas ou ne vous souvenez pas de ce qui a été fait il y a longtemps.

Lorsque nous écrivons un logiciel, nous devons également nous assurer que le code possède la fonctionnalité prévue. Bien qu'il existe des moyens formels de définir la sémantique, l'approche la plus simple et la plus rapide (mais la moins rigoureuse) consiste à utiliser la fonctionnalité et à voir si elle produit les résultats attendus.

La plupart des développeurs connaissent ces pratiques : une documentation du code sous forme de commentaires pour clarifier l'objectif d'un bloc de code, et une série de tests pour s'assurer que la fonction donne le résultat souhaité.

Mais généralement, la documentation et les tests sont effectués en étapes distinctes. En unifiant ces pratiques, nous pouvons offrir une meilleure expérience à toute personne impliquée dans le développement de projets. Cet article explore l'implémentation d'un programme simple capable d'exécuter une spécification JavaScript adaptée à la fois à la documentation et aux tests.

Nous allons construire une interface de ligne de commande qui recherchera tous les fichiers de spécification dans un répertoire, extraira toutes les assertions trouvées dans chaque spécification et calculera leurs résultats, montrant enfin quelles assertions ont échoué et lesquelles ont échoué.

Format de la spécification

Chaque fichier de spécification exportera une chaîne à partir du texte du modèle. La première ligne peut servir de titre au cahier des charges. Les littéraux de modèle nous permettront d'intégrer des expressions JS entre des chaînes, et chaque expression représentera une assertion. Pour identifier chaque assertion, on peut commencer la ligne par un caractère unique.

Dans ce cas, nous pouvons utiliser une combinaison de barre (|) et de tiret (-). Le tiret est similaire au symbole de la porte tournante, qui peut parfois être utilisé comme symbole pour des assertions logiques.

Voici un exemple avec quelques explications de son utilisation :

const dependency = require('./dependency')module.exports = `
  Example of a Specification File
  
  This project allows to test JavaScript programs using specification files.
  Every *.spec.js file exports a single template literal that includes a general
  explanation of the file being specified. Each file represents a logical
  component of a bigger system. Each logical component is composed of several
  units of functionality that can be tested for certain properties.
  Each one of this units of functionality may have one or more
  assertions. Each assertion is denoted by a line as the following:

  |- ${dependency} The dependency has been loaded and the first assert has
  been evaluated.

  Multiple assertions can be made for each file:

  |- ${false} This assertion will fail.

  |- ${2 + 2 === 4} This assertion will succeed.

  The combination of | and - will form a Turnstile ligature (|-) using the appropriate
  font. Fira Code is recommended. A Turnstile symbol was used by Gottlob Frege
  at the start of sentenses being asserted as true.

  The intended usage is for specification-first software. Where the programmer
  defines the high level structure of a program in terms of a specification,
  then progressively builds the parts conforming that specification until all
  the tests are passed. A desired side-effect is having a simple way to generate
  up-to-date documentation outside the code for API consumers.
`

Passons maintenant à la structure de haut niveau de notre programme.

Structure de notre programme

Toute la structure de notre programme peut être définie en quelques lignes de code, en plus d'utiliser deux bibliothèques Node.js pour gérer le système de fichiers (fs ) et chemin du répertoire (path), il n'y a aucune dépendance. Dans cette section, nous définissons uniquement la structure du programme, les définitions des fonctions seront données dans la section suivante.

#!/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])
// Get all the specification file paths
// If a specification file is provided then just test that file
// Otherwise find all the specification files in the target directory
const paths = specRegExp.test(target)
  ? [ target ]
  : findSpecifications(target, specRegExp).filter(x => x)
// Get the content of each specification file
// Get the assertions of each specification file
const assertionGroups = getAssertions(getSpecifications(paths))
// Log all the assertions
logAssertions(assertionGroups)
 
// Check for any failed assertions and return an appropriate exit code
process.exitCode = checkAssertions(assertionGroups)

Comme c'est aussi le point d'entrée de notre CLI (Command Line Interface), nous devons ajouter la première ligne du shebang, qui indique que ce fichier doit être exécuté par le programme de nœud. Il n'est pas nécessaire d'ajouter une bibliothèque spécifique pour gérer les options de commande puisque nous ne nous intéressons qu'à un seul argument. Toutefois, si vous envisagez d’étendre ce programme de manière significative, vous pouvez envisager d’autres options.

Pour obtenir le fichier ou le répertoire de test cible, nous devons combiner le chemin pour exécuter la commande (en utilisant process.cwd()) avec les paramètres fournis par l'utilisateur comme premier argument lors de l'exécution de la commande (en utilisant process. argv[2 ]) se connecte.

Vous pouvez trouver des références à ces valeurs dans la documentation Node.js de l'objet processus. Par cette méthode, nous obtenons le chemin absolu du répertoire/fichier cible.

Maintenant, la première chose que nous devons faire est de trouver tous les fichiers de spécifications JavaScript. Comme le montre la ligne 12, nous pouvons utiliser des opérateurs conditionnels pour offrir plus de flexibilité : si l'utilisateur fournit un fichier canonique comme cible, nous utilisons simplement le chemin du fichier directement.

Sinon, si l'utilisateur fournit un chemin de répertoire, nous devons trouver tous les fichiers correspondant à la constante définie par specRegExp, que nous utilisons la fonction findSpecifications que nous définirons plus tard. Cette fonction renverra un tableau de chemins vers chaque fichier de spécifications dans le répertoire cible.

A la ligne 18, nous définissons la constante assertionGroups en combinant les deux fonctions getspécification() et getassertion(). Obtenez d’abord le contenu de chaque fichier de spécification, puis extrayez-en les assertions.

Nous définirons ces deux fonctions plus tard, pour l'instant notons simplement que nous utilisons la sortie de la première fonction comme paramètre de la deuxième fonction, simplifiant ainsi le processus et entre ces deux fonctions une connexion directe a été établie.

Bien que nous puissions avoir une seule fonction, et en les divisant, nous pouvons mieux comprendre quel est le processus réel, rappelez-vous que le programme doit être clair et compréhensible ; La structure de la constante

assertionsGroup est la suivante :

assertionGroup[specification][assertion]

Ensuite, nous enregistrons toutes ces assertions dans le journal de l'utilisateur afin que les résultats puissent être rapportés à l'aide de la fonction logassertion(). Chaque assertion contiendra le résultat (vrai ou faux) et une petite description, et nous pourrons utiliser ces informations pour donner à chaque type de résultat une couleur spéciale.

Enfin, nous définissons le code de sortie en fonction du résultat de l'assertion. Cela fournira au processus des informations sur la façon dont le programme s'est terminé : le processus a-t-il réussi ou échoué ? Un code de sortie de 0 signifie que le processus s'est terminé avec succès, 1 s'il a échoué, ou dans notre exemple, 1 lorsqu'au moins une assertion a échoué.

Trouver tous les fichiers de spécifications

要找到所有的JavaScript规范文件,我们可以使用一个递归函数,该函数遍历用户作为CLI参数指定的目录。在搜索时,应该使用程序开始时定义的正则表达式(/\.spec\.js$/)检查每个文件,该表达式将匹配以.spec.js结尾的所有文件路径。

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

我们的findspecification函数接受一个目标目录(dir)和一个正则表达式,该正则表达式标识规范文件(matchPattern)。

获取每个规范的内容

由于我们导出的是模板文本,因此获取内容和计算后的断言非常简单,因此我们必须导入每个文件,当它被导入时,所有的断言都将自动进行计算。

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

使用map()函数,我们使用节点的require函数将数组的路径替换为文件的内容。

从文本中提取断言

此时,我们有一个数组,其中包含每个规范文件的内容,并且已经计算了它们的断言。我们使用旋转门指示器(|-)来查找所有这些断言并提取它们。

function getAssertions (specifications) {
  return specifications.map(specification => ({
    title: specification.split('\n\n', 1)[0].trim(),
    assertions: specification.match(/^( |\t)*(\|-)(.|\n)*?\./gm).map(assertion => {
      const assertionFragments = /(?:\|-) (\w*) ((?:.|\n)*)/.exec(assertion)
 
      return {
        value: assertionFragments[1],
        description: assertionFragments[2].replace(/\n /, '')
      }
    })
  }))
}

这个函数将返回一个类似的数组,但是用一个如下结构的对象替换每个规范的内容:

title: <String: Name of this particular specification>,
  assertions: [
    {
      value: <Boolean: The result of the assertion>,
      description: <String: The short description for the assertion>
    }
  ]
}

标题是用规范字符串的第一行设置的。然后,每个断言都作为数组存储在断言键中。该值将断言的结果表示为布尔值。我们将使用这个值来知道断言是否成功。

此外,描述将显示给用户,作为识别哪些断言成功和哪些断言失败的方法。我们在每种情况下都使用正则表达式。

记录结果

我们沿着程序构建的数组现在有一系列JavaScript规范文件,其中包含一列找到的断言及其结果和描述,因此除了向用户报告结果之外,没有什么可做的。

{
  function logAssertions(assertionGroups) {
  // Methods to log text with colors
  const ansiColor = {
    blue: text => console.log(`\x1b[1m\x1b[34m${text}\x1b[39m\x1b[22m`),
    green: text => console.log(`\x1b[32m    ${text}\x1b[39m`),
    red: text => console.log(`\x1b[31m    ${text}\x1b[39m`)
  }
  // Log the results
  assertionGroups.forEach(group => {
    ansiColor.blue(group.title)
    group.assertions.forEach(assertion => {
      assertion.value === &#39;true&#39;
        ? ansiColor.green(assertion.description)
        : ansiColor.red(assertion.description)
    })
  })
 
  console.log(&#39;\n&#39;)
}

我们可以根据结果使用颜色来格式化输入。为了在终端上显示颜色,我们需要添加ANSI转义码。为了在下一个块中简化它们的用法,我们将每种颜色保存为ansiColor对象的方法。

首先,我们要显示规范的标题,请记住,我们为每个规范使用数组的第一个维度,并将其命名为一组(断言)。然后,我们使用它们各自的颜色根据它们的值记录所有断言:绿色表示计算为true的断言,红色表示具有其他值的断言。

注意比较,我们检查true是否为字符串,因为我们从每个文件接收字符串。

检查结果

最后,最后一步是检查所有测试是否成功。

function checkAssertions (assertionGroups) {
  return assertionGroups.some(
    group => group.assertions.some(assertion => assertion.value === &#39;false&#39;)
  ) ? 1 : 0
}

我们使用数组的some()方法检查每个断言组(规范),看看是否至少有一个值是' ' ' false ' ' '。我们嵌套了其中的两个因为我们有一个二维数组。

运行我们的程序

此时,我们的CLI应准备好运行一些JavaScript规范,并查看是否拾取并评估了断言。在test目录中,您可以从本文开头复制规范示例,并将以下命令粘贴到您的文件中:package.json

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

其中test是包含示例规范文件的目录的名称。

当运行npm test命令时,您应该看到使用它们各自颜色的结果。

相关免费学习推荐:js视频教程

更多编程相关知识,请访问:编程入门!!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer