都2022年了,你不會還不會寫單元測試吧?

語言: CN / TW / HK

theme: condensed-night-purple

本文正在參加「金石計劃 . 瓜分6萬現金大獎」

相信有很多人遇到過這種情況,就是在入職公司後,開始接手公司的老專案,給公司的老專案修修改改。當我們在一個系統裡邊修改了很多程式碼時,但又不確定改動是否影響在核心邏輯時,是否會導致專案原來的功能出現bug時。我們就可以使用單元測試來幫助我們來進行測試。所以軟體開發者編寫單元測試,就成了很重要的事情。

那我們為什麼要編寫單元測試? 單元測試的優點是什麼?

  • 必要性:JavaScript 缺少型別檢查,編譯期間無法定位到錯誤,單元測試可以幫助你測試多種異常情況。(現在可以使用TypeScript來彌補型別檢查的缺點)。
  • 驗證功能:單元測試可以確保我們的程式碼正常執行,並且不出現異常輸出以及副作用————這是很多bug產生的原因。
  • 防止錯誤再次發生:當我們發現錯誤時,新增單元測試來檢查場景錯誤,可以防止程式碼在後期重構和優化中錯誤的再次發生。
  • 自動化、效率高:通過 console 雖然可以打印出內部資訊來檢查錯誤。但是這是效率十分低的操作,;每次測試都得列印一次,效率不能得到保證。通過編寫測試用例,可以做到一次編寫,多次自動執行,效率高。
  • 保護您的應用程式:單元測試可以檢查可利用的漏洞(例如啟用惡意 SQL 注入的漏洞用來檢查程式碼的可靠性)。
  • 更利於後期程式碼的維護:網際網路行業產品迭代速度很快,迭代後必然存在程式碼重構的過程,那怎麼才能保證重構後代碼的質量呢?有測試用例做後盾,就可以大膽的進行重構。

# 編寫單元測試的一些規範 單元測試框架的使用,讓我們能夠快速編寫和自動執行我們的測試,並且將它們整合到我們的開發和部署過程中。

以下是一些常見編寫測試的規範。

### 保持單元測試的功能單一和程式碼簡潔 不要讓你的單元測試過於複雜,本末倒置。儘量讓你的單元測試程式碼簡單明瞭,責任單一。

### 全面的考慮函式執行的結果 我們不僅僅要考慮函式正常執行時的情況,還要考慮函式錯誤執行時的情況。對程式碼進行單元測試,我們不僅僅要確保函式在輸入正確的值時,有正確的輸出,還要確保函式在輸入錯誤引數時,執行的結果是失敗的。這些對錯誤的檢查更有利於我們預測引發錯誤的原因以及場景。

### 拆分複雜的函式 對功能邏輯複雜的函式,編寫單元測試是十分困難的。我們要把複雜的函式拆分為相對較小的函式來進行單元測試。

### 避免測試時涉及資料的請求(資料庫and網路請求) 單元測試應該是快速和輕量級的。但如果測試過程中涉及到網路資料的請求,或者對資料庫的操作這就需要很長的時間來進行響應。這會使我們的單元測試變得很臃腫和重量級。但如果無法避免資料請求的話,我們一般會模擬請求結果來減輕我們的測試壓力。

# 如何編寫單元測試 現在我們都已經對單元測試有了一定的瞭解了,那我們就著手開始編寫我們的第一個單元測吧!!!

這次我將帶著大家使用Mocha框架--市面上比較主流的測試框架之一。來編寫我們的單元測試,雖然市面上每個框架都不同,但是他們大體是相似的。只要我們掌握了其中一種框架,其他的框架也能夠很快的上手。

在編寫單元測試之前,請確保你的電腦上已經安裝的Node.JS環境。因為我們的Mocha是執行在node環境下的。所以我預設你的node環境已經安裝好了。

### 建立一個新的專案

首先建立了一個新的資料夾(必須是以英文命名) ,然後在資料夾裡開啟你的終端視窗或命令列視窗。然後輸入npm init -y

然後你的專案裡面就會生成一個package.json檔案

image.png (我這裡資料夾命名為UNIT-TEST)

然後我們就可以再在我們的專案裡面安裝Moche框架了。在我們的終端視窗輸入我們的npm install -D mocha命令(如果安裝速度慢的建議用cnpm)

然後開啟我們的package.json檔案,把腳本里的test命令修改成mocha

image.png

### 編寫我們的被測試檔案

這裡我們編寫一個簡單的紅綠燈系統,來用於我們待會的單元測試。

我們在專案中建立我們traffic,js檔案以及編寫我們的TrafficLight

```js class TrafficLight {

constructor() { // 對所有例項的lightIndex都賦值為0 this.lightIndex = 0; } // 定義我們的靜態函式colors,只要一呼叫就返回函式 static get colors () { return ["green", "yellow", "red"]; } get light () { return TrafficLight.colors[this.lightIndex]; }

next () { this.lightIndex++; // 這裡故意設定了一個錯誤,this.lightIndex為3時是undefined if (this.lightIndex > TrafficLight.colors.length) { this.lightIndex = 0; } } }

module.exports = TrafficLight; ```

這個class由4部分組成

TrafficLight.colors:一個關於交通燈的顏色的陣列常量。

lightIndex:一個指示當前交通燈顏色的Index的變數。

light():一個返回當前交通燈顏色的函式。

next():一個改變當前交通燈顏色的函式,使交通燈指向下一個顏色。

接下來開始著手編寫我們的第一個變數

首先,在專案資料夾中建立一個名為test的資料夾。testMocha預設存放單元測試程式碼的資料夾。然後我們在test專案下再新建一個traffic.test.js檔案來編寫我們的單元測試

接下來開始編寫我們的traffic.test.js單元測試,首先匯入我們的被測試的TrafficLight模組。

js const TrafficLight = require( "../traffic" ); 我們還需要在程式碼中使用assert模組進行測試,所以我們要匯入assert模組

js const assert = require( "assert" );Mocha中我們可以使用describe()函式來幫我們進行單元測試的分組。所以我們應該先定義一個頂層分組。

js describe( "TrafficLight", function () { }); 然後我們就可以在這個分組下進行一些子功能的測試定義與分組。首先我們先定義一個對colors的測試。看colors是否和我們預期的相同。

js describe( "TrafficLight", function () { describe( "colors", function () { //測試內容 }); }); 我們預設交通燈的colors只是三種顏色green、yellow、red。所以我們可以去驗證它的長度是否符合我們的要求。我們需要用到Mocha框架裡定義的it()函式語法。它要編寫在describe()函式裡。

```js describe( "TrafficLight", function () { describe( "colors", function () { it( "has 3 states", function () { const traffic = new TrafficLight(); assert.equal( 3, TrafficLight.colors.length ); }); }); });

`` 然後我們執行一下看是否能通過測試。我們在終端視窗執行npm test`,如果一切正確,Mocha 會打印出單元測試執行的結果。

image.png 執行通過,而且結構清晰

編寫更多的單元測試

現在我們的專案已經可以正常執行我們的單元測試了,所以我們可以編寫更多的測試用例。來測試我們的功能是否正常。

首先,我們可以在colors分組中新增一個對紅綠燈顏色順序是否正確的測試。

js 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 ] ); } });

image.png

我們還可以對next()函式進行測試,看他是否可能正確的改變紅綠燈的顏色,使其按照正確的順序執行。對next()的測試不應該屬於color分組。所以我們應該為它新建一個分組。並且在這個分組裡編寫兩個測試,一個是測試交通燈的顏色是否按正確的順序改變,一個是測試交通燈是否可以正確的迴圈執行。

```js 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(); // 顏色變化的迴圈順序是 green -> yellow -> red -> green for( let i = 0; i < 3; i++ ) { traffic.next(); } assert.equal( traffic.light, TrafficLight.colors[ 0 ] ); }); });

```

image.png 當我們執行單元測試時,我們可以看到有一個測試失敗的提示。這因為我們在編寫TrafficLight類時,故意設定的一個錯誤,當this.lightIndex為3時結果是undefined。無法正確迴圈執行從red回到green

js next () { this.lightIndex++; // 這裡故意設定了一個錯誤,this.lightIndex為3時是undefined if (this.lightIndex > TrafficLight.colors.length) { this.lightIndex = 0; } 那麼其實修改這個錯誤也十分簡單,我們只需要把我們的>改為===,當lightIndex的值超過我們的color陣列長度時,它就會自動被賦值為0,回到初始值。這樣就可以使我們的程式碼正常執行。

js if( this.lightIndex === TrafficLight.colors.length ){ this.lightIndex = 0; }

image.png

總結

單元測試的編寫是比較簡單的,它是提高我們軟體開發的工具。它的使用有助於幫我更早的發現錯誤。並防止我們後期重構程式碼時再次產生同樣的錯誤。它可以讓我們的專案後期更易於管理和維護,即使我們的專案程式碼體積結構變得更大更復雜——尤其是在更大的開發團隊中。而且自動化單元測試還能夠讓開發人員在夠重構和優化程式碼時,不必擔心新程式碼的是否會影響舊的功能。

單元測試是開發過程的關鍵部分,對於幫助您構建更好、更安全的 JavaScript 應用程式至關重要。