獨孤九式總結,將近花費十天講述如何撰寫可維護性的測試程式碼,其目的是為了讓 AI 能夠撰寫可維護的測試程式碼,並且理解什麼是可維護性的測試案例。
根據專案經驗,自動化測試案例會隨著功能改變而需要動態調整。如果測試程式碼相依性很高,可能會導致明明只修改一個功能,卻連帶使其他測試案例失敗,甚至出現 Flaky Test 的情況。這不僅會讓測試人員花費大量時間排查問題,也可能影響整個團隊對自動化測試的信任。一個良好的測試案例應當是易於維護的,主要基於以下幾點:
接下來,我們來討論會導致測試案例難以維護的常見問題,也就是「反模式 (Anti-Pattern)」:
await page.click('#submit123'); // 當元素 ID 一修改,所有相關測試案例都會失敗。
await page.waitForTimeout(3000); // 不穩定,且增加了不必要的執行時間。
// login.spec.ts
test('User login', async ({ page }) => {
await page.goto('https://app.example.com/login');
await page.fill('#username', 'user01');
await page.fill('#password', 'pass123');
await page.click('#login-btn');
await expect(page).toHaveURL('https://app.example.com/home');
});
這段程式碼的主要問題在於:Selector 寫死、登入流程與測試邏輯耦合,且無法重用。
// pages/LoginPage.ts
export class LoginPage {
constructor(private page) {}
async goto() {
await this.page.goto('/login');
}
async login(username: string, password: string) {
await this.page.getByTestId('login-username').fill(username);
await this.page.getByTestId('login-password').fill(password);
await this.page.getByTestId('login-submit').click();
}
}
// login.spec.ts
import { LoginPage } from '../pages/LoginPage';
test('User login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user01', 'pass123');
await expect(page).toHaveURL('/home');
});
Page Object Model 的價值在於它能在測試邏輯和使用者介面細節之間建立一道防火牆,帶來以下三大優勢:
#login-btn
改為 #sign-in-button
,你只需要在 LoginPage.ts
這個單一檔案中修改。所有依賴它的測試案例都會自動更新,大大降低了維護成本。loginPage.login()
,讓你的測試程式碼更簡潔,也更容易重複使用。data-testid
作為穩定的選擇器:data-testid
是一個專為測試而生的 HTML 屬性,它提供一個穩定的錨點給測試框架,讓你的測試腳本不易因 UI 外觀改變而中斷。await page.waitForTimeout()
,改用 狀態斷言,例如 expect(page).toBeVisible()
或 expect(page).toHaveURL()
。它們會智慧地等待直到條件滿足,確保測試的穩定與效率。測試的可維護性與重構,是確保自動化測試能真正發揮長期價值的關鍵。當你的測試程式碼變得易於閱讀、修改與重用時,它將不再是負擔,而成為團隊快速迭代、自信發布的堅實後盾。