Test Data Management (TDM) 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.
Trước hết, đây là vấn đề cốt lõi mà Test Data Management giải quyết, tức là hai bài test cùng dùng chung một dữ liệu và ghi đè lẫn nhau.

Có một loại lỗi khiến tester mất rất nhiều thời gian debug: test fail nhưng code không sai, và chạy lại thì lại pass. Rất nhiều trường hợp trong số đó do dữ liệu test, chứ không phải do logic của test case.
Hãy nhìn một đoạn test login quen thuộc trong Cypress:

Với một vài test thì không sao. Khi bộ test lên đến hàng trăm case, bốn vấn đề sau sẽ xuất hiện:
- Hardcode rải rác: các chuỗi như
'wronguser'hay'abc123'nằm khắp nơi. Muốn đổi một giá trị, bạn phải tìm và sửa hàng chục file. - Xung đột khi chạy song song: hai spec cùng dùng một tài khoản để tạo đơn hàng, dữ liệu đè lên nhau và test fail dù không có bug.
- Dữ liệu mồ côi: test tạo user mới nhưng không xoá. Sau vài tháng database phình to, và không ai dám dọn vì không biết bản ghi nào an toàn.
- Test phụ thuộc thứ tự: test B chỉ pass khi test A chạy trước và để lại dữ liệu. Đổ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.
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.
Các loại test data bạn cần phân biệt:
| Loại | Ví dụ | Cách quản lí trong Cypress |
|---|---|---|
| Dữ liệu tĩnh | Danh sách quốc gia, mã lỗi, mock response | File JSON trong cypress/fixtures/ |
| Dữ liệu sinh động | User mới, đơn hàng mới | Factory + Faker, tạo bằng API |
| Dữ liệu nền (baseline) | Tài khoản admin, danh mục sản phẩm | Seed vào DB bằng cy.task trước khi chạy |
| Dữ liệu nhạy cảm | Thông tin khách thật | Không dùng. Chỉ dùng dữ liệu giả hoặc đã được mask |
Kĩ thuật 1: Test Data Factory, “nhà máy” sản xuất dữ liệu
Thay vì gõ tay từng field, hãy viết một hàm chuyên tạo dữ liệu mẫu. Field nào quan trọng với test thì override, field nào không quan trọng thì để mặc định.
Tạo file cypress/support/factories/user.factory.ts:
Cài thư viện bằng lệnh npm i -D @faker-js/faker. Cách dùng:
Mỗi lần gọi createUser(), email có timestamp kèm chuỗi ngẫu nhiên nên rất khó trùng. Cách viết này cũng dễ đọc hơn nhiều so với một khối JSON hardcode.
Mẹo thêm: nếu cần tái hiện đúng bộ dữ liệu của một lần chạy bị lỗi, hãy cố định seed bằng faker.seed(12345) ở đầu spec. Khi đó Faker sinh ra cùng một dãy giá trị ở mỗi lần chạy.
Kĩ thuật 2: Tạo dữ liệu qua API thay vì qua UI
Nhiều bạn cần một user để test nên đi đăng kí user đó qua giao diện, click từng nút và điền từng field. Cách này vừa chậm vừa dễ fail, vì phụ thuộc vào UI mà bạn không định kiểm thử.
Quy tắc đơn giản: dùng API để chuẩn bị tiền điều kiện (pre-condition), dùng UI để test đúng chức năng cần test. Nếu Register chính là thứ test case cần kiểm tra thì vẫn đi qua UI.
Cypress có sẵn cy.request() để gọi API mà không cần mở trang. Hình dưới là Test Runner của Cypress, nơi bạn thấy từng lệnh request hiện trong Command Log:

Để tái sử dụng, hãy gói các lời gọi API thành custom command trong cypress/support/commands.ts:
cy.request() mặc định sẽ làm test fail nếu API trả về status không phải 2xx hoặc 3xx. Vì vậy bạn không cần tự viết đoạn kiểm tra response.ok() như trong các framework khác. Riêng lệnh xoá, mình cố ý tắt cơ chế này để bước dọn dẹp không làm hỏng kết quả test.
Dùng trong test:

Phần chuẩn bị dữ liệu giờ chỉ tốn vài chục mili-giây thay vì vài giây click chuột. Lưu ý thêm { log: false } ở ô mật khẩu để password không hiện trong Command Log và video.
Kĩ thuật 3: Dọn dẹp dữ liệu sau khi test
Cypress không có fixture dạng use() để tự teardown, nên ta dùng hook beforeEach và afterEach của Mocha. Để tránh lặp code ở mọi spec, hãy gói chúng vào một hàm tiện ích cypress/support/hooks/useTestUser.ts:


Hook afterEach vẫn chạy khi test fail, nên dữ liệu không bị bỏ sót vì một assertion sai.
Tuy vậy, tài liệu của Cypress cũng cảnh báo rằng afterEach không phải bảo hiểm tuyệt đối. Nếu bạn dừng test giữa chừng trong chế độ cypress open, nếu trình duyệt crash hoặc runner bị kill trên CI, hook có thể không chạy. Vì vậy hãy dùng ba lớp phòng thủ như sơ đồ dưới:

Lớp 2: dọn trước khi chạy. Cypress cung cấp event bef ore:run, chạy một lần ở đầu mỗi lần cypress run. Bạn gọi hàm dọn dữ liệu cũ ở đó, trong cypress.config.ts


Các endpoint ở đây chỉ là ví dụ minh hoạ, bạn hãy thay bằng API admin thật của hệ thống mình.
Kĩ thuật 4: Đặt “namespace” cho dữ liệu test
Khi chạy trên môi trường dùng chung (dev, staging), hãy gắn tiền tố nhận diện cho mọi dữ liệu do auto test tạo ra. Factory ở Kĩ thuật 1 đã đọc tiền tố từ Cypress.env('DATA_PREFIX'), nên bạn chỉ cần truyền giá trị khác nhau theo ngữ cảnh:

Lợi ích:
- Biết ngay dữ liệu nào do ai hoặc lần chạy nào tạo ra, nên không sợ xoá nhầm.
- Job dọn dẹp quét theo tiền tố và tuổi dữ liệu để xoá phần còn sót.
- Có thể dọn chính xác toàn bộ dữ liệu của một lần chạy CI thất bại, vì chỉ cần xoá theo
ci_<run id>.
Kĩ thuật 5: Seed dữ liệu nền bằng cy.task
Có những dữ liệu không nên tạo lại ở mỗi test, ví dụ danh mục sản phẩm, bảng giá, tài khoản admin. Hãy seed chúng một lần trước khi chạy, hoặc dùng cy.task để reset database về trạng thái chuẩn khi cần:

Quy tắc phân vai: dữ liệu nền chỉ đọc, còn dữ liệu mà test cần sửa hoặc xoá thì tạo riêng cho từng test bằng API.
Kĩ thuật 6: Cache đăng nhập bằng cy.session
Khi mỗi test có một user riêng thì phải đăng nhập lại ở mỗi test. cy.session() giúp bạn đăng nhập bằng API một lần cho mỗi user rồi cache cookie và storage, thay vì đi qua form login:

Nhờ đó chỉ test login mới cần đi qua form UI, còn mọi test khác bắt đầu thẳng từ trạng thái đã đăng nhập.
Kĩ thuật 7: Khống chế dữ liệu bằng cy.intercept
Có những dữ liệu bạn không kiểm soát được, như cổng thanh toán, dịch vụ bên thứ ba, hoặc dữ liệu phụ thuộc thời gian. Với chúng, hãy dùng cy.intercept kết hợp file trong cypress/fixtures/ để trả về response cố định:

Kết quả là test không còn phụ thuộc vào tác động bên ngoài, và bạn dễ dàng giả lập các tình huống hiếm như response lỗi hay danh sách rỗng.
Lưu ý khi chạy song song trong Cypress
Cypress song song hoá theo spec file. Khi dùng --parallel với Cypress Cloud hoặc các công cụ tương đương, các spec được chia cho nhiều máy cùng chạy, còn các test trong cùng một spec vẫn chạy tuần tự. Vì vậy:
- Mỗi spec phải tự tạo dữ liệu riêng, không giả định spec khác đã chạy trước.
- Tuyệt đối không để spec này đọc dữ liệu do spec khác tạo ra.
- Từ Cypress 12,
testIsolationmặc định đã bật, nên cookie, localStorage và sessionStorage được xoá sạch giữa các test. Hãy giữ mặc định này thay vì tắt đi cho tiện.
Những lỗi thường gặp
- Dùng chung một tài khoản “vàng” cho mọi test. Tạo tài khoản riêng bằng API cho từng test sẽ an toàn hơn nhiều.
- Phụ thuộc dữ liệu có sẵn trên staging, ví dụ “đơn hàng số 1024”. Chỉ cần ai đó xoá là test vỡ.
- Dọn dẹp bằng cách click qua UI. Việc này chậm và dễ fail đúng lúc bạn cần nó chạy thành công nhất. Hãy dọn bằng API.
- Hardcode token hoặc mật khẩu thật trong code. Hãy đưa vào
cypress.env.json(đã thêm vào.gitignore) hoặc biến môi trường CI. - Dùng dữ liệu thật của khách hàng. Dữ liệu cá nhân phải được mask, hoặc thay hoàn toàn bằng dữ liệu giả từ Faker.
- Chỉ dựa vào
afterEach. Hãy luôn có thêm lớp dọn trước khi chạy và job quét định kì.
Checklist nhanh trước khi merge test
- Dữ liệu được tạo bằng factory, không hardcode.
- Pre-condition được tạo qua API, UI chỉ dùng cho phần cần kiểm thử.
- Mỗi bản ghi tạo ra đều có cách xoá, bằng
afterEachhoặc dọn theo id. - Test chạy đúng khi đổi thứ tự hoặc chạy riêng lẻ (
.only). - Không có dữ liệu nhạy cảm và không có secret trong repo.
Vì sao đáng đầu tư
- Chạy song song an toàn hơn: mỗi test có dữ liệu riêng nên giảm hẳn xung đột.
- Test nhanh hơn: tạo dữ liệu qua API nhanh và ổn định hơn nhiều so với đi qua các bước UI.
- Ít flaky hơn: không còn phụ thuộc vào dữ liệu có sẵn mà ai đó có thể sửa hoặc xoá bất cứ lúc nào.
- Môi trường sạch hơn: dev và staging ít rác, dễ debug hơn.
- Dễ đọc, dễ bảo trì:
createUser({ fullName: 'Nguyen Van A' })rõ ràng hơn 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, trong khi không ít flaky test bắt nguồn từ dữ liệu hoặc shared state chứ không phải từ logic của test case. Chỉ cần ba thói quen là bộ test của bạn khác hẳn: tạo dữ liệu bằng factory, chuẩn bị pre-condition qua API, và dọn dẹp theo nhiều lớp. Bạn không cần làm tất cả cùng lúc. Hãy bắt đầu với Test Data Factory, rồi thêm dần các kĩ thuật còn lại theo nhu cầu của dự án.



