Heim > Artikel > Web-Frontend > Eine ausführliche Erklärung zum Erstellen einer ausführbaren JavaScript-Spezifikation
Beim Programmieren geht es nicht nur darum, einem Computer Anweisungen zu geben, wie eine Aufgabe zu erledigen ist, es geht auch darum, Ideen auf präzise Weise mit anderen Menschen zu kommunizieren, sogar mit Ihrem zukünftigen Ich. Eine solche Kommunikation kann mehrere Ziele verfolgen, etwa den Austausch von Informationen oder einfach nur die Erleichterung des Überarbeitens – was schwierig ist, wenn man nicht versteht oder sich nicht daran erinnert, was vor langer Zeit getan wurde.
Wenn wir Software schreiben, müssen wir auch sicherstellen, dass der Code die vorgesehene Funktionalität aufweist. Zwar gibt es formale Methoden zur Definition der Semantik, der einfachste und schnellste (aber weniger strenge) Ansatz besteht jedoch darin, die Funktion zu nutzen und zu prüfen, ob sie die erwarteten Ergebnisse liefert.
Die meisten Entwickler sind mit diesen Praktiken vertraut: Codedokumentation als Kommentare, um das Ziel eines Codeblocks zu verdeutlichen, und eine Reihe von Tests, um sicherzustellen, dass die Funktion die gewünschte Ausgabe liefert.
Aber in der Regel erfolgen Dokumentation und Prüfung in unterschiedlichen Schritten. Durch die Vereinheitlichung dieser Praktiken können wir allen, die an der Projektentwicklung beteiligt sind, eine bessere Erfahrung bieten. In diesem Artikel wird die Implementierung eines einfachen Programms untersucht, das eine JavaScript-Spezifikation ausführen kann, die sowohl für die Dokumentation als auch für Tests geeignet ist.
Wir werden eine Befehlszeilenschnittstelle erstellen, die nach allen Spezifikationsdateien in einem Verzeichnis sucht, alle in jeder Spezifikation gefundenen Behauptungen extrahiert, ihre Ergebnisse berechnet und schließlich anzeigt, welche Behauptungen fehlgeschlagen und welche bestanden haben.
Kanonisches Format
Jede kanonische Datei exportiert eine Zeichenfolge aus dem Vorlagentext. Die erste Zeile kann als Titel der Spezifikation dienen. Mithilfe von Vorlagenliteralen können wir JS-Ausdrücke zwischen Zeichenfolgen einbetten, und jeder Ausdruck stellt eine Behauptung dar. Um jede Behauptung zu identifizieren, können wir die Zeile mit einem eindeutigen Zeichen beginnen.
In diesem Fall können wir eine Kombination aus Balkenzeichen (|) und Bindestrich (-) verwenden. Der Bindestrich ähnelt dem Drehtürsymbol, das manchmal als Symbol für logische Aussagen dargestellt werden kann.
Hier ist ein Beispiel mit einer Erklärung seiner Verwendung:
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. `
Lassen Sie uns nun mit der übergeordneten Struktur unseres Programms fortfahren.
Struktur unseres Programms
Die gesamte Struktur unseres Programms kann in wenigen Codezeilen definiert werden, ohne dass etwas anderes erforderlich ist, als zwei Node.js-Bibliotheken zu verwenden, um Dateisysteme (fs) und Verzeichnispfade (Pfad) zu verwalten . In diesem Abschnitt definieren wir nur die Struktur des Programms, die Funktionsdefinitionen werden im nächsten Abschnitt angegeben.
#!/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)
Da dies auch der Einstiegspunkt unserer CLI (Command Line Interface) ist, müssen wir die erste Zeile des Shebangs hinzufügen, die angibt, dass diese Datei vom Knotenprogramm ausgeführt werden soll. Es ist nicht erforderlich, eine spezielle Bibliothek zur Verarbeitung von Befehlsoptionen hinzuzufügen, da wir nur an einem einzelnen Parameter interessiert sind. Wenn Sie jedoch planen, dieses Programm erheblich zu erweitern, können Sie andere Optionen in Betracht ziehen.
Um die Zieltestdatei oder das Zielverzeichnis zu erhalten, müssen wir den Pfad des ausgeführten Befehls (mithilfe von „process.cwd()“) mit den vom Benutzer bereitgestellten Argumenten als erstes Argument verketten, wenn wir den Befehl ausführen (mithilfe von „process.argv[2“) ]).
Verweise auf diese Werte finden Sie in der Node.js-Dokumentation für das Prozessobjekt. Mit dieser Methode erhalten wir den absoluten Pfad des Zielverzeichnisses/der Zieldatei.
Jetzt müssen wir zunächst alle JavaScript-Spezifikationsdateien finden. Wie in Zeile 12 gezeigt, können wir bedingte Operatoren verwenden, um mehr Flexibilität zu bieten: Wenn der Benutzer eine kanonische Datei als Ziel angibt, verwenden wir einfach direkt den Dateipfad.
Andernfalls müssen wir, wenn der Benutzer einen Verzeichnispfad angibt, alle Dateien finden, die den in specRegExp definierten Konstanten entsprechen. Dazu verwenden wir die Funktion findSpecifications, die wir später definieren werden. Diese Funktion gibt ein Array von Pfaden zu jeder Spezifikationsdatei im Zielverzeichnis zurück.
In Zeile 18 definieren wir die AssertionGroups-Konstante, indem wir die beiden Funktionen getpecification() und getassertion() kombinieren. Rufen Sie zunächst den Inhalt jeder Spezifikationsdatei ab und extrahieren Sie dann Behauptungen daraus.
Wir werden diese beiden Funktionen später definieren. Beachten Sie zunächst, dass wir die Ausgabe der ersten Funktion als Parameter der zweiten Funktion verwenden, wodurch der Prozess vereinfacht und eine direkte Verbindung zwischen den beiden Funktionen hergestellt wird.
Wir können zwar nur eine Funktion haben und durch die Aufteilung eine bessere Vorstellung davon bekommen, was der eigentliche Prozess ist, aber denken Sie daran, dass das Programm klar und verständlich sein sollte.
Die AssertionsGroup-Konstante ist wie folgt aufgebaut:
assertionGroup[specification][assertion]
Als nächstes protokollieren wir alle diese Assertionen im Benutzerprotokoll, damit die Ergebnisse mit der Funktion logassertion() gemeldet werden können. Jede Behauptung enthält das Ergebnis (wahr oder falsch) und eine kurze Beschreibung, und wir können diese Informationen verwenden, um jedem Ergebnistyp eine spezielle Farbe zu geben.
Abschließend definieren wir den Exit-Code basierend auf dem Ergebnis der Behauptung. Dadurch erhält der Prozess Informationen darüber, wie das Programm beendet wurde: War der Prozess erfolgreich oder fehlgeschlagen? Ein Exit-Code von 0 bedeutet, dass der Prozess erfolgreich beendet wurde, 1, wenn er fehlgeschlagen ist, oder in unserem Beispiel 1, wenn mindestens eine Behauptung fehlgeschlagen ist.
Alle Spezifikationsdokumente finden
要找到所有的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 === 'true' ? ansiColor.green(assertion.description) : ansiColor.red(assertion.description) }) }) console.log('\n') }
我们可以根据结果使用颜色来格式化输入。为了在终端上显示颜色,我们需要添加ANSI转义码。为了在下一个块中简化它们的用法,我们将每种颜色保存为ansiColor对象的方法。
首先,我们要显示规范的标题,请记住,我们为每个规范使用数组的第一个维度,并将其命名为一组(断言)。然后,我们使用它们各自的颜色根据它们的值记录所有断言:绿色表示计算为true的断言,红色表示具有其他值的断言。
注意比较,我们检查true是否为字符串,因为我们从每个文件接收字符串。
检查结果
最后,最后一步是检查所有测试是否成功。
function checkAssertions (assertionGroups) { return assertionGroups.some( group => group.assertions.some(assertion => assertion.value === 'false') ) ? 1 : 0 }
我们使用数组的some()方法检查每个断言组(规范),看看是否至少有一个值是' ' ' false ' ' '。我们嵌套了其中的两个因为我们有一个二维数组。
运行我们的程序
此时,我们的CLI应准备好运行一些JavaScript规范,并查看是否拾取并评估了断言。在test目录中,您可以从本文开头复制规范示例,并将以下命令粘贴到您的文件中:package.json
"scripts": { "test": "node index.js test" }
其中test是包含示例规范文件的目录的名称。
当运行npm test命令时,您应该看到使用它们各自颜色的结果。
相关免费学习推荐:js视频教程
更多编程相关知识,请访问:编程入门!!
Das obige ist der detaillierte Inhalt vonEine ausführliche Erklärung zum Erstellen einer ausführbaren JavaScript-Spezifikation. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!