程式設計不僅僅是給計算機下達如何完成一項任務的指令,它還包括以一種精確的方式與他人交流思想,甚至是與未來的自己。這樣的交流可以有多個目標,也許是為了共用資訊,或者只是為了更容易地修改—如果你不理解或不記得很久以前做過什麼,那麼就很難修改。
當我們編寫軟體時,我們還需要確保程式碼具有預期的功能。雖然有定義語意的正式方法,但是最簡單、最快速(但不那麼嚴格)的方法是將該功能投入使用,並檢視它是否產生預期的結果。
大多數開發人員都熟悉這些實踐:程式碼檔案作為註釋來明確程式碼塊的目標,以及一系列測試來確保函數給出所需的輸出。
但是通常檔案和測試是在不同的步驟中完成的。通過統一這些實踐,我們可以為參與專案開發的任何人提供更好的體驗。本文探討了一個簡單的程式實現,該程式可以執行既適用於檔案編寫又適用於測試的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命令時,您應該看到使用它們各自顏色的結果。
相關免費學習推薦:
更多程式設計相關知識,請存取:!!
以上就是詳解構建可執行的JavaScript規範的方法的詳細內容,更多請關注TW511.COM其它相關文章!