iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0

系統需要進行哪些測試?

目標系統的單一功能通過驗證,只能確認該功能在指定條件下符合預期。完整替換遺留系統前,還要確認各項功能可以正確組合、輸入輸出維持相容、重要操作能完成,以及已確認的品質與風險處理條件都已通過。

前面章節建立的功能案例、既有行為基準與核准差異,會成為本章的測試輸入。本章不再重複單一案例的建立方式,而是把這些案例組成可追溯的整體測試策略,並且定義哪些結果會阻擋正式切換。

從需求與風險建立測試範圍

測試範圍要從已確認資訊建立,不能只按照現有程式結構或測試工具提供的功能決定。開始安排測試前,應該彙整下列內容:

  • 列出目標系統需要完成的功能需求與適用品質條件。
  • 將需要保留的既有行為與已核准差異分開記錄。
  • 納入既有系統曾經發生的故障、歷史例外與難以重現的情境。
  • 將已確認的安全、資料、相依項目及失敗復原風險轉換成可執行案例。
  • 記錄資料遷移、部署與正式切換前需要完成的驗證結果。

每項內容都要有明確的預期結果。像是「完成整合測試」只能說明採用了某種測試層級,無法判斷哪些需求已經受到保護。測試範圍應該使用需求或風險識別連接案例,讓失敗結果能回到原本要確認的條件。

建立可以追溯的測試策略

單一功能的案例紀錄已經包含初始狀態、輸入、預期結果與清除方式。整體測試策略要進一步記錄案例在系統層級的用途、執行位置及阻擋條件。

需求或風險識別 案例識別 測試目的與層級 執行條件 正式切換條件
REQ-IMP-01 E2E-001 端對端測試,確認有效匯入流程 使用受控輸入檔案與獨立輸出位置 必須通過
RISK-DUP-01 REG-014 迴歸測試,確認重複執行結果 從相同初始狀態執行兩次 必須通過
DIFF-IMP-02 UAT-001 驗收測試,確認核准差異 使用已確認的混合內容案例 需要指定責任者確認

實際紀錄還要包含案例版本、目標程式版本、測試資料版本、執行環境、實際結果與處理狀態。同一個案例識別應該出現在測試名稱與結果紀錄中,避免自動化測試和驗收文件各自使用不同名稱。

為了比較不同測試層級,後續 Vitest 範例使用同一個已確認情境:目標系統會在同一執行環境中讀取匯入檔案、保存有效內容、記錄無效內容,並且避免相同檔案重複產生結果。範例中的函式名稱、輸入格式與預期值都要按照實際規格替換。

選擇符合技術條件的測試框架

測試框架負責執行案例、準備測試條件、比較結果及產生報告。選擇時應該先配合目標系統使用的程式語言、建置方式與執行環境,再比較斷言、測試準備、參數化、模擬及擴充能力。

測試框架 適用技術 主要能力 選擇時要確認的內容
Vitest JavaScript、TypeScript 與 Vite 生態系 提供斷言、模擬、快照、涵蓋率及瀏覽器模式,並且支援 TypeScript 與 JSX 確認專案轉換設定、執行環境,以及整合或端對端測試所需的實際相依項目
pytest Python 提供詳細斷言結果、自動尋找測試、測試準備函式、參數化及外掛擴充 確認測試準備函式的範圍、清除方式、外掛版本及非同步程式的支援方式
JUnit Java 與其他 JVM 語言 透過 JUnit Platform 與 JUnit Jupiter 提供測試生命週期、斷言、參數化及擴充模型 確認 Java 版本、建置工具整合,以及既有 JUnit 測試的遷移方式
NUnit .NET 語言 提供測試類別、斷言、參數化案例、測試前後處理及多種執行方式 確認 .NET 版本、測試執行器、平行執行設定與測試狀態清除方式

這四種工具都能支援多種測試目的,但不會自動產生正確的驗收條件。案例仍要來自已確認的需求、風險與既有行為。相依項目模擬、容器化測試環境、瀏覽器操作或效能負載如果需要其他工具,也應該按照實際情境補充,不能只因為測試框架可以執行程式就省略真實互動驗證。

使用單元測試保護獨立規則

單元測試(Unit Test)驗證可以獨立執行的功能規則、資料轉換或狀態判斷。它應該能快速執行,並且在失敗時指出是哪一項規則不符合預期。時間、隨機值或相依項目如果會影響結果,需要先建立可控制的邊界。

下列 Vitest 範例只驗證數量與單價的計算規則,不載入檔案,也不保存結果:

import { expect, test } from 'vitest'
import { calculateAmount } from './calculate-amount.js'

test('UNIT-001:數量與單價會產生正確總額', () => {
    const result = calculateAmount({
        quantity: 3,
        unitPrice: 120,
    })

    expect(result).toBe(360)
})

如果這項測試失敗,可以直接檢查計算規則。它不負責確認檔案能否讀取或結果能否保存,這些互動要由其他測試層級處理。

使用整合測試確認實際互動

整合測試(Integration Test)驗證組成項目與實際相依項目能否按照規格互動。如果系統包含保存機制、檔案交換、訊息傳遞或外部互動,測試要確認交換內容、失敗處理與狀態變化,不能只檢查模擬函式曾經被呼叫。

下列情境已確認系統會把匯入結果保存成 JSON 檔案。測試使用實際檔案系統,並且在完成後清除暫存內容:

import { expect, test } from 'vitest'
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { saveImportResult } from './save-import-result.js'

test('INT-001:匯入結果會寫入指定檔案', async () => {
    const directory = await mkdtemp(join(tmpdir(), 'import-result-'))
    const filePath = join(directory, 'result.json')

    try {
        await saveImportResult(filePath, { imported: 2 })
        const saved = JSON.parse(await readFile(filePath, 'utf8'))

        expect(saved).toEqual({ imported: 2 })
    } finally {
        await rm(directory, { recursive: true })
    }
})

這項測試能發現檔案路徑、編碼、序列化與寫入行為的問題。其他保存方式應該使用相應的實際元件,不需要為了沿用範例而改成檔案。

使用契約測試保護輸入輸出

契約測試(Contract Testing)確認兩個項目對交換內容具有一致理解。契約可能存在於函式公開介面、結構化檔案、訊息或其他已確認的互動方式,不限定為特定網路介面。

契約至少要描述必要欄位、型別、語意、允許值、錯誤格式及版本相容規則。提供內容與讀取內容的兩端都要使用相同契約,否則單獨驗證其中一端仍可能留下不相容差異。

下列 Vitest 範例確認第一版匯入結果具有必要欄位與允許狀態:

import { expect, test } from 'vitest'
import { createImportResult } from './create-import-result.js'

test('CONTRACT-001:匯入結果符合第一版契約', () => {
    const result = createImportResult({ imported: 2, rejected: [] })

    expect(result).toMatchObject({
        schemaVersion: 1,
        status: expect.stringMatching(/^(completed|partial|failed)$/),
        imported: expect.any(Number),
        rejected: expect.any(Array),
    })
    expect(result.imported).toBeGreaterThanOrEqual(0)
})

toMatchObject 允許結果加入契約沒有禁止的新欄位。如果交換規格要求欄位完全一致,測試就要改用嚴格比較。是否允許擴充應該由版本相容規則決定。

使用端對端測試確認關鍵完整流程

端對端測試(End-to-End Test)從系統實際入口觸發操作,經過主要組成項目後檢查輸出、保存狀態與外部影響。它適合保護少量關鍵流程,不適合承擔所有條件組合,因為完整環境的準備成本較高,失敗時也需要更多資訊才能定位原因。

下列 Vitest 範例從公開匯入入口讀取固定檔案,並且確認執行結果及實際輸出檔案:

import { expect, test } from 'vitest'
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { runImport } from './run-import.js'

test('E2E-001:有效輸入會完成匯入並保存結果', async () => {
    const directory = await mkdtemp(join(tmpdir(), 'import-flow-'))
    const outputPath = join(directory, 'result.json')
    const inputPath = new URL('./fixtures/valid-records.csv', import.meta.url)

    try {
        const result = await runImport({ inputPath, outputPath })
        const saved = JSON.parse(await readFile(outputPath, 'utf8'))

        expect(result.status).toBe('completed')
        expect(saved.imported).toBe(2)
    } finally {
        await rm(directory, { recursive: true })
    }
})

這項案例只保護一條重要完整流程。邊界值與拒絕條件仍應該優先放在較快、責任更集中的測試中,再為跨項目失敗或正式切換必要情境補充端對端案例。

將既有案例組成迴歸測試

迴歸測試(Regression Testing)是一種測試目的,可以包含單元、整合、契約及端對端案例。修正缺陷、調整結構或更新相依項目後,重新執行相關案例,確認先前通過的行為沒有出現非預期變化。

既有系統的所有結果不會自動成為迴歸基準。需要保留的行為沿用既有預期,已核准差異則使用目標結果。尚未確認的差異要保留為未決項目,不能先寫成通過斷言。

下列 Vitest 範例保護曾經修正的重複匯入問題:

import { expect, test } from 'vitest'
import { createImportState, importFile } from './import-file.js'

test('REG-014:相同檔案重複執行不會增加結果', async () => {
    const state = createImportState()
    const input = {
        fileId: 'file-001',
        rows: [{ code: 'A01', quantity: 2 }],
    }

    await importFile(input, state)
    await importFile(input, state)

    expect(state.records).toEqual([{ code: 'A01', quantity: 2 }])
})

案例識別 REG-014 應該連回原始故障、修正內容與受影響規則。如果相關規則也出現在其他完整流程,還要重新執行對應的整合或端對端案例。

透過使用者驗收測試確認目標需求

使用者驗收測試(User Acceptance Testing, UAT)確認目標系統能支援已確認的實際操作方式。這項測試需要由需求決策者、操作人員或其他已指定的驗收責任者判斷結果是否符合需求,特別是遺留系統與目標系統預定不同的地方。

可以先將可客觀判斷的部分自動化,讓每次驗收使用相同輸入與預期結果。下列 Vitest 範例執行已確認的混合內容案例:

import { expect, test } from 'vitest'
import { runApprovedImportFlow } from './run-approved-import-flow.js'

test('UAT-001:混合內容會完成有效項目並列出拒絕原因', async () => {
    const result = await runApprovedImportFlow({
        rows: [
            { row: 1, code: 'A01', quantity: 2 },
            { row: 2, code: '', quantity: 1 },
        ],
    })

    expect(result).toEqual({
        status: 'partial',
        imported: 1,
        rejected: [{ row: 2, reason: 'code_required' }],
    })
})

自動化結果只能說明程式符合已寫入的條件。驗收責任者仍要確認案例代表實際操作方式、拒絕原因可以支援後續處理,以及這項差異已經獲得核准。驗收紀錄需要保存確認人員、版本、案例與結果。

按照回饋速度與風險配置測試組合

測試金字塔(Test Pyramid)可以協助思考測試數量與執行成本。通常會以較多快速且範圍集中的測試保護功能規則,再用較少的整合與端對端測試確認實際互動。不過,這是一項配置參考,不是所有系統都必須遵守的固定比例。

測試組合要同時考量下列因素:

  • 快速測試是否能在修改後立即指出受影響規則。
  • 整合測試是否涵蓋實際採用的保存、交換及相依方式。
  • 契約測試是否能在兩端整合前發現不相容變更。
  • 端對端測試是否集中保護正式切換所需的關鍵流程。
  • 測試執行時間、環境準備與失敗調查成本是否能長期負擔。

程式碼涵蓋率只能協助找出沒有執行到的路徑。高涵蓋率不代表斷言正確,也無法說明重要需求與風險已經受到保護。判斷缺漏時,應該先檢查需求與風險追溯表,再將涵蓋率作為補充資訊。

控制測試環境、資料與相依項目

測試結果要能重現,執行前提就必須可以重新建立。每項測試應該使用已知初始狀態,固定會影響結果的設定、時間與隨機值,完成後清除產生內容。測試資料要保留會影響規則的格式、關聯與邊界情況,同時避免包含不需要的正式敏感內容。

無法穩定控制的相依項目可以先使用測試替身(Test Double),重現正常結果、拒絕條件、逾時或其他已確認的失敗。測試替身只能確認目標程式如何處理預定回應,不能證明實際互動一定相容。因此,正式切換前仍要在受控環境中執行必要的實際整合測試。

測試失敗時,要先判斷原因屬於目標系統缺陷、案例問題、環境問題、相依項目異常或已核准差異。只靠重複執行直到通過,會隱藏結果不穩定或狀態未清除等問題。修正後應該從相同初始條件重新執行,並且保存處理結果。

將專項測試納入整體門檻

單元、整合、契約與端對端測試主要描述功能與互動範圍。目標系統如果具有資料遷移、安全保護、效能容量、失敗復原或部署後檢查需求,還要將相應的專項測試結果納入整體通過條件。相關方法由前面或後續章節說明,本章只確認它們能連回需求、風險與正式切換門檻。

Vitest 可以讀取專項工具產生的結構化結果並執行門檻斷言,但不會因此變成效能負載或安全檢查工具。以下範例只確認容量測試報告符合已核准的處理時間與失敗比例:

import { expect, test } from 'vitest'
import { readCapacitySummary } from './read-capacity-summary.js'

test('PERF-GATE-001:容量測試結果符合核准門檻', async () => {
    const summary = await readCapacitySummary('./reports/capacity.json')

    expect(summary.p95Milliseconds).toBeLessThanOrEqual(800)
    expect(summary.failureRate).toBeLessThanOrEqual(0.01)
})

報告仍要由適合的專項工具與受控情境產生,並且記錄工具版本、資料規模、執行條件與原始結果。只把人工填寫的數值交給 Vitest 比較,無法證明專項測試已經正確執行。

判斷整體測試是否通過

目標系統具備正式切換條件前,至少要確認下列結果:

  • 重要需求、既有行為、核准差異與已確認風險都有對應案例及執行結果。
  • 單元、整合、契約與端對端測試的責任及適用條件已經明確區分。
  • 阻擋性案例全部通過,未完成項目及允許差異都有明確處理決定。
  • 使用者驗收測試已由指定責任者確認關鍵操作方式與目標結果。
  • 適用的資料遷移、安全、效能、復原及部署後測試已經符合核准條件。
  • 每項失敗都已分類、處理並從相同條件重新驗證。
  • 案例、目標程式、測試資料、執行環境、專項報告與驗收結果都能追溯版本。

適合自動執行的測試還要記錄執行前提、必要頻率與失敗時的阻擋條件。如何把這些測試放入持續整合(Continuous Integration, CI)、發布與部署流程,會由後續章節進一步說明。

重點整理

  • 完整測試策略要從需求、既有行為、核准差異與已確認風險建立範圍,並且讓案例、版本、環境與結果都能追溯。
  • 單元測試保護獨立規則,整合測試確認實際互動,契約測試保護交換內容,端對端測試則驗證少量關鍵完整流程。
  • Vitest、pytest、JUnit 與 NUnit 分別適合不同技術條件,測試框架只能協助執行案例,驗收條件仍要來自已確認的需求與風險。
  • 迴歸測試會跨越不同測試層級保護既有行為,已核准差異則要使用目標系統的新預期結果。
  • 使用者驗收測試可以自動化客觀條件,但關鍵操作方式與核准差異仍要由指定責任者確認。
  • 測試金字塔與程式碼涵蓋率可以協助調整測試組合,不能取代需求涵蓋、風險涵蓋及實際結果判斷。
  • 測試環境、資料與相依項目要能重複建立、彼此隔離並且完整清除,測試替身也要搭配必要的實際整合測試。
  • 資料遷移、安全、效能、復原與部署後檢查等專項測試要按照實際需求納入正式切換門檻。

上一篇
[Day 20] 如何管理系統異常事件?
下一篇
[Day 22] 系統如何部署?應該使用甚麼工具部署?
系列文
遠古聖遺物改造工程:遺留系統全面重構實務指南22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言