Chào bạn quay lại với series automation test! Bài này mình sẽ nói về một thứ âm thầm nhưng lại cực kì quan trọng: Test Data.
Nhớ lại ví dụ Login ở 2 bài trước:
await loginPage.login('wronguser', 'wrongpass');
Nhìn thì ổn, nhưng khi dự án lớn dần, bạn sẽ gặp những vấn đề quen thuộc sau:
- Hardcode tràn lan: chuỗi 'wronguser', 'abc123'... nằm rải rác khắp hàng trăm test, đổi 1 cái lại phải tìm và sửa khắp nơi
- Test cùng lúc: 2 test cùng chạy song song, cùng dùng tài khoản user@test.com để tạo đơn hàng, dữ liệu bị đè lên nhau, test fail dù code chẳng có lỗi gì
- Dữ liệu "mồ côi": test tạo ra một user mới, chạy xong không dọn dẹp, database ngày càng phình to, sau vài tháng không ai dám xoá vì không rõ cái nào an toàn
- Test phụ thuộc thứ tự: test B chỉ pass nếu test A đã chạy trước đó và để lại dữ liệu sẵn - cực kì mong manh, đổi thứ tự chạy là vỡ trận
Tất cả những vấn đề này đều bắt nguồn từ việc KHÔNG quản lí dữ liệu test một cách có chủ đích. Đó là lí do cần Test Data Management. Đây là cách bạn chủ động tạo ra, sử dụng, và dọn dẹp dữ liệu phục vụ cho test - thay vì để nó trôi nổi ngẫu nhiên hoặc phụ thuộc vào dữ liệu có sẵn trong hệ thống.
Trong phạm vi ở bài này, mình sẽ tập trung vào 3 tiêu chí quan trọng:
- Độc lập - mỗi test tự tạo dữ liệu riêng, không đụng vào dữ liệu của test khác.
- Nhất quán - chạy lần nào cũng ra kết quả như lần đó, không phụ thuộc "may rủi" dữ liệu có sẵn.
- Sạch sẽ - chạy xong tự dọn dẹp, không để lại rác trong hệ thống.
Kĩ thuật 1: Test Data Factory - “nhà máy” tạo dữ liệu test
Thay vì gõ tay từng field, ta viết hàm chuyên tạo dữ liệu mẫu, muốn field nào khác thì override lại.
// factories/user.factory.ts
import { faker } from '@faker-js/faker';
export interface User {
email: string;
password: string;
fullName: string;
}
export function createUser(overrides: Partial<User> = {}): User {
return {
email: `auto_${Date.now()}_${faker.number.int({ min: 1000, max: 9999 })}@example.com`,
password: 'Test@12345',
fullName: faker.person.fullName(),
...overrides,
};
}
Dùng trong test:
const user = createUser({ fullName: 'Nguyen Van A' });
// Các field khác như email/password được factory tự tạo giá trị mặc định
Mỗi lần gọi createUser(), email sẽ được gắn timestamp kèm một số ngẫu nhiên giúp giảm nguy cơ các test sử dụng trùng dữ liệu. Cái gì không quan trọng với test hiện tại (như password) thì để mặc định, cái gì quan trọng (fullName) thì override.
Kĩ thuật 2: Tạo dữ liệu qua API thay vì qua UI
Nhiều người hay mắc lỗi này: muốn có 1 user để test, lại đi... đăng kí user đó qua giao diện (click từng nút, điền từng field). Vừa chậm, vừa dễ fail vì phụ thuộc UI.
Cách tốt hơn: chỉ nên dùng API để chuẩn bị pre-condition. Nếu chức năng Register là thứ testcase muốn kiểm thử thì vẫn cần thực hiện qua UI.
// helpers/api.ts
import { APIRequestContext } from '@playwright/test';
import { User } from '../factories/user.factory';
export async function registerUserViaApi(request: APIRequestContext, user: User) {
const response = await request.post('https://example.com/api/users', {data: user});
if (!response.ok()) {
throw new Error( `Failed to create test user: ${response.status()}` );
}
return response.json(); // trả về user vừa tạo, kèm id
}
Dùng trong test:
test('login with a freshly created account', async ({ page, request }) => {
const user = createUser();
await registerUserViaApi(request, user);
const loginPage = new LoginPage(page);
await page.goto('https://example.com/login');
await loginPage.login(user.email, user.password);
// assert đăng nhập thành công...
});
Test chạy nhanh hơn nhiều, vì phần "chuẩn bị data" không còn tốn thời gian click qua UI - để dành UI focus đúng phần cần test.
Kĩ thuật 3: Dọn dẹp dữ liệu sau khi test xong
Playwright có sẵn cơ chế fixture rất hợp để làm việc này - tạo dữ liệu trước test, và tự động dọn sau khi test xong dù pass hay fail.
// fixtures/user.fixture.ts
import { test as base } from '@playwright/test';
import { createUser, User } from '../factories/user.factory';
import { registerUserViaApi, deleteUserViaApi } from '../helpers/api';
export const test = base.extend<{ testUser: User }>({
testUser: async ({ request }, use) => {
const user = createUser();
const created = await registerUserViaApi(request, user);
await use(user); // <-- test chạy ở đây, dùng testUser
await deleteUserViaApi(request, created.id); // dọn dẹp sau khi test xong
},
});
Giờ test chỉ cần khai báo testUser là có ngay dữ liệu sẵn sàng, không cần lo dọn dẹp:
import { test } from '../fixtures/user.fixture';
test('login with a freshly created account', async ({ page, testUser }) => {
const loginPage = new LoginPage(page);
await page.goto('https://example.com/login');
await loginPage.login(testUser.email, testUser.password);
// assert đăng nhập thành công...
});
Đây chính là điểm hay: dù testcase fail thì phần teardown sau use() vẫn được thực thi để dọn dẹp - dữ liệu không bị bỏ sót lại trong hệ thống. Tuy nhiên nếu process bị crash đột ngột, cleanup có thể không chạy - đây là lí do ta vẫn nên có cách nhận diện test data để dọn dẹp định kì.
Kĩ thuật 4: Đặt "namespace" cho dữ liệu test
Nếu test trên môi trường dùng chung (staging, dev...), hãy gắn một tiền tố nhận diện cho mọi dữ liệu do test tạo ra, ví dụ prefix auto_ hoặc gắn timestamp:
email: `auto_${Date.now()}_${faker.number.int({ min: 1000, max: 9999 })}@example.com`
Lợi ích:
- Có thể viết script dọn dẹp định kì các user có prefix auto_ và đã tồn tại quá một khoảng thời gian an toàn, nếu chúng bị sót lại do test crash
- Dev/Tester khác nhìn vào database biết ngay đâu là dữ liệu chạy auto test, không sợ xoá nhầm.
Lí do nên đầu tư vào Test Data Management
- Test chạy song song an toàn hơn - mỗi test có dữ liệu riêng, giảm nguy cơ chồng chéo nhau.
- Test nhanh hơn - tạo data qua API thường nhanh và ổn định hơn việc đi qua nhiều bước UI
- Giảm dữ liệu rác - giúp dev/stg sạch hơn và dễ debug hơn
- Test ổn định hơn (ít flaky) - không còn phụ thuộc vào "dữ liệu có sẵn trong database" vốn có thể bị ai đó xoá hay sửa bất cứ lúc nào.
- Dễ đọc, dễ maintain - createUser({ fullName: 'Nguyen Van A' }) rõ ràng hơn nhiều so với một khối JSON hardcode dài dòng.
Lời kết
Dữ liệu test thường bị xem nhẹ so với code test, nhưng thực tế không ít flaky test bắt nguồn từ dữ liệu hoặc shared state, chứ không hẳn do logic test case sai. Đầu tư một chút vào Test Data Factory, tạo dữ liệu qua API, và cơ chế dọn dẹp tự động sẽ giúp bộ test suite của bạn chạy nhanh hơn, ổn định hơn. Chúc bạn quản lí dữ liệu test gọn gàng!

