如何用 JavaScript 編寫你的第一個單元測試

chuck 發表於 2022-12-08
JavaScript

前言

測試程式碼是使程式碼安全的第一步。做到這一點的最好方法之一是使用單元測試,確保應用程式中的每個小功能都能發揮其應有的作用--特別是當應用程式處於邊緣情況,比如無效的輸入,或有潛在危害的輸入。

為什麼要單元測試

說到單元測試,有許多不同的方法。單元測試的一些主要目的是:

  • 驗證功能:單元測試確保程式碼做正確的事情,不做不應該做的事情--這是大多數錯誤發生的地方。
  • 防止程式碼混亂:當我們發現一個bug時,新增一個單元測試來檢查這個場景,可以保證程式碼的更改不會在將來重新引入這個bug。
  • 文件化程式碼:有了正確的單元測試,一套完整的測試和結果提供了一個應用程式應該如何執行的規範。
  • 程式碼更安全:單元測試可以檢查可被利用的漏洞(比如那些可以實現惡意SQL隱碼攻擊的漏洞)。

確定範圍

使用單元測試框架使我們能夠快速編寫和自動化我們的測試,並將它們整合到我們的開發和部署過程中。這些框架通常支援在前端和後端的JavaScript程式碼中進行測試。

下面是一些幫助你編寫效能單元測試和可測試程式碼的一般準則。

保持簡短

不要讓你的單元測試冗餘。測試應該只有幾行程式碼,檢查應用程式的程式碼塊。

同時考慮正反面

編寫一個測試來確認一個函式的正確執行是有幫助的。然而,編寫一套更廣泛的測試,檢查一個函式在被誤用時或在邊緣情況下是否會失敗,會更有效果。這些負面測試甚至更有價值,因為它們有助於預測意外情況。例如一個函式什麼時候應該丟擲異常,或者它應該如何處理接收到的畸形資料。

分解複雜功能

含有大量邏輯的大型函式很難測試;包括太多的操作,無法有效測試每個變數。如果一個函式過於複雜,可以將其分割成較小的函式進行單獨測試。

避免網路和資料庫連線

單元測試應該快速且輕量,但是函式會發出網路請求,或者連線其他程式並花很長時間執行。這使得同時執行許多操作具有挑戰性,並可能產生更脆弱的程式碼。你可以在單元測試中造假資料來實現模擬的網路或資料庫呼叫,這可以讓你測試函式的其餘部分。你可以在不同的測試過程中包含真正的網路和資料庫連線,這稱為整合測試

如何編寫單元測試

現在,我們已經回顧了一些單元測試的最佳實踐,你已經準備好在JavaScript中編寫你的第一個單元測試。

本教程使用了Mocha框架,它是最流行的單元測試之一。每個測試框架都略有不同,但足夠相似,學習基本概念將使你能夠在它們之間切換自如。

要跟著示例,請確保電腦上已經安裝了Node.js。

建立新專案

首先,開啟終端視窗或命令提示符到一個新的專案資料夾。然後,透過輸入npm init -y在其中建立一個新的Node.js專案。

這會在資料夾內建立package.json檔案,使你能夠使用npm install -D mocha將Mocha安裝為開發依賴。

接著,在編輯器中開啟package.json檔案,用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預設檢查單元測試的地方。在test資料夾下新增traffic.test.js檔案。

接著,在檔案頂部匯入TrafficLight

const TrafficLight = require( "../traffic" );

我們要用到測試的assert模組,因此也需要匯入:

const assert = require( "assert" );

在Mocha的幫助下,我們可以使用describe()函式將單元測試分組。因此我們可以為這個類設定一個頂級組,如下所示:

describe( "TrafficLight", function () {
});

然後,我們在子組中新增校驗交通燈顏色的單元測試,位於TrafficLight集合內部,並稱為colors

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會列印出單元測試執行的結果。

passing.png

新增更多單元測試

我們的專案現在已經準備好執行單元測試了,因此可以新增更多的單元測試,確保程式碼正確執行。

首先,新增一個單元測試到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類中有一個錯誤。

error.png

修復bug

再次開啟TrafficLight類的程式碼,找到next()函式內的註釋,其內容為// This is intentionally wrong!

從我們的單元測試中,我們知道這個函式沒有正確地返回到綠色。我們可以看到,目前的程式碼在lightIndex值超過交通燈顏色的數量時進行檢查,但索引是從0開始的。相反,我們必須在該索引值達到顏色數量時返回到綠色。讓我們更新程式碼,當lightIndex值等於交通燈顏色列表的長度時,將其重置為0

// This is intentionally wrong!
if( this.lightIndex === TrafficLight.colors.length ) {
    this.lightIndex = 0;
}

現在你所有的單元測試都應該透過。最重要的是,即使TrafficLight類被重構或大量修改,我們的單元測試也會在它觸達使用者之前捕獲這個錯誤。

pas.png

總結

單元測試很容易設定,是軟體開發的有效工具。它們有助於早期消除錯誤,並防止它們返回。這使專案更易於管理和維護,即使它們變得更大和更復雜,特別是在更大的開發團隊中。像這樣的自動化測試也使開發人員能夠重構和最佳化他們的程式碼,而不必擔心新程式碼的行為是否正確。

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

祝你測試愉快!

以上就是本文的所有內容,如果對你有所幫助,歡迎收藏、點贊、轉發~