接續第 16 天的話題,並不是所有功能都像「破氣式」那樣,只從核心業務邏輯出發。有時,我們還會面對一種基於資料的功能,例如 API 測試。這種時候,我們需要另一種心法來應對,那就是 Data-Driven Testing。
Data-Driven Testing(資料驅動測試)是一種透過外部化測試數據來驅動測試邏輯的測試方法。它的核心概念是 測試邏輯與測試資料分離,讓同一組測試程式可以針對不同的資料重複執行,達到高覆蓋率、降低維護成本、提升可擴充性。
資料驅動測試(Data-Driven Testing)的核心價值在於將測試邏輯與測試資料分離,這讓我們不必為不同的測試情境重複撰寫程式碼,從根本上解決了程式碼重複的問題。透過這種方式,同一套測試流程能夠應用於多組資料,大幅提升測試覆蓋率,同時也讓測試維護變得更簡單,因為當需要新增或修改測試案例時,我們只需要更新外部的資料檔案,而無需更動任何程式碼。這種設計不僅讓測試腳本更精簡,也為未來的擴充性提供了極大的彈性。
測試資料的來源其實非常多樣,最簡單也最直觀的,是從 CSV、JSON、Excel 或 YAML 這些靜態檔案中讀取,適合用來管理小型的測試案例。當我們需要處理大量或複雜的資料關係時,我會直接從資料庫中讀取,這能確保測試資料與應用程式使用的資料來源一致。如果測試目標是 Web 服務,那麼透過 API 來動態取得或建立測試資料會更有效率,因為這能模擬真實世界中的資料流。不過你也可以用 AI 來即時生成測試資料,特別是在處理一些難以窮舉的邊界值或隨機組合時,這種方式能大幅提高測試的覆蓋度和效率。
假設我們的登入測試資料存放在 login-data.json
:
[
{ "username": "user01", "password": "pass123", "expected": "首頁" },
{ "username": "user02", "password": "wrongpass", "expected": "登入失敗" }
]
測試程式碼:
import { test, expect } from '@playwright/test';
import loginData from './login-data.json';
for (const data of loginData) {
test(`Login with ${data.username}`, async ({ page }) => {
await page.goto('https://your-app.example.com/login');
await page.getByTestId('login-username').fill(data.username);
await page.getByTestId('login-password').fill(data.password);
await page.getByTestId('login-submit').click();
await expect(page.locator(`text=${data.expected}`)).toBeVisible();
});
}
有些情境下,我們需要從資料庫讀取最新的測試資料,例如會員帳號、訂單編號等。
以下示範 PostgreSQL 版本(MySQL、MSSQL 類似):
import { Client } from 'pg';
import { test, expect } from '@playwright/test';
async function getLoginDataFromDB() {
const client = new Client({
host: 'localhost',
port: 5432,
user: 'db_user',
password: 'db_password',
database: 'test_db'
});
await client.connect();
const res = await client.query('SELECT username, password, expected FROM login_test_data');
await client.end();
return res.rows;
}
test.describe('DB Data-Driven Login Test', () => {
let testData;
test.beforeAll(async () => {
testData = await getLoginDataFromDB();
});
for (const data of testData) {
test(`Login with ${data.username}`, async ({ page }) => {
await page.goto('https://your-app.example.com/login');
await page.fill('#username', data.username);
await page.fill('#password', data.password);
await page.click('#submit');
await expect(page.locator(`text=${data.expected}`)).toBeVisible();
});
}
});
當測試資料由後端服務維護時,我們可以直接從 API 取得最新測資。
import { test, expect } from '@playwright/test';
test.describe('API Data-Driven Login Test', () => {
let loginData;
test.beforeAll(async ({ request }) => {
const res = await request.get('https://your-api.example.com/test-data/login');
loginData = await res.json();
});
for (const data of loginData) {
test(`Login with ${data.username}`, async ({ page }) => {
await page.goto('https://your-app.example.com/login');
await page.fill('#username', data.username);
await page.fill('#password', data.password);
await page.click('#submit');
await expect(page.locator(`text=${data.expected}`)).toBeVisible();
});
}
});
在實作資料驅動測試時,我認為有幾個原則非常重要。首先是資料隔離,我們必須確保每組測試資料都是獨立的,避免測試之間互相依賴,不然會因為測試案例執行的先後順序,而影響測試結果,在測試結束後進行清理資料。其次,為了讓團隊能夠共用測試資料協作,將所有的靜態測試資料放在一個集中的地方,例如:版本控管系統或是資料庫中,這不僅方便使用,也能確保每次測試都能使用相同的測試資料集。
另外,在程式碼中避免寫死測試資料,所有資料都應該從外部檔案或資料庫讀取,這樣才能真正實現測試邏輯與資料的分離。在撰寫測試資料時,我們也不能只專注於正常情境,而是要確保測試資料的多樣性,涵蓋正常值、邊界值、錯誤值,甚至極端值。最後,再將這些測試流程與資料集整合到 CI/CD 流程中,讓不同的測試環境能自動對應到不同的測試資料集。
資料驅動測試(Data-Driven Testing)的核心價值在於將測試邏輯與測試資料分離,這讓我們能夠將同一個測試流程,應用於多組不同的資料集,從而快速擴大測試覆蓋率。我發現這種方法特別適合用在表單驗證、API 多參數組合測試,以及所有擁有相同流程但輸入不同的情境。透過結合 JSON、資料庫、API 等多種資料來源,我們不僅讓測試變得更易於維護,也讓它們能夠更靈活、更真實地反映業務需求。