什麼是測試

編寫軟體時,您可以透過測試確認運作是否正常運作。測試可大致定義為以特定方式執行軟體的程序,以確保測試正常運作。

測試成功後,您就能確保在新增程式碼、功能,或甚至升級依附元件時,已編寫的軟體將會繼續按照預期方式運作。測試也可以協助保護您的軟體,防範罕見情況或非預期的輸入。

以下列舉幾個建議您測試的網頁行為:

  • 確保當使用者按下按鈕時,網站的功能運作正常。
  • 確認複雜函式能產生正確的結果。
  • 完成需要使用者登入的動作。
  • 檢查表單是否在輸入格式錯誤的資料時正確回報錯誤。
  • 確保複雜的網頁應用程式可以在使用者頻寬極低或離線時繼續運作。

自動與手動測試

測試軟體的一般方式有兩種:自動化測試和手動測試。

手動測試涉及真人直接執行軟體,例如在瀏覽器中載入網站,並確認網站可正常運作。手動測試很容易建立或定義,例如網站能否載入?您能執行這些操作嗎?—但每次執行產生的費用都會耗費大量人類時間。雖然人類的創意相當自由,可以啟用稱為「探索測試」的測試類型,但我們仍難免會發現失敗或不一致的問題,特別是在執行相同的工作多次時。

自動化測試是指任何可由電腦編寫程式碼及執行測試的程序,可確認軟體行為,「不必」執行任何重複步驟 (例如設定或檢查結果)。重要的是,自動測試設定完成後,即可頻繁執行。這仍然是非常廣泛的定義,值得注意的是,自動化測試能處理各種形狀和形式。本課程大部分以自動化測試為例

手動測試確實有其職責,通常是編寫自動化測試的前身,但自動測試變得不可靠、範圍廣泛或難以寫入。

透過範例掌握基礎概念

對我們來說,編寫 JavaScript 或相關語言的網頁開發人員來說,簡潔的自動化測試可以是類似每天執行的指令碼,例如透過 Node 執行,或透過瀏覽器載入:

import { fibonacci } from "../src/math.js";

if (fibonacci(0) !== 0) {
  throw new Error("Invalid 0th fibonacci result");
}
const fib13 = fibonacci(13);
if (fib13 !== 233) {
  throw new Error("Invalid 13th fibonacci result, was=${fib13} wanted=233");
}

這個簡單的範例提供了下列深入分析:

  • 此為「測試」,因為它會執行一些軟體 (Fibonacci 函式),並檢查結果與預期值,確保其行為符合預期的方式運作。如果行為不正確,就會引發錯誤,JavaScript 會擲回 Error 來表達。

  • 即使您是在終端機或瀏覽器中手動執行這個指令碼,仍屬於自動測試,因為您不需要執行任何個別步驟,就可以重複執行測試。下一頁 (測試執行位置) 將進一步說明更多相關資訊。

  • 雖然這項測試並未使用任何程式庫 (可在任何位置執行的 JavaScript),目前仍在測試中。有許多工具可協助您編寫測試 (包括本課程稍後會介紹的工具),但這些工具仍可用於執行發生問題時造成錯誤的基本原則。

實際測試程式庫

大多數程式庫或內建測試架構都提供兩個主要基本功能,讓測試更容易撰寫:斷言和定義獨立測試的方式。我們會在下一節「斷言和其他基元」中詳細說明這些概念。但整體來說,請務必記得,您看到或寫入的所有測試,最終都會使用這些基元。

斷言是一種結合檢查結果的方式,可在發生問題時導致錯誤。舉例來說,您可以導入 assert,讓先前的測試更簡潔:

import { fibonacci } from "../src/math.js";
import { assert } from "a-made-up-testing-library";

assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");

您可以定義獨立的「測試」,進一步改善這項測試,可以選擇歸類為「套件」。以下套件獨立測試 Fibonacci 函式和 Catalan 函式

import { fibonacci, catalan } from "../src/math.js";
import { assert, test, suite } from "a-made-up-testing-library";

suite("math tests", () => {
  test("fibonacci function", () => {
    assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
    assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");
  });
  test("relationship between sequences", () => {
    const numberToCheck = 4;
    const fib = fibonacci(numberToCheck);
    const cat = catalan(numberToCheck);
    assert.isAbove(fib, cat);
  });
});

在軟體測試中,「test」代表名詞是指「測試案例」:一個獨立且可定址的情境,例如上一個範例中的「序列之間的關聯」測試案例。

個別命名的測試對於下列工作等工作相當實用:

  • 判斷測試在一段時間內的成功或失敗情況。
  • 依名稱標示錯誤或情境,以便更輕鬆地測試情境是否已解決。
  • 執行部分測試,與其他測試分開執行,例如透過 glob 篩選器。

測試測試案例的其中一種方法是使用「三個 A」單元測試:安排、行動和斷言。每個測試案例的核心都會:

  • 安排部分值或狀態 (這可能是硬式編碼的輸入資料)。
  • 執行動作,例如呼叫方法。
  • 斷言輸出值或更新狀態 (使用 assert)。

測試規模

上一節的程式碼範例描述的是單元測試,因為它們測試軟體的次要部分,通常專注於單一檔案,在本例中,僅指單一函式的輸出內容。隨著您考量多個檔案、元件,甚至是不同互連系統的程式碼 (有時並非由您控制,例如網路服務或外部依附元件的行為),測試的複雜度會逐漸增加。因此,測試類型通常是根據其「範圍」或「scale」來命名。

除了單元測試外,還有一些其他測試類型的範例包括元件測試視覺測試整合測試。這些名稱沒有嚴格的定義,而且會因程式碼集而有不同的含義,因此請記得將這些名稱當做指南,並想出適合您的定義。舉例來說,您的系統中有哪些元件測試中?對於 React 開發人員,這可能實際上會對應至「React 元件」,但在其他情況下,開發人員可能有不同的意義。

個別測試的規模可將此概念放在一個概念內 (通常稱為「測試金字塔」),是有關測試檢查和執行方式的實用經驗法則。

測試金字塔位於頂端,其中設有端對端 (E2E) 測試、中間的整合測試和底部的單元測試。
測試金字塔。

這個概念已經過重溫,現在各種其他形狀也廣受歡迎,例如測試鑽石或測試冰錐。測試寫入的優先順序可能專屬於程式碼集。不過常見功能是較簡單的測試 (例如單元測試) 的執行速度較快、更容易寫入 (因此會產生更多測試),以及測試範圍有限;然而,編寫複雜的測試 (如端對端測試) 並不容易編寫,但可測試更廣泛的範圍。事實上,許多測試「形狀」的頂層通常是手動測試,因為有些使用者互動行為太複雜,無法轉換成自動化測試。

這些類型將在自動化測試類型中擴張。

隨堂測驗

大部分測試程式庫和架構提供哪些基本功能?

使用雲端服務供應商的執行器服務。
部分瀏覽器型執行器可讓您將測試外包出去,但這並非測試程式庫的正常功能。
導致不符合例外狀況的斷言。
雖然您可以在測試失敗時擲回錯誤,但 assert() 及其變化版本往往會納入,因為檢查較容易撰寫。
一種將測試分類至測試金字塔的方法。
其實並沒有標準的標準做法。您可以加上測試名稱的前置字串,或是將測試放入不同的檔案中,但大部分的測試架構實際上並未內建分類功能。
可依函式定義獨立測試。
test() 方法幾乎包含所有測試執行器。這一點很重要,因為測試程式碼不會在檔案的頂層執行,導致測試執行器將每個測試案例都視為獨立的單元。