程式設計不僅僅是給電腦下達如何完成一項任務的指令,它還包括以一種精確的方式與他人交流思想,甚至是與未來的自己。這樣的交流可以有多個目標,也許是為了分享訊息,或者只是為了更容易修改——如果你不理解或不記得很久以前做過什麼,那麼就很難修改。
當我們編寫軟體時,我們還需要確保程式碼具有預期的功能。雖然有定義語義的正式方法,但最簡單、最快速(但不那麼嚴格)的方法是將該功能投入使用,並查看它是否產生預期的結果。
大多數開發人員都熟悉這些實踐:程式碼文件作為註解來明確程式碼區塊的目標,以及一系列測試來確保函數給出所需的輸出。
但是通常文件和測試是在不同的步驟中完成的。透過統一這些實踐,我們可以為參與專案開發的任何人提供更好的體驗。本文探討了一個簡單的程式實現,該程式可以運行既適用於文件編寫又適用於測試的JavaScript規格。
我們將建立一個命令列介面,該介面將查找目錄中的所有規範文件,提取每個規範中找到的所有斷言,併計算它們的結果,最後顯示哪些斷言失敗了,哪些斷言通過了。
規格的格式
每個規範檔案將從範本文字匯出一個字串。第一行可以作為規範的標題。模板文字將允許我們在字串之間嵌入JS表達式,每個表達式將表示一個斷言。要識別每個斷言,我們可以用一個獨特的字元開始行。
在本例中,我們可以使用bar字元(|)和破折號(-)的組合,破折號類似於旋轉閘符號,有時可以將其作為邏輯斷言的符號表示。
下面是一個例子,對它的用法做了一些解釋:
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. `
現在讓我們繼續我們程式的高層結構。
我們程式的結構
我們的程式的整個結構可以在幾行程式碼中定義,除了使用兩個Node.js庫來處理檔案系統(fs )和目錄路徑(path)之外,沒有任何依賴關係。在本節中,我們只定義程式的結構,函數定義將在下一節中給出。
#!/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)
因為這也是我們的CLI(命令列介面)的入口點,所以我們需要加入第一行shebang,它表示這個檔案應該由節點程式執行。不需要添加特定的庫來處理命令選項,因為我們只對單一參數感興趣。但是,如果您打算以相當大的方式擴展此程序,則可以考慮其他選項。
要取得目標測試檔或目錄,我們必須將執行指令的路徑(使用process.cwd())與使用者提供的參數作為執行指令時的第一個參數(使用process.argv[2 ])連接起來。
您可以在process物件的Node.js文件中找到這些值的參考。透過這種方法,我們獲得了目標目錄/檔案的絕對路徑。
現在,我們要做的第一件事就是找到所有的JavaScript規格檔。如第12行所示,我們可以使用條件運算子來提供更大的靈活性:如果用戶提供了一個規範文件作為目標然後我們就直接使用,文件路徑。
否則,如果使用者提供了一個目錄路徑然後我們必須找到相匹配的所有檔案模式specRegExp定義的常數,我們使用findSpecifications函數以後,我們將定義。這個函數將傳回目標目錄中每個規範檔案的路徑數組。
在第18行中,我們透過組合兩個函數getspecification()和getassertion()來定義assertionGroups常數。首先取得每個規範文件的內容,然後從中提取斷言。
我們稍後將定義這兩個函數,現在只需要注意,我們使用第一個函數的輸出作為第二個函數的參數,從而簡化了過程,並在這兩個函數之間建立了直接的聯繫。
雖然我們可以只有一個函數,透過拆分它們,我們可以更好地了解什麼是實際的過程,但請記住,程式應該清晰易懂;僅僅做到這一點是不夠的。
assertionsGroup常數的結構如下:
assertionGroup[specification][assertion]
接下來,我們將所有這些斷言記錄到使用者日誌中,以便使用logassertion()函數報告結果。每個斷言將包含結果(true或false)和一個小描述,我們可以使用該資訊為每種類型的結果賦予特殊的顏色。
最後,我們根據斷言的結果定義退出程式碼。這將向流程提供關於程序如何結束的信息:流程是成功的還是失敗了?退出碼為0表示進程成功退出,如果失敗則為1,或者在我們的示例中,當至少一個斷言失敗時為1 。
找出所有規範檔案
要找到所有的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视频教程
更多编程相关知识,请访问:编程入门!!
以上是詳解建構可運行的JavaScript規範的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!