iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Claude AI

AI 時代下最值得投資的 UI 自動化:30 天用 Claude Code 學會寫 Playwright系列 第 14

Day14: 資料驅動測試:同一個流程跑十組資料

  • 分享至 

  • xImage
  •  

上一篇的六個表單場景,流程一模一樣,差的只有輸入值和預期。把流程和資料分開——流程寫一次,資料列成表——就是資料驅動測試(Data-Driven Testing),也有人叫參數化測試(Parameterized Testing),Playwright 官方文件用的是後面這個詞。

這裡說的「表」,是一張列著輸入值和預期結果的測試資料表。如果你受過測試案例設計的訓練,等價類、邊界值那套功夫的產出就是它,你早就畫過無數次;還沒畫過也不要緊,〈老功夫直接變現〉那節會帶你從一條驗證規則推導出第一張——而且畫完直接就能跑,不是躺在文件裡等人執行。

概念一張圖

https://ithelp.ithome.com.tw/upload/images/20260813/20161809AxRGgR5Wiu.png
圖 1:流程寫一次,資料換著跑,報告上每列資料一個結果

好處不只是省程式碼。資料驅動把測試的知識集中到一張表上:要加新場景,加一列資料就好,不用碰程式;要審查涵蓋了哪些情況,看表就好,不用讀程式。

對不寫程式的你,這件事的意義是:測試的核心資產(那張表)完全在你的掌控範圍內。

業界怎麼寫:一個迴圈,而不是六份複本

Playwright 官方推薦的寫法很樸素:把資料放進陣列,用迴圈把同一個測試跑過每一筆。你不需要會寫,但看得懂結構,驗收 AI 產出時就有依據:

const cases = [
  { input: '17',   expect: '擋下', message: '年齡須滿 18 歲' },
  { input: '18',   expect: '接受' },
  { input: '40',   expect: '接受' },
  { input: '65',   expect: '接受' },
  { input: '66',   expect: '擋下', message: '年齡上限 65 歲' },
  { input: '30.5', expect: '擋下', message: '請輸入整數' },
  { input: 'abc',  expect: '擋下', message: '請輸入數字' },
  { input: '',     expect: '擋下', message: '此欄位必填' },
];
 
for (const c of cases) {
  test(`年齡驗證 › ${c.input} → ${c.expect}`, async ({ page }) => {
    // 開啟表單 → 填入 c.input → 送出 → 依 c.expect 驗證
  });
}

兩個業界慣例值得注意。第一,測試名稱把資料放進去(17 → 擋下),報告才讀得懂;第二,資料陣列放在測試檔開頭或獨立檔案,審查的人打開就先看到「測了哪些情況」,而不是先看到程式。

老功夫直接變現

https://ithelp.ithome.com.tw/upload/images/20260813/201618096wi0EjM4BC.png
圖 2:年齡欄位的等價類與邊界值分析——一個有效類別、四個無效類別,加上邊界,就是資料表

這張表就是等價類劃分加邊界值分析的標準產出。做過測試案例設計的人對它再熟不過;第一次看到的人,只要記得圖 2 的推導順序:先分類,再取代表,邊界另補兩側各一點。

一個常見的誤解要先拆掉:等價類不是只有「有效」和「無效」兩格。無效那一側往往好幾類——太小、太大、非整數、非數字、空白——各走不同的驗證邏輯,各要一個代表,漏一類就是漏一段程式沒測到。有效那一側也一樣:業務規則有分段的話(例如票價分兒童、成人、敬老),每一段都是獨立的有效類,各取各的代表和邊界。這個年齡欄位剛好只有一個有效類,是題目簡單,不是規則如此。

過去這張表躺在測試案例文件裡,靠你的手一列一列執行。現在,整張表交給 Claude Code。你可以這樣說:
年齡欄位的驗證規則是 18〜65 歲的整數。用資料驅動的方式寫 Playwright 測試,資料如下:17 應擋下並顯示「年齡須滿 18 歲」;18、40、65 應接受;66 應擋下並顯示「年齡上限 65 歲」;30.5 應擋下並顯示「請輸入整數」;「abc」應擋下並顯示「請輸入數字」;空白應擋下並顯示「此欄位必填」。用一個資料陣列跑同一個流程,不要寫八個獨立測試,測試名稱要帶上輸入值和預期。

倒數第二句是關鍵。不特別交代的話,AI 有時會老實產出八份幾乎相同的測試。明確要求「一個資料陣列跑同一個流程」,產出的結構才好維護,也才是上一節那種業界慣例的樣子。

你可能想問:這個年齡欄位是哪來的?老實說,它是教學用的假想規格,你手邊沒有現成的網頁可以照著跑。別急,下一節就把它變成真的。

動手做:先生系統,再生測試

沒有系統可測?那就先生一個。這不是玩笑,而是 AI 時代練功的標準起手式:規格自己開,系統請 Claude Code 做,做完馬上測它。整個過程你只講需求,不寫程式。

第一步,生系統。跟 Claude Code 說:

做一個本地的 HTML 註冊頁 register.html,只有一個年齡欄位和送出鈕。驗證規則是 18〜65 歲的整數,錯誤訊息:未滿 18 顯示「年齡須滿 18 歲」、超過 65 顯示「年齡上限 65 歲」、小數顯示「請輸入整數」、非數字顯示「請輸入數字」、空白顯示「此欄位必填」;通過則顯示「註冊成功」。驗證邏輯抽到獨立的 validate.js。欄位用 type="text" 不要用 type="number"。每個要素加上 data-testid,方便寫測試。

提示詞裡有兩個講究。type="text" 是刻意的:type="number" 會讓瀏覽器直接擋掉 abc,「非數字」那一類就永遠測不到自己寫的驗證邏輯。data-testid 則是業界的定位器慣例:測試抓的是掛勾不是畫面文字,之後改文案不會弄斷測試。

https://ithelp.ithome.com.tw/upload/images/20260813/20161809KTbVMvWtfI.png
圖 3:三十秒生出來的練習系統——擋下與接受兩種狀態,四個 data-testid 掛勾

第二步,生測試。用上一節那段提示詞(八組資料、一個陣列、名稱帶輸入值和預期)。產出的測試檔長這樣,對照著看,結構就是圖 1:

const { test, expect } = require('@playwright/test');
const path = require('path');
const PAGE = 'file://' +
  path.resolve(__dirname, '../site/register.html');
 
const cases = [ /* 八組資料,同前 */ ];
 
for (const c of cases) {
  const name =
    `年齡驗證 › ${c.input || '(空白)'} → ${c.expect}`;
  test(name, async ({ page }) => {
    await page.goto(PAGE);                              // 開啟表單
    await page.getByTestId('age-input').fill(c.input);  // 填入資料
    await page.getByTestId('submit-btn').click();       // 送出
    if (c.expect === '接受') {                           // 驗證結果
      await expect(page.getByTestId('success-message')).toBeVisible();
    } else {
      await expect(page.getByTestId('age-error')).toHaveText(c.message);
    }
  });
}

第三步,跑起來。三個指令:npm install、npx playwright install chromium、npx playwright test。畫面上會是這個樣子:

Running 8 tests using 4 workers
 
  ✓  年齡驗證 › 17 → 擋下
  ✓  年齡驗證 › 18 → 接受
  ✓  年齡驗證 › 40 → 接受
  ✓  年齡驗證 › 65 → 接受
  ✓  年齡驗證 › 66 → 擋下
  ✓  年齡驗證 › 30.5 → 擋下
  ✓  年齡驗證 › abc → 擋下
  ✓  年齡驗證 › (空白) → 擋下
 
  8 passed (4.1s)

第四步,故意弄壞一次。全綠很療癒,但全綠證明不了測試有用——測試的價值要在它變紅的時候才看得到。打開 validate.js,把下界檢查 n < 18 改成 n <= 18,重跑:

  ✓  年齡驗證 › 17 → 擋下
  ✘  年齡驗證 › 18 → 接受
 
     Expected: 「註冊成功」可見
     Received: 錯誤訊息「年齡須滿 18 歲」
 
  1 failed, 7 passed

八組資料裡,只有「18 → 接受」變紅,其他七組不動——邊界值分析取的那個點,精準命中邊界寫錯的 bug。這一刻你會相信:那張表上的每一列,都在守一段特定的邏輯。改回來,再全綠。

資料表放哪裡

資料少,直接寫在測試檔開頭的陣列裡,一眼看盡。

資料多、或想讓不寫程式的同事一起維護,就抽到獨立的 CSV 檔,測試讀檔取資料——這也是 Playwright 官方文件示範的做法,用現成的 csv-parse 套件讀檔,每一列產生一個測試。同事用 Excel 就能增修場景。改造只要一句話:

把年齡測試的資料陣列抽到 data/age-cases.csv,測試改成用 csv-parse 讀那個檔,每一列產生一個測試,行為和測試名稱都維持不變。

改完之後的測試程式跟陣列版幾乎一樣,差別只在資料的來源:一個讀檔案開頭的陣列,一個讀 CSV,迴圈和流程一行都不變。

一個實務提醒:CSV 進了版本控制,誰改了哪一列、什麼時候改的,歷史都查得到。這張表從此不是散落在個人硬碟裡的 Excel,而是跟程式一起被管理的資產。

小心組合爆炸

能輕鬆跑幾十組資料之後,會出現一種誘惑:所有欄位的所有組合,全部排列跑一遍。忍住。

這節一樣做給你玩,而且這次請 AI 當出題者。跟 Claude Code 說:

做一個本地的結帳頁 checkout.html,購物金額固定 1000 元,三個下拉選單:會員等級(一般/VIP/企業,折扣 0%/10%/15%)、付款方式(信用卡/ATM/貨到付款,貨到付款加手續費 30 元)、配送方式(宅配/超商/門市,運費 100/60/0 元)。按「計算應付金額」後顯示總額,計算邏輯抽到 checkout.js,要素加 data-testid。另外,在程式裡偷埋一個只有某三個值同時成立才會觸發的計算錯誤,不要告訴我埋在哪。

系統有了,該寫幾條測試?全組合 3×3×3 = 27 條——看圖 5 左邊那排就知道有多冗:「一般 × 信用卡」這個搭配重複出現了三次,每次只是換個配送方式,再把同樣的折扣和手續費邏輯算一遍。欄位再多一點,數字就失控:五個欄位各三種,全組合 243 條。

https://ithelp.ithome.com.tw/upload/images/20260813/20161809jLfAfCAwUq.png
圖 4:全組合 27 條 vs. 成對組合 9 條——右下角是依判斷手動補上的第 10 條

業界的標準解法是成對組合(pairwise),出發點是一個經驗事實:多數 bug 由單一欄位或兩個欄位的搭配觸發,很少需要三個值同時湊齊。所以不必窮舉,只要保證「任兩個欄位的所有搭配都出現過」——圖 5 右邊 9 條就做到了:9 種會員×付款、9 種會員×配送、9 種付款×配送,全部涵蓋。產生這 9 條不用手算,交給 Claude Code(或微軟的 PICT 這類老牌工具):

結帳頁三個欄位:會員等級(一般/VIP/企業)、付款方式(信用卡/ATM/貨到付款)、配送方式(宅配/超商/門市)。產生成對組合(pairwise)的測試資料,放在 data/checkout-pairwise.csv,用資料驅動跑同一個結帳流程,測試名稱帶上三個值和預期金額。預期金額照提示詞 4 給的規則自己算,不要看 checkout.js 的程式碼。

最後那句不是多餘的謹慎。預期結果必須來自規格,不能來自受測程式——如果 AI 打開 checkout.js 照程式邏輯算預期值,埋的雷會被原封不動抄進預期值裡,測試就成了「驗證 bug 等於 bug」的同義反覆。跑起來:

Running 9 tests using 4 workers
 
  ✓  結帳 › 一般 × 信用卡 × 宅配 → 1100 元
  ✓  結帳 › 一般 × ATM × 超商 → 1060 元
     …(9 條全綠)
 
  9 passed (5.2s)

全綠。但別忘了,系統裡埋著一顆雷——而它還活著。我實際驗過這個劇本:埋的是「企業 × 貨到付款 × 門市」同時成立才走到的錯誤計算,把全部 27 種組合掃一遍,錯的就只有這一條;而 pairwise 挑出的 9 條,剛好沒有排到這個三元組。這就是 pairwise 的盲區:它保證任兩個值的搭配都測過,不保證三個值的特定組合出現。全綠,不代表沒事。

解法不是退回 27 條全組合,而是靠判斷補位:哪些三元組風險高?交易金額的特例、公司政策的禁用組合、出過事故的搭配——把它們手動補進資料表。補上第 10 條「企業、貨到付款、門市、880 元」,重跑:

  ✘  結帳 › 企業 × 貨到付款 × 門市 → 880 元
 
     Expected: 應付金額:880 元
     Received: 應付金額:850 元
 
  1 failed, 9 passed

抓到了:850 元,正是那個寫死金額、忘了加貨到付款手續費的舊特例。收尾:

測試抓到了:企業 × 貨到付款 × 門市 的應付金額算出 850,規格上應該是 880。找出你埋的那個計算錯誤,修掉它,不要動其他邏輯,然後重跑測試給我看 10 條全綠。

這一輪你體驗到的就是完整的分工:機器負責涵蓋兩兩搭配(9 條,便宜),你負責點名危險的三元組(第 10 條,值錢)。

工具讓產生測試變便宜了,挑代表的判斷力反而更值錢——這句話後面還會反覆出現。

今天的練習
• 暖身:在官方示範站(demo.playwright.dev/todomvc)實跑。給 Claude Code 一個資料陣列——五筆不同的待辦文字,包含很長的、含特殊符號的——同一個「新增並驗證出現」流程跑五組,體驗報告上一列五個結果的樣子。
• 把「動手做」那節從頭走一遍——自己對 Claude Code 講需求,把系統和測試生出來,跑到全綠,再弄壞一次看它變紅。講需求的能力,才是這個系列真正要練的。
• 組合爆炸那節的結帳系統也照樣走一遍:請 Claude Code 生系統並偷埋一個三元組 bug,跑 9 條 pairwise 全綠,補第 10 條抓到它,修好系統,再全綠。
• 換真實戰場:挑你們系統一個有驗證規則的欄位;手邊沒系統的話,公開練習站 practicesoftwaretesting.com 的註冊表單有生日、電話、密碼一堆規則可以挖。做等價類加邊界值分析表(記得無效類別通常不只一種,三欄:輸入值、分類、預期),交給 AI 轉成資料驅動測試。
• 把資料抽到 CSV,自己用 Excel 加一列新場景,重跑,看新場景自動出現在報告上。這個瞬間你會懂資料驅動的爽感。


上一篇
Day13: 表單測試:輸入、驗證與錯誤訊息
系列文
AI 時代下最值得投資的 UI 自動化:30 天用 Claude Code 學會寫 Playwright14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言