iT邦幫忙

2026 iThome 鐵人賽

DAY 9
1
Software Development

不會寫程式,我照樣做效能測試 - Claude Code × k6 的 30 天系列 第 9

Day 9|測試資料的兩難:參數化,以及你製造的髒資料怎麼辦

  • 分享至 

  • xImage
  •  

一、全員用同一筆資料,會測出三種假象

目前為止我們的腳本,每個 VU 送的內容都一模一樣:同樣的披薩條件、同一個帳號、同一筆查詢。小流量練習沒問題,但在真實系統上,100 個 VU 全用同一筆資料,測出來的數字會系統性失真——而且失真的方向不只一種。

https://ithelp.ithome.com.tw/upload/images/20260907/20161809es9UtOEGXf.png
圖 1:共用資料的三種假象——快取讓系統假快、鎖衝突讓它假慢、業務規則讓它假壞

• 假快:同一筆資料被查第二次起就從快取(cache,系統暫存最近用過的資料的地方)回應,你測到的是快取的速度,資料庫根本沒被考驗。真實使用者每個人查的都不一樣,快取幫不上那麼多忙。
• 假慢:同一筆資料被同時更新,資料庫為了保證正確性會排隊上鎖(lock),一個改完下一個才能改。等待時間被你當成了系統效能,但真實世界裡 100 個人不會同時改同一筆訂單。
• 假壞:同帳號重複下單、重複領券,被業務規則正當地擋下,錯誤率飆高——但那不是效能問題,是你的測試資料違反了系統設計。

真實現場常見的樣子
最常見的劇本:QA 交出「錯誤率 15%,系統有問題」的報告,RD 花半天追查,最後發現 15% 全是「此優惠券已使用」。雙方都白忙一場,而且下次 QA 再交報告時,RD 已經先打了折扣。
另一種更難抓:報告說「P95 只要 80ms,系統很快」,上線當天卻塞爆——因為壓測時 10,000 次請求全命中同一筆快取,資料庫從頭到尾在睡覺。

三種假象共同的解法就是今天的主題之一:參數化——讓每個虛擬使用者拿到不同的資料,像真實世界一樣。

二、參數化:SharedArray 讓每個 VU 各拿各的

2.1 先講概念:什麼是 SharedArray

k6 的標準做法是把測試資料放在外部檔案(通常是 JSON 或 CSV),用 SharedArray 載入。「Shared」的意思是:所有 VU 共用同一份唯讀資料(記憶體裡只有一份,不會 100 個 VU 各複製一份),但每個 VU 可以依自己的編號去拿不同的那一筆。

你可以把它想成辦公室的公用檔案櫃:檔案只有一套,大家都可以來翻,但每個人翻的是不同頁。

https://ithelp.ithome.com.tw/upload/images/20260907/201618095sj0Jli1y2.png
圖 2:SharedArray 的運作方式——一份資料,每個 VU 依編號取不同的一筆

2.2 動手做:請 Claude Code 改造 pizza 腳本

直接把下面這段貼給 Claude Code。注意最後一句「請解釋」——這是給不寫程式的人最重要的一句,它會讓 Claude Code 把每個改動用白話講一遍,你才有辦法判斷它做的對不對。

Prompt 1|生成資料檔並改造腳本

請做兩件事:
1. 建立 pizza-data.json:一個 JSON 陣列,20 筆不同的披薩條件,
   每筆包含 maxCaloriesPerSlice(300 到 1000 之間的不同值)
   和 mustBeVegetarian(true 或 false 交錯)。
2. 修改 pizza-api-test.js:用 SharedArray 載入這個檔案,
   每一輪依 VU 編號與輪次取不同的一筆當作請求內容。
   請保留原本的 options、check 和 threshold 設定,不要動。
 
完成後請用白話解釋修改的每一段,特別是「怎麼決定這一輪用哪一筆」。
另外請告訴我:如果我想改成「每個 VU 固定用同一筆」,要改哪一行?

Claude Code 改造後的關鍵段落大致長這樣(你的版本可能略有不同,重點是結構一樣):

import { SharedArray } from 'k6/data';
 
// init 階段載入一次,所有 VU 共用這份唯讀資料
const pizzaData = new SharedArray('pizza 條件', function () {
  return JSON.parse(open('./pizza-data.json'));
});
 
export default function () {
  // 策略一:每個 VU 固定用同一筆(模擬「每人有自己的帳號」)
  // const item = pizzaData[(__VU - 1) % pizzaData.length];
 
  // 策略二:每一輪換一筆(資料多樣性最大化)
  const item = pizzaData[(__VU * 1000 + __ITER) % pizzaData.length];
 
  const payload = JSON.stringify({
    maxCaloriesPerSlice: item.maxCaloriesPerSlice,
    mustBeVegetarian: item.mustBeVegetarian,
  });
  // ...後續與原本相同
}

這段程式在做什麼(逐段白話版)
https://ithelp.ithome.com.tw/upload/images/20260907/201618092H6viSzEAH.png

兩種取用策略,怎麼選
https://ithelp.ithome.com.tw/upload/images/20260907/20161809jTyFferCIh.png

公式裡的 * 1000 是為了讓不同 VU 的起點錯開:VU 1 從第 1000 筆開始數、VU 2 從第 2000 筆開始數(再取餘數),避免 VU 1 的第 2 輪和 VU 2 的第 1 輪剛好撞到同一筆。數字本身不神聖,只要夠大就行。

2.3 手上的資料是 CSV 或 Excel?

實務上測試資料很少是你手打的 JSON,多半是 RD 匯出的 CSV、或 PM 整理的 Excel。一句話請 Claude Code 轉就好。注意台灣的經典坑:舊版 Excel 存出來的 CSV 常是 Big5 編碼,直接讀會變亂碼。

Prompt 1b|把 CSV 轉成測試資料檔

我有一個 users.csv(在同一個資料夾),欄位是 username、password、memberLevel。
請把它轉成 users.json 給 k6 的 SharedArray 用。
注意:這個 CSV 可能是 Big5 編碼,如果轉出來有亂碼,請以 Big5 讀取後轉存成 UTF-8。
轉完請印出前 3 筆讓我確認內容正確,並告訴我總共幾筆。

為什麼要「印出前 3 筆」?因為你看不懂程式,但你看得懂資料。轉檔對不對,看資料最快。

2.4 唯一值:零依賴的生成法

有些欄位絕對不能重複——訂單編號、信箱、使用者名稱。如果 100 個 VU 都用 test@example.com 註冊,第二個開始就會被擋「此信箱已註冊」(這就是第一節的「假壞」)。

最簡單的做法不需要任何外掛:用時間戳加上 VU 編號與輪次組合,天然不重複,而且一眼看得出是哪次測試、哪個 VU、第幾輪產生的:

https://ithelp.ithome.com.tw/upload/images/20260907/201618092suvwhFmcH.png
圖 3:唯一值的四段組成——前綴、時間戳、VU 編號、輪次

Prompt 1c|加上唯一值

請在 pizza-api-test.js 裡加一個唯一值產生方式:
格式是 PERF_ 加上目前時間戳(毫秒)、加上 VU 編號、加上輪次,
例如 PERF_1755683200123_VU7_I42。
另外產生一個測試用信箱,格式 perf_時間戳_VU_輪次@test.example.com。
暫時先把這兩個值加進請求的 header 裡(X-Test-Id),不要改 payload。
請解釋為什麼這樣組合就不會重複。
// 例:PERF_1755683200123_VU7_I42
const uniqueId = `PERF_${Date.now()}_VU${__VU}_I${__ITER}`;
 
// 用在信箱之類的欄位
const email = `perf_${Date.now()}_${__VU}_${__ITER}@test.example.com`;

Date.now() 會回傳「現在距離 1970 年 1 月 1 日過了幾毫秒」,是一個 13 位數字,每毫秒都不同。用反引號(鍵盤左上角、數字 1 左邊那個符號)包起來的字串裡,${...} 的部分會被換成實際的值——這叫「樣板字串」,你可以把它想成 Excel 裡用 & 把「PERF_」、時間戳、VU 編號串接起來的公式。

開頭那個 PERF_ 前綴不是裝飾——它是下一節髒資料防線的第一道。

業界怎麼處理唯一值(補充)
https://ithelp.ithome.com.tw/upload/images/20260907/20161809RYhMaQMW4J.png

三、髒資料:寫入型測試的善後責任

讀取型測試(GET)跑完就船過水無痕,寫入型完全是另一回事:POST 會真的建立訂單、真的扣庫存、真的寄通知信。一次 30 分鐘的中等流量測試,在共用環境留下上萬筆資料是常態——污染別人的功能測試、灌爆報表、觸發庫存警戒。Day 6 講的告知義務裡「資料影響」那一欄,就是為這件事存在的。

https://ithelp.ithome.com.tw/upload/images/20260907/20161809JdO2HSsDQW.png
圖 4:髒資料三道防線——可辨識、自動清理、講好規則

• 可辨識:所有測試資料掛統一前綴(上一節的 PERF_ 開頭)。看得出來,才清得掉;報表端也才排得除。
• 自動清理:清理寫進測試本身,測完自動執行——這就是下一節 teardown 的工作。
• 講好規則:有些環境有還原快照、定期重建機制,那清理策略完全不同——先問,別自己猜。

3.1 業界常見的四種處理方式
「講好規則」之前,你得先知道有哪些規則可以講。實務上大致有四種做法,由省事到講究排列:

https://ithelp.ithome.com.tw/upload/images/20260907/20161809msDF72U2ZP.png
圖 5:業界常見的測試資料處理方式——多數公司是前兩種混用

https://ithelp.ithome.com.tw/upload/images/20260907/20161809Gm1JZxcxhK.png

重點:你的公司是哪一種,決定了你的腳本長什麼樣

如果是 ③ 或 ④,你的 teardown 只需要印一行「本輪執行編號」供追蹤;
如果是 ① 或 ②,teardown 就要真的去呼叫清理——而清理 API 有沒有、能不能給你用,是第五節那份提問清單要問出來的事。

四、setup 與 teardown:資料準備與清理的正規機制

k6 的測試有完整的生命週期(lifecycle),前後各有一個「整個測試只跑一次」的鉤子(hook,可以理解為「系統在特定時間點會自動來呼叫的函式」)——正是資料準備與清理的家:

https://ithelp.ithome.com.tw/upload/images/20260907/201618094ZJFocNeeI.png
圖 6:k6 生命週期——setup 一次、default 多輪、teardown 一次;資料經由參數在三者間傳遞

https://ithelp.ithome.com.tw/upload/images/20260907/20161809oRZtSwT71M.png

4.1 動手做一個看得見執行順序的版本

Prompt 2|加上 setup 與 teardown

請幫 pizza-api-test.js 加上生命週期:
1. setup():產生一個本輪測試的執行編號(PERF_ 加時間戳),
   印出「測試開始,執行編號是…」,並回傳給後續使用。
2. default(data):沿用原本的請求,但在 console 不要印東西(保持輸出乾淨)。
3. teardown(data):印出「測試結束,本輪執行編號…,若有寫入請依此前綴清理」。
 
請解釋 data 參數是怎麼從 setup 流到 default 和 teardown 的。
另外請幫我準備一個 5 秒、2 個 VU 的短版指令,讓我能快速看到執行順序。

跑一次短版,觀察輸出的順序:

INFO[0000] 測試開始,執行編號:PERF_1755683200123
 
  (中間是正常的測試執行,沒有多餘輸出...)
 
INFO[0006] 測試結束,執行編號:PERF_1755683200123,若有寫入請依此前綴清理

親眼確認三件事:setup 印一次、teardown 印一次、中間不管有幾個 VU 跑了幾輪都沒有多印。前面的 [0000] 和 [0006] 是「測試開始後第幾秒」——你可以看出 teardown 是在所有 VU 跑完之後才執行的。

data 是怎麼流動的
setup 最後那行 return { runId } 是關鍵:k6 會把 setup 回傳的東西原封不動交給每一輪的 default(data) 和最後的 teardown(data)。你在 setup 裡放什麼,後面就能用 data.什麼 拿到。這就是圖 6 裡那兩條虛線。

反過來說,default 裡改了 data 不會影響其他 VU,也不會傳回 teardown——它是「發下去的影本」,不是「共用的原本」。所以真正要傳給 teardown 的東西,一定要在 setup 就準備好。

4.2 真實系統上的 setup 與 teardown 長什麼樣

練習版只是印字。在真實系統上,setup 的典型工作是建立測試帳號、取得共用 token(登入後拿到的通行證)、做一次健康檢查;teardown 則是依前綴呼叫刪除 API。把下面這段給 Claude Code,它會幫你把骨架搭好,你只要填進公司的 API 路徑:

Prompt 2b|真實系統版的 setup 與 teardown(骨架)

請把 pizza-api-test.js 的 setup 和 teardown 升級成真實系統會用的版本:
 
setup():
  a. 先打一次 GET /health(或我指定的任何 API),如果不是 200 就用 fail() 讓整個測試立刻停止,
     並印出「環境未就緒,測試中止」。
  b. 呼叫 POST /auth/login 用測試帳號登入,取得 token。
  c. 產生執行編號 PERF_ + 時間戳。
  d. 回傳 { runId, token }。
 
default(data):
  每個請求的 header 加上 Authorization: Bearer + data.token,
  以及 X-Test-Run-Id: data.runId。
 
teardown(data):
  呼叫 DELETE /test-data?prefix= + data.runId(先假設有這個端點),
  印出清理結果;如果回傳不是 2xx,印出警告「清理失敗,請人工依 runId 清理」。
 
API 路徑先用上面的假設,我之後會請你改成公司實際的路徑。
請逐段解釋,並特別說明 fail() 和 check() 的差別。
為什麼要先做健康檢查
setup 失敗,整個測試不會跑——這是 k6 刻意的設計。資料沒準備好、環境根本沒起來就開壓,只會得到一堆假錯誤,還要花時間分辨「是系統掛了還是環境沒起」。
讓測試「快速失敗」(fail fast),永遠比「帶病執行 30 分鐘後發現全是垃圾數據」便宜。

和 Day 6 止血流程直接相關的細節

按一次 Ctrl+C 是「溫和停止」:k6 會讓進行中的請求跑完,然後執行 teardown。連按兩次是「強制中斷」:立刻砍掉,teardown 不會執行。所以急停過的測試,清理要人工補做。這也是為什麼 Day 6 說止血包含「確認恢復」,現在你知道它還包含「確認清理」。

五、動手做:生成你的「資料清理提問清單」

第三道防線「講好規則」需要一場和開發團隊的對話。讓 Claude Code 幫你把問題準備好,你只要帶著去問:
Prompt 3|生成帶去公司的提問清單

請建立 data-cleanup-questions.md,內容是我要去問開發團隊的
「效能測試資料清理」提問清單,涵蓋:
1. 這個環境有沒有既定的測試資料規範(前綴、專用帳號區段)?
2. 有沒有清理機制或 API?多久跑一次?我能不能呼叫?
3. 環境有沒有還原快照或定期重建?週期是什麼?
4. 哪些資料表有寫入連鎖反應(寄信、扣庫存、觸發報表、通知第三方)?
5. 如果我要新增測試帳號,走什麼流程?誰核准?
6. 報表或 BI 那邊,測試資料是用什麼規則排除的?需要我配合什麼前綴?
 
用繁體中文,每題附一句「為什麼要問」,
並在最後加一個表格,讓我把答案填進去,包含「對應的腳本調整」欄位。

這是本篇第二個帶得走的資產。拿著清單去聊十分鐘,得到的答案會直接決定你的腳本要不要自帶 teardown 清理、前綴要怎麼取、setup 要不要先登入。先問清楚規則,永遠比事後道歉便宜。

拿到答案之後,回來這樣請 Claude Code 改

「開發團隊說:環境每天凌晨重建,有 /api/test/cleanup?prefix= 端點可以呼叫,測試帳號區段是 900001~901000。
請依這些資訊調整 pizza-api-test.js 的 setup 和 teardown,並更新 data-cleanup-questions.md 的答案表格。」
——把「人講的話」原封不動貼給它,讓它去對應到程式,這就是不寫程式也能做效能測試的核心技巧。

六、注意事項:測試資料的紅線與細節
https://ithelp.ithome.com.tw/upload/images/20260907/20161809rFHqvPCdcj.png

給 RD 的一句話

QA 問「有沒有清理 API」的時候,最好的答案是「有,而且效能測試可以呼叫」。
為測試環境提供一個依前綴刪除的端點,成本很低,卻能讓每一次壓測都自帶善後——這比事後手動清資料庫體面得多,也安全得多。

七、觀念驗證:三個問題確認你有帶走今天的重點

  1. 測試錯誤率 15%,一查全是「重複下單被拒」——這是效能問題嗎?根因是什麼?怎麼修?(第一節)
  2. 為什麼唯一值的前綴要統一成 PERF_ 這類固定開頭?它同時服務了哪三件事?(第二、三節)
  3. 同事用連按兩次 Ctrl+C 停掉了一個會寫入的測試——他接下來必須做什麼?為什麼?(第四節、第六節)
    加碼題:你的公司是第三節四種處理方式中的哪一種?如果不知道——那就是你明天要去問的第一個問題。

八、小結

今天解了測試資料的兩難:參數化讓每個 VU 各拿各的資料,破除假快、假慢、假壞三種假象;SharedArray 配 __VU 與 __ITER 是取用的基本功,時間戳前綴則是零依賴的唯一值方案。寫入型測試的善後有三道防線——可辨識、自動清理、講好規則——而業界的四種資料處理方式,決定了你的 setup 與 teardown 要做多少事。記住那個和止血流程綁在一起的細節:強制中斷不會執行 teardown,急停之後,清理要人工補上。

最後再強調一次今天的工作方式:你不需要寫這些程式,你需要的是看懂它在做什麼、用對的話請 Claude Code 改、把團隊告訴你的規則原封不動交給它。

附錄 A:生命週期與取用策略速查

// ── 生命週期骨架 ──────────────────────────
export function setup() {
  const runId = `PERF_${Date.now()}`;
  return { runId };            // 回傳給 default 與 teardown
}
 
export default function (data) {
  // data.runId 可用於前綴、標記
}
 
export function teardown(data) {
  // 依 data.runId 清理;溫和停止會執行,強制中斷不會
}
 
// ── 取用策略 ─────────────────────────────
// 每 VU 固定一筆(帳號類)
data[(__VU - 1) % data.length]
 
// 每輪換一筆(內容多樣性)
data[(__VU * 1000 + __ITER) % data.length]
 
// 標準 UUID(需要時才用,來自官方工具庫)
// import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';
// const id = uuidv4();

附錄 B:本篇 Prompt 速查
https://ithelp.ithome.com.tw/upload/images/20260907/20161809Vyf6A7Q65v.png


上一篇
Day 8|HTTP 請求實戰:把 Postman 和 curl 變成 k6 腳本
下一篇
Day 10|關聯(上):登入拿 token,為什麼這麼難
系列文
不會寫程式,我照樣做效能測試 - Claude Code × k6 的 30 天11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言