---
title: Performance Testing 入門 — k6 vs JMeter vs Locust 該選哪個
description: 給 QA 看的效能測試完整入門。Load / Stress / Spike / Soak 四種類型差異、k6 實戰範例、跟 JMeter / Locust / Artillery 對比、CI 整合策略。
category: automation
tags: [performance, k6, jmeter, locust, load-testing]
date: 2026-06-11
---

# Performance Testing 入門 — k6 vs JMeter vs Locust 該選哪個

「網站要扛得住 5000 人同時上線」是業務常常開的單。**Performance test 不是壓爆它，是知道它什麼時候會爆**。這篇給你 QA 角度的完整入門。

## 4 種效能測試一次看懂

```mermaid
flowchart TB
    A[Load Test<br>負載測試] --> A1[正常使用量<br>持續一段時間]
    B[Stress Test<br>壓力測試] --> B1[超過正常 2-5 倍<br>找崩潰臨界點]
    C[Spike Test<br>尖峰測試] --> C1[突然爆量<br>例如限時搶購]
    D[Soak Test<br>耐久測試] --> D1[正常量但跑 8+ 小時<br>找 memory leak]

    style A fill:#06b6d4,color:#fff
    style B fill:#ef4444,color:#fff
    style C fill:#f59e0b,color:#fff
    style D fill:#a855f7,color:#fff
```

| 類型 | 模擬什麼 | 抓什麼 bug |
|------|---------|-----------|
| **Load** | 正常流量 + 些許 buffer | 平均回應時間、throughput |
| **Stress** | 超量 2-5 倍 | 崩潰點、graceful degradation |
| **Spike** | 突發爆量 | autoscaling、queue 機制 |
| **Soak** | 正常量 8 小時+ | memory leak、connection leak |

**多數團隊只做 Load**，這是 80% 痛點來源。

## 為什麼選 k6

```mermaid
flowchart LR
    Choose{選效能<br>測試工具} --> JMeter[JMeter]
    Choose --> Gatling[Gatling]
    Choose --> Locust[Locust]
    Choose --> k6[k6]
    Choose --> Artillery[Artillery]

    JMeter --> JM["Java + GUI<br>老牌、強但重"]
    Gatling --> GA["Scala<br>強但學習曲線"]
    Locust --> LO["Python<br>容易但效能弱"]
    k6 --> K6["JavaScript<br>輕量、CI 友善"]
    Artillery --> AR["YAML<br>簡單但功能少"]

    style k6 fill:#10b981,color:#fff
    style K6 fill:#10b981,color:#fff
```

| 工具 | 語言 | 學習曲線 | CI 友善 | 1 機可模擬人數 |
|------|------|---------|---------|--------------|
| **k6** | JS | 低 | ⭐⭐⭐ | 30K+ VU |
| Locust | Python | 低 | ⭐⭐ | 5K VU |
| JMeter | XML / GUI | 中 | ⭐ | 10K VU |
| Gatling | Scala | 高 | ⭐⭐ | 50K+ VU |
| Artillery | YAML | 低 | ⭐⭐ | 5K VU |

**新團隊建議 k6**：JS 寫、CI 一行裝、效能最好。

## 30 分鐘從 0 到 1：k6 第一個 test

### 1. 安裝（macOS）

```bash
brew install k6
```

或 Docker：

```bash
docker pull grafana/k6
```

### 2. 寫第一支腳本

```javascript
// test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 50,           // 50 個 virtual users
  duration: '30s',   // 跑 30 秒
};

export default function () {
  const res = http.get('https://staging.example.com/api/products');
  check(res, {
    'status is 200': r => r.status === 200,
    'response time < 500ms': r => r.timings.duration < 500,
    'has data': r => JSON.parse(r.body).length > 0,
  });
  sleep(1);
}
```

### 3. 跑

```bash
k6 run test.js
```

輸出長這樣：

```
✓ status is 200
✓ response time < 500ms
✓ has data

checks.........................: 100.00%
http_req_duration..............: avg=187.3ms p(95)=423ms p(99)=789ms
http_reqs......................: 1487   49.5/s
vus............................: 50
data_received..................: 2.3 MB
```

**關鍵指標**：
- `p(95) = 423ms` — 95% 的 request 在 423ms 內回完
- `p(99) = 789ms` — 99% 在 789ms 內
- `49.5/s` — 每秒處理 49.5 個 request

## 寫好 k6 腳本的 5 個原則

### 1. Stages — 階梯式加壓

```javascript
export const options = {
  stages: [
    { duration: '2m', target: 100 },  // 2 分鐘漸增到 100 VU
    { duration: '5m', target: 100 },  // 維持 5 分鐘
    { duration: '2m', target: 200 },  // 再增到 200
    { duration: '5m', target: 200 },
    { duration: '2m', target: 0 },    // 漸減回 0
  ],
};
```

突然開 1000 個 user 是 stress test 的玩法，**load test 要漸進**。

### 2. Thresholds — 自動 fail

```javascript
export const options = {
  vus: 50,
  duration: '5m',
  thresholds: {
    http_req_duration: ['p(95)<500'],     // p95 必須 < 500ms
    http_req_failed: ['rate<0.01'],       // 錯誤率 < 1%
    'checks{group:checkout}': ['rate>0.95'],
  },
};
```

CI 不過自動退 PR。

### 3. Scenarios — 真實場景

```javascript
export const options = {
  scenarios: {
    browse_users: {
      executor: 'ramping-vus',
      stages: [{ duration: '5m', target: 80 }],
      exec: 'browse',
    },
    checkout_users: {
      executor: 'constant-vus',
      vus: 20,
      duration: '5m',
      exec: 'checkout',
    },
  },
};

export function browse() { /* 瀏覽 */ }
export function checkout() { /* 結帳 */ }
```

80% 在瀏覽、20% 在結帳，**比所有人都做同件事真實**。

### 4. Setup / Teardown

```javascript
export function setup() {
  // 跑測試前：建測試帳號、塞測試資料
  const res = http.post('/api/test-users');
  return { token: res.json().token };
}

export default function (data) {
  http.get('/api/profile', {
    headers: { Authorization: `Bearer ${data.token}` },
  });
}

export function teardown(data) {
  // 清理
  http.del(`/api/test-users/${data.userId}`);
}
```

### 5. Custom Metrics

```javascript
import { Trend } from 'k6/metrics';

const checkoutTime = new Trend('checkout_time');

export default function () {
  const start = Date.now();
  // 跑 checkout 流程
  checkoutTime.add(Date.now() - start);
}
```

業務指標自定義，不只看 HTTP timing。

## 完整實戰範例：模擬電商高峰

```javascript
import http from 'k6/http';
import { check, group, sleep } from 'k6';

export const options = {
  scenarios: {
    normal_browsing: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 200 },
        { duration: '5m', target: 200 },
        { duration: '2m', target: 0 },
      ],
      exec: 'browse',
    },
    checkout_flow: {
      executor: 'constant-vus',
      vus: 50,
      duration: '7m',
      exec: 'checkout',
    },
  },
  thresholds: {
    'http_req_duration{name:product_list}': ['p(95)<300'],
    'http_req_duration{name:add_to_cart}': ['p(95)<500'],
    'http_req_duration{name:checkout}': ['p(95)<1000'],
    'http_req_failed': ['rate<0.01'],
  },
};

const BASE_URL = __ENV.BASE_URL || 'https://staging.example.com';

export function browse() {
  group('Browse', () => {
    http.get(`${BASE_URL}/api/products`, { tags: { name: 'product_list' } });
    sleep(2);
    http.get(`${BASE_URL}/api/products/123`, { tags: { name: 'product_detail' } });
    sleep(3);
  });
}

export function checkout() {
  group('Checkout', () => {
    const r1 = http.post(`${BASE_URL}/api/cart`,
      JSON.stringify({ sku: '123', qty: 1 }),
      { tags: { name: 'add_to_cart' }, headers: { 'Content-Type': 'application/json' } }
    );
    check(r1, { 'cart created': r => r.status === 201 });
    sleep(1);

    const r2 = http.post(`${BASE_URL}/api/orders`,
      JSON.stringify({ cart_id: r1.json().id }),
      { tags: { name: 'checkout' }, headers: { 'Content-Type': 'application/json' } }
    );
    check(r2, { 'order placed': r => r.status === 201 });
    sleep(2);
  });
}
```

跑：

```bash
BASE_URL=https://staging.example.com k6 run --out json=results.json scripts/ecommerce.js
```

## CI 整合（GitHub Actions）

```yaml
name: Performance Test
on:
  schedule:
    - cron: '0 2 * * *'   # 每天凌晨 2 點跑
  workflow_dispatch:

jobs:
  perf:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: grafana/k6-action@v0.3.1
        with:
          filename: perf/ecommerce.js
        env:
          BASE_URL: ${{ secrets.STAGING_URL }}
      - if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: perf-results
          path: results.json
```

**不要每個 PR 都跑** — 太慢、太貴。跑 nightly 或 weekly 即可。

## 結果該怎麼讀（指標解讀）

```mermaid
flowchart TD
    Result[k6 結果] --> Q1{p95 達標?}
    Q1 -->|否| F1[效能不夠<br>找 bottleneck]
    Q1 -->|是| Q2{錯誤率 < 1%?}
    Q2 -->|否| F2[系統不穩<br>看 server log]
    Q2 -->|是| Q3{throughput<br>能持續?}
    Q3 -->|否| F3[降速、queue 滿]
    Q3 -->|是| OK[通過]

    style OK fill:#10b981,color:#fff
    style F1 fill:#ef4444,color:#fff
    style F2 fill:#ef4444,color:#fff
    style F3 fill:#f59e0b,color:#fff
```

### 我看效能報告的順序

1. **Error rate** — 有錯誤就先看這個
2. **p99 latency** — 看最慘的 1%（這才是使用者罵的）
3. **p95** — 主要 SLA 指標
4. **Throughput trend** — 看是不是穩定（崩盤前會先降速）
5. **Server-side metrics**（搭配 Datadog / Grafana）— CPU / RAM / DB query

**只看 average 是新手**。Average 200ms 可能包含 p99 = 5 秒。

## 反模式

1. **本機跑大流量** — 1 機網路打不出 1000 RPS，要用雲端
2. **沒 ramp-up** — 突然 1000 VU 是 stress test 不是 load
3. **打 prod** — 除非你想被 fire
4. **不清理 test data** — 一個月後 staging DB 變垃圾場
5. **沒設 threshold** — 結果出來大家看一下、沒人 own
6. **效能規格未談** — 沒 SLA 不知道過或不過
7. **只跑一次** — 應該 nightly 跑、看趨勢

## 跟其他工具的比較

### k6 vs JMeter

- JMeter 強在 GUI 設計複雜流程、適合非工程師
- k6 強在 code 易維護、CI 整合、效能好
- **新案首選 k6**

### k6 vs Locust

- Locust Python 寫、容易上手
- k6 JS 寫、效能強 5-10 倍
- **重視效能選 k6、重視團隊 Python 熟悉度選 Locust**

### k6 vs Gatling

- Gatling Scala 寫、效能最強
- k6 JS 寫、夠用且容易
- **電商 / 金融大規模選 Gatling、中小團隊 k6 即可**

## 進階主題（之後可以深入）

- **Distributed k6** — 雲端跑、模擬全球流量
- **Browser-based perf** — k6/browser 加 Chromium 跑（真實前端 perf）
- **gRPC / WebSocket 測試** — k6 原生支援
- **整合 APM** — Datadog / New Relic 看 server-side
- **Continuous performance testing** — 整合 SLO 監控

## 給 QA 的關鍵 takeaway

效能測試不是「測一次過了就算」。**它是**：

1. 建立 baseline（系統正常時的 p95）
2. 每次大改動跑、看有沒有 regression
3. 收集 trend、跟 PM 約 SLO
4. 上線後配合 APM 監控

**從一支 k6 腳本開始，3 個月後你會發現自己變不可或缺**。
