測試代碼是確定代碼穩定的第一步。能做到這一點的最佳方法之一就是使用單元測試,確定應用程式中的每個較小的功能都按應有的方式運作——尤其是當應用程式接收到極端或無效輸入,甚至可能有害的輸入時。
為什麼要進行單元測試?
進行單元測試有許多不同的方法,一些主要目的是:
- 驗證功能:單元測試確定代碼做正确的事情并且不做任何不應該做的事情——大多數錯誤發生在這裡。
- 防止代碼回歸:當我們發現錯誤時,添加單元測試來檢查場景可以防止代碼更改在将來重新引入錯誤。
- 記錄代碼:通過正确的單元測試,一套完整的測試和結果提供了應用程式應該如何工作的規範。
- 保護您的應用程式:單元測試可以檢查可利用的漏洞(例如啟用惡意 SQL 注入的漏洞)。
範圍界定和編寫單元測試
使用單元測試架構使我們能夠快速編寫和自動化我們的測試,并将它們內建到我們的開發和部署過程中。這些架構通常支援前端和後端 JavaScript 代碼的測試。
以下是幫助你編寫性能單元測試和可測試代碼的一些通用指南。
保持單元測試簡短而簡單
不要編寫過重的單元測試,測試應該隻有幾行代碼來檢查應用程式的簡短功能塊。
考慮正面和負面的測試用例
雖然編寫正确執行函數的測試是有用的,但是,編寫更廣泛的測試集來檢查函數在被濫用或在極端情況下是否正确或者失敗同樣重要。這些負面測試可能更有價值,因為它們有助于預測意外情況,例如函數何時應引發異常或應如何處理接收格式錯誤的資料。
分解長而複雜的函數
包含大量邏輯的大型函數難以測試;包含太多操作則難以有效地測試每個變量。如果函數太複雜,請将其拆分為較小的函數以進行單獨測試。
避免網絡和資料庫連接配接
單元測試應該是快速和輕量級的,但是進行網絡調用或連接配接到其他應用程式或程序的功能需要長時間才能執行。這使得同時運作多個操作變得具有挑戰性,且會産生更脆弱的代碼。你可以在單元測試中調用模拟的網絡或資料庫而非真實地連接配接網絡。而如果要進行包含真實的網絡和資料庫連接配接的測試,應當在稱為內建測試(所有的單元或子產品被組合在一起并作為一個整體進行測試)中進行而不是單元測試。
如何編寫單元測試
我們已經回顧了一些單元測試的最佳實踐,現在準備好用 JavaScript 編寫你的第一個單元測試了。
本教程使用 Mocha 架構 —— 最流行的單元測試之一。每個測試架構都略有不同,但它們足夠相似,學習基本概念将使你能夠輕松地在它們之間切換。
在開始前,請確定你的電腦上安裝了 Node.js 環境。
建立一個新項目
首先打開一個終端視窗或指令提示符到一個新的項目檔案夾。然後,通過以下指令在其中建立一個新的 Node.js 項目。
npm init -y
這會在檔案夾中建立一個檔案
package.json
,使你能夠使用
npm install -D mocha
指令來安裝 mocha 架構。
接下來,在代碼編輯器中打開
package.json
檔案并将 test script 替換為:
mocha
"scripts": {
"test": "mocha"
},
實作一個類
接下來,編寫一個簡單的紅綠燈系統進行單元測試。
在項目目錄中,建立一個名為
traffic.js
檔案,其中是一個
TrafficLight
的類:
class TrafficLight {
constructor() {
this.lightIndex = 0;
}
static get colors() {
return [ "green", "yellow", "red" ];
}
get light() {
return TrafficLight.colors[ this.lightIndex ];
}
next() {
this.lightIndex++;
// This is intentionally wrong!
if( this.lightIndex > TrafficLight.colors.length ) {
this.lightIndex = 0;
}
}
}
module.exports = TrafficLight;
這個類由四部分組成:
-
:交通燈顔色的常量屬性。TrafficLight.colors
-
:一個變量,跟蹤目前交通燈顔色的索引。lightIndex
-
:以字元串形式傳回目前交通燈顔色的類屬性。light
-
:将紅綠燈更改為下一個燈光顔色的功能。next()
配置和添加我們的第一個單元測試
現在是時候圍繞代碼添加一些單元測試了。
在項目中建立一個名為
test
的目錄,這是 Mocha 預設檢查單元測試的地方。然後,在新的測試檔案夾中添加一個名為
traffic.test.js
的檔案。
接下來,在檔案頂部導入
TrafficLight
類:
const TrafficLight = require( "../traffic" );
我們還将使用該
assert
子產品進行測試,是以在你的代碼中需要引入它:
const assert = require( "assert" );
在 Mocha 中我們可以使用
describe()
這個函數将單元測試進行分組集合,如下:
describe( "TrafficLight", function () {
});
然後,我們将建立一些單元測試來驗證他們自己的子組中的交通顔色:
describe( "TrafficLight", function () {
describe( "colors", function () {
});
});
對于第一個單元測試,我們可以驗證
colors
隻有三種狀态:綠色、黃色和紅色。測試方式是使用
describe()
組内的
it()
函數定義的,是以編寫測試如下:
describe( "TrafficLight", function () {
describe( "colors", function () {
it( "has 3 states", function () {
const traffic = new TrafficLight();
assert.equal( 3, TrafficLight.colors.length );
});
});
});
現在讓我們運作單元測試,看看它是否通過。
在終端視窗中運作
npm test
,如果一切正确,Mocha 會列印出單元測試運作的結果。
添加更多單元測試
我們的項目現在已準備好運作單元測試,是以我們可以添加更多測試以確定我們的代碼正常工作。
首先,向
colors
組中添加一個單元測試,以驗證紅綠燈顔色是否正确且有序。這是實作此測試的一種方法:
it( "colors are in order", function () {
const expectedLightOrder = [ "green", "yellow", "red" ];
const traffic = new TrafficLight();
for( let i = 0; i < expectedLightOrder.length; i++ ) {
assert.equal( expectedLightOrder[ i ], TrafficLight.colors[ i ] );
}
});
其次,測試
next()
方法看它是否正确地改變了交通信号燈。建立一個新的子組并添加兩個單元測試:一個檢查燈光是否以正确的順序變化,另一個檢查燈光是否能循環在紅燈之後變為綠燈:
describe( "next()", function () {
it( "changes lights in order", function () {
const traffic = new TrafficLight();
for( let i = 0; i < TrafficLight.colors.length; i++ )
assert.equal( traffic.light, TrafficLight.colors[ i ] );
traffic.next();
}
});
it( "loops back to green", function () {
const traffic = new TrafficLight();
// Change the light 3x to go from green -> yellow -> red -> green
for( let i = 0; i < 3; i++ ) {
traffic.next();
}
assert.equal( traffic.light, TrafficLight.colors[ 0 ] );
});
});
現在,當我們重新運作測試時,我們會看到其中一個測試失敗了。這是因為
TrafficLight
類中有一個錯誤。
修複錯誤
為友善調試本例提前注明好的錯誤代碼位置,我們再次打開
TrafficLight
類并找到
next()
函數内的這句注釋:
// This is intentionally wrong!
。
從單元測試中我們知道這個函數沒有正确地循環回
green
,我們可以看到代碼是在判斷
lightIndex
值超過交通燈顔色的數量時給索引設定了
,這顯然是不對的,我們必須在值達到确切的顔色數時立即将索引修改為
:
// This is intentionally wrong!
if( this.lightIndex === TrafficLight.colors.length ) {
this.lightIndex = 0;
}
現在你所有的單元測試都應該通過了。而這帶來的好處是即使
TrafficLight
這個類被重構或大量修改,我們的單元測試也會在它到達使用者之前捕獲這個錯誤。