結論先講
Smoke Test 是「部署後花 2 分鐘確認系統還活著」,回歸測試是「確認新功能沒有把舊功能搞壞」。 兩個都不能省,但做法完全不同——Smoke Test 要少而精(5-10 個),回歸測試要廣而全(但可以分批跑)。
真實場景:我們的 EC 專案某次部署後,首頁看起來正常,但結帳流程完全壞掉——金流串接的環境變數在部署時被覆蓋。如果有 Smoke Test 跑一次「登入→瀏覽→加入購物車→結帳」,三分鐘就能發現,而不是等客服回報。
Smoke Test:系統還活著嗎?
什麼是 Smoke Test
Smoke Test 這個名稱來自硬體測試——電路板通電後如果冒煙,就知道有問題。軟體的 Smoke Test 也是同一個概念:部署後跑最關鍵的幾條路徑,確認系統基本功能正常。
它是 E2E 測試的子集,但有幾個關鍵差異:
| 特性 | Smoke Test | E2E Test |
|---|---|---|
| 測試數量 | 5-10 個 | 數十到數百個 |
| 執行時間 | 1-3 分鐘 | 10-60 分鐘 |
| 涵蓋範圍 | 只測關鍵路徑 | 測所有使用者流程 |
| 什麼時候跑 | 每次部署後 | PR merge 或 nightly |
| 失敗的意義 | 系統有重大問題,可能要 rollback | 某個功能有 bug |
Smoke Test Checklist:以電商為例
一個電商網站的 Smoke Test Checklist 大概長這樣:
✅ 首頁可以載入(HTTP 200 + 關鍵元素存在)
✅ 使用者可以登入
✅ 商品列表可以載入
✅ 商品詳情頁可以載入
✅ 加入購物車功能正常
✅ 結帳流程可以走到付款頁
✅ API health check 回傳正常
✅ 搜尋功能可以回傳結果
注意:不是測「加入購物車後數量對不對」,只是測「加入購物車這個動作不會 500 error」。
用 Playwright 寫 Smoke Test
// tests/smoke/critical-path.spec.ts
import { test, expect } from '@playwright/test';
const BASE_URL = process.env.SMOKE_TARGET_URL || 'https://staging.example.com';
test.describe('Smoke Tests - Critical Path', () => {
test('首頁載入正常', async ({ page }) => {
const response = await page.goto(BASE_URL);
expect(response?.status()).toBe(200);
await expect(page.locator('nav')).toBeVisible();
await expect(page.locator('[data-testid="product-list"]')).toBeVisible();
});
test('登入流程正常', async ({ page }) => {
await page.goto(`${BASE_URL}/login`);
await page.fill('[name="email"]', 'smoke-test@example.com');
await page.fill('[name="password"]', 'SmokeTestPassword!');
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/dashboard|home/);
});
test('商品可以加入購物車', async ({ page }) => {
await page.goto(`${BASE_URL}/products/1`);
await page.click('[data-testid="add-to-cart"]');
await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');
});
test('API Health Check', async ({ request }) => {
const response = await request.get(`${BASE_URL}/api/health`);
expect(response.status()).toBe(200);
const body = await response.json();
expect(body.status).toBe('ok');
expect(body.database).toBe('connected');
});
test('搜尋功能正常', async ({ page }) => {
await page.goto(BASE_URL);
await page.fill('[data-testid="search-input"]', '測試商品');
await page.press('[data-testid="search-input"]', 'Enter');
await expect(page.locator('[data-testid="search-results"]')).toBeVisible();
});
});Smoke Test 的維護原則
- 數量控制在 10 個以內——超過就不是 Smoke Test 了
- 只測 happy path——不測邊界情況、不測錯誤處理
- 不要依賴特定資料——用專門的 smoke test 帳號
- 執行時間控制在 3 分鐘以內——太慢就失去意義
- 失敗就 rollback——Smoke Test 失敗代表系統有重大問題
回歸測試:新功能有沒有搞壞舊的
什麼是回歸測試
回歸(Regression)= 退步。回歸測試就是確認軟體沒有因為新的變更而「退步」。
每次改 code 都有可能影響到看似無關的功能。回歸測試就是系統性地重新跑一次既有的測試,確保一切正常。
回歸測試策略
不是每次都要跑全部測試。根據變更的範圍,可以選擇不同的策略:
| 策略 | 說明 | 適用場景 |
|---|---|---|
| 全量回歸 | 跑所有測試 | 大版本發布、重構 |
| 選擇性回歸 | 根據改動的模組跑相關測試 | 日常 PR |
| 風險導向回歸 | 優先跑高風險區域的測試 | 時間有限時 |
| 自動影響分析 | 工具自動判斷該跑哪些測試 | 大型專案 |
根據改動檔案決定跑哪些測試
# .github/workflows/regression.yml
name: Targeted Regression
on:
pull_request:
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
auth: ${{ steps.filter.outputs.auth }}
cart: ${{ steps.filter.outputs.cart }}
payment: ${{ steps.filter.outputs.payment }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
auth:
- 'src/auth/**'
- 'src/middleware/auth*'
cart:
- 'src/cart/**'
- 'src/products/**'
payment:
- 'src/payment/**'
- 'src/orders/**'
test-auth:
needs: detect-changes
if: needs.detect-changes.outputs.auth == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test -- --testPathPattern='auth|middleware'
test-cart:
needs: detect-changes
if: needs.detect-changes.outputs.cart == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test -- --testPathPattern='cart|product'
test-payment:
needs: detect-changes
if: needs.detect-changes.outputs.payment == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test -- --testPathPattern='payment|order'CI/CD 整合:什麼時候跑什麼
PR 階段:
├── Unit Test(每次 push)
├── 選擇性回歸(根據改動檔案)
└── 契約測試
Merge to main:
├── 全量回歸
└── Integration Test
部署後(staging/production):
└── Smoke Test
Nightly Build:
├── 全量 E2E
├── 安全掃描
└── 效能測試
Smoke Test vs 回歸測試:完整比較
| 維度 | Smoke Test | 回歸測試 |
|---|---|---|
| 目的 | 確認系統能動 | 確認沒有退步 |
| 範圍 | 關鍵路徑 | 全部或部分功能 |
| 數量 | 5-10 個 | 數十到數百 |
| 時機 | 部署後 | PR 或 merge |
| 失敗意義 | 可能要 rollback | 有 bug 要修 |
| 維護成本 | 低(很少變動) | 中高(隨功能增加) |
| 執行頻率 | 每次部署 | 每次 PR |
實務踩坑經驗
踩坑 1:Smoke Test 太多
曾經有個團隊把 Smoke Test 搞到 50 個,跑 15 分鐘。部署後等 15 分鐘才知道結果,根本失去 Smoke Test 的意義。後來砍到 8 個,3 分鐘搞定。
踩坑 2:回歸測試沒有分層
所有測試都是 E2E 等級,一個 PR 要跑 40 分鐘。後來按照測試金字塔重新分層——大部分邏輯用 Unit Test 覆蓋,E2E 只留關鍵流程,整體降到 8 分鐘。
踩坑 3:Smoke Test 用的測試帳號被刪了
staging 環境定期清資料,Smoke Test 的帳號也被清掉了。解法:Smoke Test 帳號由 seeder 管理,每次部署前先跑 seeder。
常見問題
Smoke Test 失敗了一定要 rollback 嗎?
看嚴重程度。如果是「首頁打不開」那就要。如果是「搜尋功能慢了一點但還能用」,可以先修再部署。但原則上,Smoke Test 裡的每一條都應該是「失敗就是大問題」等級的。
回歸測試套件越來越大怎麼辦?
三個方向:1)用 paths-filter 做選擇性回歸;2)把慢的 E2E 測試移到 nightly build;3)定期檢視測試,刪掉重複或過時的。
可以用 curl 代替 Playwright 做 Smoke Test 嗎?
API 層的 Smoke Test 可以。但如果要測前端渲染(例如「首頁的商品列表有沒有出來」),還是需要瀏覽器。建議兩者混用:API health check 用 curl,關鍵 UI 流程用 Playwright。
Smoke Test 要寫在跟一般測試一樣的 repo 嗎?
看團隊。放在同一個 repo 比較好維護,但要確保 Smoke Test 可以指定不同的 target URL(staging / production)。用環境變數切換就好。