獨孤九劍的「破掌式」用來破解世間各種拳、腳、指、掌的功夫,包括長拳短打、擒拿點穴、鷹爪、虎爪、鐵沙掌等拳腳功夫。根據拳、腳、指、掌,而有不同的破解方法。就算是貓拳、狗拳、女朋友的拳頭,都有不同的破解策略。而在自動化測試領域,也有類似的概念,根據不同的演算法和流程,但驗證相同的行為。今天想來說說策略模式。
Strategy Pattern (策略模式) 是一種行為類別的設計模式,主要是執行測試的時候動態選擇不同的演算法或流程實作,而不需要改變測試程式碼的流程。在撰寫自動化測試程式碼,這種模式特別適合應用在「測試相同的功能,但是有不同的測試流程」,舉例來說:
假設我們要測試「用戶註冊」功能,在不同情況下,我們可能想用不同的策略來建立用戶:
// strategies/UserCreationStrategy.ts
export interface UserCreationStrategy {
createUser(username: string, password: string): Promise<void>;
}
// strategies/UiUserCreation.ts
import { Page } from '@playwright/test';
import { UserCreationStrategy } from './UserCreationStrategy';
export class UiUserCreation implements UserCreationStrategy {
constructor(private page: Page) {}
async createUser(username: string, password: string) {
await this.page.goto('https://your-app.example.com/register');
await this.page.fill('#username', username);
await this.page.fill('#password', password);
await this.page.click('#submit');
await this.page.waitForSelector('text=註冊成功');
}
}
// strategies/ApiUserCreation.ts
import { APIRequestContext } from '@playwright/test';
import { UserCreationStrategy } from './UserCreationStrategy';
export class ApiUserCreation implements UserCreationStrategy {
constructor(private request: APIRequestContext) {}
async createUser(username: string, password: string) {
await this.request.post('/api/users', {
data: { username, password }
});
}
}
假設我們用 PostgreSQL 當資料庫,透過 pg 套件直接插入測試用戶。
// strategies/DbUserCreation.ts
import { Client } from 'pg';
import { UserCreationStrategy } from './UserCreationStrategy';
export class DbUserCreation implements UserCreationStrategy {
private client: Client;
constructor() {
this.client = new Client({
host: process.env.DB_HOST ?? 'localhost',
port: Number(process.env.DB_PORT ?? 5432),
user: process.env.DB_USER ?? 'db_user',
password: process.env.DB_PASS ?? 'db_password',
database: process.env.DB_NAME ?? 'test_db',
});
}
async createUser(username: string, password: string) {
await this.client.connect();
await this.client.query(
'INSERT INTO users (username, password) VALUES ($1, $2)',
[username, password]
);
await this.client.end();
}
}
// tests/user-registration.spec.ts
import { test } from '@playwright/test';
import { UiUserCreation } from '../strategies/UiUserCreation';
import { ApiUserCreation } from '../strategies/ApiUserCreation';
import { DbUserCreation } from '../strategies/DbUserCreation';
test('使用不同策略建立用戶', async ({ page, request }) => {
let strategy;
switch (process.env.USER_CREATION_STRATEGY) {
case 'ui':
strategy = new UiUserCreation(page);
break;
case 'api':
strategy = new ApiUserCreation(request);
break;
case 'db':
strategy = new DbUserCreation();
break;
default:
throw new Error('請設定 USER_CREATION_STRATEGY=ui|api|db');
}
await strategy.createUser('testUser', 'pass123');
// 後續驗證,例如登入成功
await page.goto('https://your-app.example.com/login');
await page.fill('#username', 'testUser');
await page.fill('#password', 'pass123');
await page.click('#login-submit');
await page.waitForSelector('text=登入成功');
});
使用策略模式可以減少重複程式碼並且每個策略都可以獨立開發與測試,甚至在新增策略不需要修改太多的測試程式碼,這樣的方式也適合使用多樣的測試環境或是有多種資料建立方式的情況。但如果策略數量過多,可能會導致程式碼的複雜度,如果使用在簡單的測試流程中,可能會讓程式碼更難以閱讀。
Strategy Pattern 在測試自動化中,特別適合需要在「完整流程驗證」與「快速資料建立」間切換的場景。它能提升測試的彈性與維護性,但需要控制策略數量與抽象層級,避免過度設計。