結論先講

Smoke Test 是「部署後花 2 分鐘確認系統還活著」,回歸測試是「確認新功能沒有把舊功能搞壞」。 兩個都不能省,但做法完全不同——Smoke Test 要少而精(5-10 個),回歸測試要廣而全(但可以分批跑)。

真實場景:我們的 EC 專案某次部署後,首頁看起來正常,但結帳流程完全壞掉——金流串接的環境變數在部署時被覆蓋。如果有 Smoke Test 跑一次「登入→瀏覽→加入購物車→結帳」,三分鐘就能發現,而不是等客服回報。


Smoke Test:系統還活著嗎?

什麼是 Smoke Test

Smoke Test 這個名稱來自硬體測試——電路板通電後如果冒煙,就知道有問題。軟體的 Smoke Test 也是同一個概念:部署後跑最關鍵的幾條路徑,確認系統基本功能正常。

它是 E2E 測試的子集,但有幾個關鍵差異:

特性Smoke TestE2E 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 的維護原則

  1. 數量控制在 10 個以內——超過就不是 Smoke Test 了
  2. 只測 happy path——不測邊界情況、不測錯誤處理
  3. 不要依賴特定資料——用專門的 smoke test 帳號
  4. 執行時間控制在 3 分鐘以內——太慢就失去意義
  5. 失敗就 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)。用環境變數切換就好。

本系列文章

  1. 測試策略(一):測試金字塔
  2. 測試策略(二):Unit vs Integration
  3. 測試策略(三):E2E + 壓力測試
  4. CD 整合
  5. API 契約測試
  6. 安全測試
  7. Smoke + 回歸測試(本篇)
  8. 測試資料管理
  9. 視覺回歸測試
  10. AI 輔助測試
  11. ISO 29119)
  12. 測試相關憑證與學習路徑