測試在持續交付中的角色#

測試是持續交付的品質保障環節。沒有完善的測試體系,快速交付就是「快速製造問題」。

持續交付不是要跳過測試,而是要透過自動化讓測試更快、更可靠、更全面。

測試金字塔#

測試金字塔模型建議測試的分布:

flowchart TB
    subgraph Pyramid["測試金字塔"]
        E2E["端對端測試<br/>(少量)"]
        INT["整合測試<br/>(適量)"]
        UNIT["單元測試<br/>(大量)"]
    end

    E2E --> INT --> UNIT

    style E2E fill:#ffcdd2
    style INT fill:#fff3e0
    style UNIT fill:#c8e6c9
層級數量速度維護成本信心度
單元測試
整合測試中高
端對端測試

程式碼靜態檢查#

靜態程式碼分析是在不執行程式的情況下,透過分析原始碼找出潛在問題。它是測試的第一道防線。

靜態檢查的價值#

統計資料顯示,70% 左右的程式碼邏輯設計和編碼缺陷屬於重複性錯誤,完全可以透過靜態程式碼分析發現和修復。

可以發現的問題類型:

  • 空指針風險
  • 資源洩漏
  • 未使用的變數
  • 錯誤的 API 使用
  • 不良的編碼風格
  • 安全漏洞

SonarQube 實踐#

SonarQube 是業界廣泛使用的程式碼品質管理平台。

核心功能:

功能說明
多語言支援Java、JavaScript、Python、Go 等 20+ 語言
規則管理可自訂檢查規則集
品質門禁設定品質閾值,未達標則失敗
技術債務追蹤和量化技術債務
整合能力IDE 插件、CI/CD 整合

六步實施流程:

flowchart TD
    A["1. 搭建 Sonar 服務"] --> B["2. 組態檢查規則 (Profile)"]
    B --> C["3. IDE 安裝 SonarLint 插件"]
    C --> D["4. CI/CD 整合掃描"]
    D --> E["5. 程式碼審查整合結果"]
    E --> F["6. 發布前品質門禁"]

    style F fill:#c8e6c9

Maven 整合:

mvn sonar:sonar \
  -Dsonar.host.url=http://sonar.internal \
  -Dsonar.projectKey=my-project

品質門禁設定範例:

quality_gates:
  - metric: coverage
    operator: LESS_THAN
    error: 80
  - metric: duplicated_lines_density
    operator: GREATER_THAN
    error: 3
  - metric: blocker_violations
    operator: GREATER_THAN
    error: 0

跳過檢查的處理#

有些情況下需要排除特定程式碼的檢查:

1. 排除整個文件

# sonar-project.properties
sonar.exclusions=**/generated/**,**/vendor/**

2. 排除特定規則

@SuppressWarnings("squid:S1135")  // 忽略 TODO 警告
public void someMethod() {
    // TODO: implement this
}

3. 調整規則等級

將某些規則從「錯誤」降級為「警告」,或完全停用。

跳過規則應該謹慎使用,並記錄原因。過度使用會讓靜態檢查失去意義。

破壞性測試#

破壞性測試(也稱為混沌測試)是通過故意製造故障來驗證系統的健壯性。

為什麼需要破壞性測試#

傳統測試關注「正常情況下系統是否工作」,而破壞性測試關注「異常情況下系統是否能夠正確處理」。

分散式系統的常見弱點:

  • 服務不可用時的回退能力不足
  • 超時設定不合理導致的重試風暴
  • 依賴服務流量過大導致的級聯故障
  • 單點故障引起的整體癱瘓

混沌工程#

混沌工程是在生產系統上進行受控實驗,以建立對系統健壯性的信心。

混沌工程的四個步驟:

  1. 定義「穩定態」

    • 確定系統正常運行的可觀測指標
    • 例如:請求成功率 > 99.9%,延遲 P99 < 500ms
  2. 建立假設

    • 假設在引入故障後,系統仍能維持穩定態
    • 例如:即使一台伺服器宕機,整體服務仍可用
  3. 引入真實故障

    • 伺服器崩潰
    • 網路中斷/延遲
    • 資源耗盡(CPU、記憶體、磁碟)
  4. 驗證假設

    • 比較實驗組和對照組
    • 發現系統弱點

混沌工程原則:

原則說明
使用真實事件模擬真實可能發生的故障
生產環境驗證在真實環境才能發現真實問題
自動化持續將實驗自動化,持續執行
最小爆炸半徑控制影響範圍,降低風險
Netflix Chaos Monkey

Netflix 是混沌工程的先驅,他們開發了 Chaos Monkey 系統。

Chaos Monkey 的工作方式:

  • 在工作日隨機終止生產環境的服務實例
  • 強迫工程師設計能夠容忍故障的系統
  • 持續驗證系統的復原能力

Netflix 的名言:

「避免失敗的最好辦法就是經常失敗。」

Chaos Monkey 家族:

  • Chaos Monkey:隨機終止實例
  • Latency Monkey:注入延遲
  • Chaos Kong:模擬整個區域故障
  • Chaos Gorilla:模擬可用區故障

實施建議:

  1. 從非生產環境開始
  2. 先進行小規模實驗
  3. 建立完善的監控和告警
  4. 準備好回滾方案
  5. 逐步擴大實驗範圍

破壞性測試的執行策略#

破壞性測試具有真實的破壞力,必須謹慎規劃和執行。

測試時機:

  • 單元測試和功能測試之後
  • 在隔離的測試環境中
  • 逐步推進到類生產環境

兩個維度的測試設計:

維度一:特定故障點測試

  • 設計特定的故障場景
  • 驗證系統的錯誤處理邏輯
  • 確認系統能否按預期恢復

維度二:探索性故障測試

  • 壓力測試找出瓶頸
  • 隨機故障注入
  • 鏈路阻斷測試

Mock 與回放技術#

自動化回歸測試面臨三大挑戰:

  1. 測試資料的準備和清理
  2. 分散式系統的依賴
  3. 測試用例的仿真度

Mock 和回放技術可以幫助解決這些問題。

Mock 技術#

Mock 是透過模擬來替代真實依賴的技術。

Mock 帶來的價值:

價值說明
獨立性測試不依賴外部服務
速度Mock 返回比真實呼叫快
可控性可以模擬各種邊界情況
穩定性不受外部服務不穩定的影響

基於對象的 Mock(單元測試):

常用框架:Mockito、EasyMock

// Mockito 範例
@Test
public void testOrderService() {
    // 創建 Mock 對象
    PaymentService paymentService = mock(PaymentService.class);

    // 定義 Mock 行為
    when(paymentService.pay(any(Order.class)))
        .thenReturn(new PaymentResult(true, "SUCCESS"));

    // 執行測試
    OrderService orderService = new OrderService(paymentService);
    OrderResult result = orderService.createOrder(order);

    // 驗證
    assertTrue(result.isSuccess());
    verify(paymentService, times(1)).pay(any());
}

基於服務的 Mock(整合測試):

常用框架:WireMock、MockServer

// WireMock 範例
stubFor(post(urlEqualTo("/api/payment"))
    .withRequestBody(matchingJsonPath("$.orderId"))
    .willReturn(aResponse()
        .withStatus(200)
        .withHeader("Content-Type", "application/json")
        .withBody("{\"success\": true}")));

// 測試程式碼可以正常呼叫 /api/payment

回放技術#

回放技術是記錄真實的用戶請求,然後在測試環境中重放。

記錄用戶請求的方法:

方案一:SLB 層攔截

flowchart LR
    U["用戶"] --> SLB["SLB"]
    SLB --> S["伺服器"]
    SLB -.->|複製| R["記錄器"]

    style R fill:#e3f2fd

方案二:伺服器端攔截

flowchart LR
    U["用戶"] --> S["伺服器"]
    S -.->|軟交換複製| R["記錄器"]

    style R fill:#e3f2fd

回放的多樣性:

模式說明用途
原樣回放按記錄的時間間隔回放功能測試
加速回放壓縮時間間隔壓力測試
篩選回放只回放特定類型請求邊界測試
循環回放重複回放持續測試

回放測試不需要記錄所有請求,抽樣記錄即可。這樣可以保持用例的新鮮度,又能控制存儲成本。

整合 Mock 和回放#

攜程的 Mock Service 實踐:

flowchart TB
    subgraph MS["Mock Service"]
        M1["記錄所有服務的請求/回應"]
        M2["如果有匹配的記錄,直接返回"]
        M3["如果沒有匹配,透傳到真實服務"]
    end

    A["服務 A"] --> MS
    B["服務 B"] --> MS
    C["服務 C"] --> MS

    style MS fill:#e3f2fd

優勢:

  • 無需編碼,組態即可使用
  • 基於真實請求,仿真度高
  • 統一管理,便於維護

測試策略整合#

持續交付中的測試階段#

flowchart TD
    A[程式碼提交] --> B[靜態檢查<br/>SonarQube]
    B --> C[單元測試 + Mock]
    C --> D[整合測試<br/>Mock Service]
    D --> E[端對端測試<br/>真實環境]
    E --> F[回放測試 + 破壞性測試]

    B -.- B1((第一道防線))
    C -.- C1((快速反饋))
    D -.- D1((服務邊界驗證))
    E -.- E1((最終驗證))
    F -.- F1((持續驗證))

    style B fill:#e3f2fd
    style C fill:#e8f5e9
    style D fill:#fff3e0
    style E fill:#fce4ec
    style F fill:#f3e5f5

測試效率最佳化#

策略說明
並行執行同時運行多個測試
分層快取跳過未變化部分的測試
增量測試只測試受影響的程式碼
智能選擇根據變更影響選擇測試用例

總結#

測試是持續交付的品質保障基石,需要多種測試方法的組合。

關鍵要點:

  1. 靜態檢查

    • 是測試的第一道防線
    • 可以發現 70% 的重複性錯誤
    • SonarQube 是主流選擇
  2. 破壞性測試

    • 驗證系統的健壯性
    • 混沌工程是高級實踐
    • 需要謹慎規劃和執行
  3. Mock 和回放

    • Mock 解決依賴和隔離問題
    • 回放提供高仿真測試用例
    • 可以組合使用
  4. 測試策略

    • 遵循測試金字塔
    • 適當組合不同測試方法
    • 持續最佳化測試效率

測試的目標不是追求 100% 覆蓋,而是用合理的成本發現盡可能多的問題。選擇正確的測試策略,比追求數字指標更重要。