Mở đầu — vì sao bài này quan trọng
Bạn đã đi qua gần trọn con đường: viết assertion, chạy Newman, tích hợp CI/CD, mock server, data-driven testing, GraphQL, WebSocket... Bạn có đủ "gạch" để xây nhà. Nhưng có một sự thật phũ phàng mà tôi muốn nói thẳng: 90% các bộ API test tôi từng review ở các công ty Việt Nam đều chết vì kiến trúc tồi, chứ không phải vì viết assertion sai.
Một collection Postman 300 request nằm phẳng trong một folder, tên request là "Copy of Copy of Login (2)", biến hardcode token trực tiếp trong URL, chạy được trên máy anh A nhưng fail trên máy chị B, và không ai dám sửa vì "đụng vào là hỏng". Đó là bức tranh quen thuộc. Bộ test kiểu này không phải tài sản — nó là nợ kỹ thuật (technical debt) mà cả team phải trả lãi mỗi sprint.
Kiến trúc bộ test (test suite architecture) chính là thứ phân biệt một QA "biết dùng Postman" với một QA "làm chủ automation". Nó quyết định liệu bộ test của bạn có sống được 2 năm, có onboard được người mới trong 1 ngày, có chạy được trên CI trong 3 phút hay không. Bài này không dạy bạn thêm một tính năng Postman nào cả — nó dạy bạn cách sắp xếp tất cả những gì bạn đã học thành một hệ thống bền vững. Đây là bài "kiến trúc sư", không phải bài "thợ xây".
Khái niệm cốt lõi
Test pyramid và vị trí của API test
Trước khi bàn folder structure, phải hiểu API test suite của bạn đang ở tầng nào trong tháp kiểm thử. Kim tự tháp kinh điển: đáy là unit test (nhiều, nhanh, rẻ), giữa là API/integration test (chỗ Postman ngự trị), đỉnh là E2E/UI test (ít, chậm, đắt). Postman suite của bạn nằm ở tầng giữa — nghĩa là nó không nên cố gắng test logic nội bộ của từng hàm (việc của unit test), cũng không nên mô phỏng toàn bộ hành trình người dùng qua UI. Nó tập trung vào hợp đồng và hành vi của API: request đúng cho response đúng, status code đúng, schema đúng, side-effect đúng.
Hiểu điều này giúp bạn tránh cái bẫy phổ biến: nhồi 800 test case vào Postman rồi than nó chạy chậm. Nếu 500 trong số đó thực chất là validation logic nên nằm ở unit test, bạn đã đặt gạch sai tầng.
Phân tầng suite theo mục đích (test tiers)
Một bộ test trưởng thành không phải một khối đồng nhất. Nó được chia theo mục đích chạy:
- Smoke tests — 5-15 request tối quan trọng, chạy sau mỗi deploy để trả lời câu hỏi "hệ thống còn thở không?". Login được, health check OK, một API đọc và một API ghi hoạt động. Phải chạy dưới 60 giây.
- Regression tests — bộ đầy đủ, chạy trước khi release hoặc hàng đêm, phủ mọi endpoint và edge case.
- Integration/contract tests — kiểm tra sự tương thích giữa các service, thường gắn với schema/OpenAPI.
--folder. Bạn không muốn CI pipeline chạy 800 test cho mỗi commit — bạn muốn nó chạy smoke, và để regression cho ban đêm.Nguyên tắc cấu trúc thư mục (folder structure)
Đây là trái tim của bài. Một repo test API tốt trông như sau:
api-tests/
├── collections/
│ ├── 01-smoke.collection.json
│ ├── 02-auth.collection.json
│ ├── 03-products.collection.json
│ └── 04-orders.collection.json
├── environments/
│ ├── dev.postman_environment.json
│ ├── staging.postman_environment.json
│ └── prod.postman_environment.json
├── data/
│ ├── users.csv
│ └── products.json
├── scripts/
│ └── common-tests.js # snippet dùng chung
├── reports/ # output, thường .gitignore
├── newman.config.js
├── package.json
└── README.md
Những nguyên tắc phía sau cấu trúc này quan trọng hơn bản thân cây thư mục:
1. Tách collection theo domain nghiệp vụ, không theo kỹ thuật. Đừng gộp tất cả GET vào một collection, tất cả POST vào một collection khác. Hãy gộp theo tài nguyên nghiệp vụ: auth, products, orders, payments. Vì khi một tính năng thay đổi, mọi test liên quan nằm cùng một chỗ.
2. Đánh số thứ tự (01-, 02-) để kiểm soát thứ tự chạy và thể hiện quan hệ phụ thuộc — auth chạy trước vì các collection sau cần token.
3. Environment tách rời hoàn toàn. Không một giá trị URL hay credential nào được hardcode trong collection. Collection dùng {{base_url}}, {{api_key}} — giá trị nằm trong file environment tương ứng.
4. Test data tách khỏi test logic. File CSV/JSON riêng, để người không code cũng thêm được case.
Naming convention và cấu trúc bên trong collection
Bên trong mỗi collection, folder con nên phản ánh luồng nghiệp vụ hoặc nhóm case. Tên request theo mẫu [METHOD] Mục đích - Kỳ vọng, ví dụ: POST Create order - valid payload → 201, POST Create order - missing sku → 400. Đọc tên là biết case test gì, kỳ vọng gì, không cần mở ra xem.
Tình huống thực tế
Ví dụ 1: Tiki và bài học "collection khổng lồ"
Một team QA (tôi gọi là mô hình kiểu Tiki cho một hệ thống e-commerce lớn) khởi đầu với một collection duy nhất tên "Tiki API Full" chứa 420 request. Ban đầu ổn. Sau 8 tháng, đây là những gì xảy ra: mỗi lần merge, hai QA sửa cùng file .json này và Git conflict liên tục — mà conflict trên file JSON export của Postman gần như không thể resolve thủ công vì nó là một dòng minified khổng lồ. Chạy full suite mất 22 phút, nên không ai dám gắn vào PR pipeline.
Họ tái cấu trúc: tách thành 9 collection theo domain (auth, catalog, cart, checkout, payment, promotion, review, seller, search). Smoke tách riêng 12 request. Kết quả: smoke chạy 40 giây gắn vào mỗi PR; regression đầy đủ chạy ban đêm; Git conflict giảm 90% vì hai người sửa payment và search không còn đụng cùng file.
Bài học: một file JSON lớn không chỉ là vấn đề chạy chậm — nó là vấn đề cộng tác. Kiến trúc tốt là kiến trúc cho phép nhiều người làm việc song song mà không giẫm chân nhau.
Ví dụ 2: Startup fintech và cái bẫy hardcode environment
Một startup fintech ở TP.HCM có bộ test chạy hoàn hảo trên máy của bạn dev lead. Nhưng khi họ tuyển QA mới và cho chạy trên máy khác, mọi thứ fail. Lý do: token, base URL, và merchant ID được hardcode rải rác trong 60 request khác nhau. Bạn dev lead đã "sửa tay" các giá trị này mỗi sáng mà không ai biết.
Tệ hơn, khi chuyển từ sandbox sang môi trường staging thật, họ phải sửa 60 chỗ. Một lần sót một chỗ, test gọi nhầm endpoint sandbox trong lúc chạy trên dữ liệu staging — may là chưa gây thiệt hại.
Họ áp dụng nguyên tắc: zero hardcode. Tạo ba file environment (dev, staging, sandbox), mọi giá trị biến đổi đưa hết vào đó. Secret nhạy cảm (như merchant secret key) không commit vào Git mà inject qua biến môi trường CI (--env-var "secret=$MERCHANT_SECRET"). Từ đó, chuyển môi trường chỉ là đổi một flag -e environments/staging.json.
Bài học: kiến trúc phải giả định rằng bộ test sẽ chạy trên nhiều máy, nhiều môi trường, bởi nhiều người. Nếu nó chỉ chạy được ở một nơi, nó chưa phải automation — nó là script cá nhân.
Ví dụ 3: Grab-style — reusable logic và maintainability
Một team làm hệ thống đặt xe quy mô lớn (mô hình kiểu Grab) có 15 collection. Mỗi collection lặp lại đoạn test giống hệt nhau: check status 2xx, check response time < 800ms, check header Content-Type: application/json, check response có schema hợp lệ. Đoạn này bị copy-paste vào ~600 request. Khi họ muốn đổi ngưỡng response time từ 800ms thành 500ms, họ phải sửa 600 chỗ.
Giải pháp kiến trúc: đưa các assertion chung lên collection-level test script (script chạy sau mỗi request trong collection) và dùng biến cấu hình cho ngưỡng. Logic phức tạp hơn (ví dụ validate schema) được viết thành hàm và lưu trong một biến collection để tái sử dụng. Giờ đổi ngưỡng chỉ ở một chỗ.
Bài học: DRY (Don't Repeat Yourself) không chỉ áp dụng cho code sản phẩm — nó áp dụng cho cả code test. Mỗi đoạn logic lặp lại là một điểm bảo trì bạn tự tạo ra cho tương lai.
Hướng dẫn từng bước
Đây là quy trình dựng một bộ test suite có kiến trúc từ đầu:
Bước 1 — Vẽ bản đồ domain trước khi viết dòng test nào. Ngồi với team, liệt kê các nhóm tài nguyên nghiệp vụ: auth, users, products, orders... Mỗi nhóm sẽ là một collection. Đây là bước "kiến trúc sư" — làm sai ở đây thì mọi thứ sau đều lệch.
Bước 2 — Khởi tạo cấu trúc repo. Tạo cây thư mục như mẫu ở trên. Đưa nó vào Git ngay từ đầu (Postman collection là JSON, hoàn toàn version-control được). Thêm .gitignore cho reports/ và node_modules/.
Bước 3 — Thiết lập environment cho từng môi trường. Tạo dev, staging, prod. Định nghĩa mọi biến: base_url, api_version, timeout_ms. Secret KHÔNG để giá trị thật trong file commit — để trống hoặc placeholder, inject lúc chạy.
Bước 4 — Xây collection smoke trước. Chỉ 5-15 request sống-còn. Đây là lưới an toàn đầu tiên và cũng để bạn kiểm chứng cấu trúc environment hoạt động.
Bước 5 — Xây các collection domain, mỗi collection có folder con theo luồng/nhóm case. Đặt tên request theo mẫu [METHOD] Mục đích → Status kỳ vọng. Trong mỗi collection, đặt phần auth (lấy token) lên đầu.
Bước 6 — Trích xuất logic chung lên collection-level scripts. Các assertion lặp lại (status, response time, content-type) đưa vào Tests script ở cấp collection. Logic phức tạp gói thành hàm tái sử dụng.
Bước 7 — Viết file cấu hình Newman và script npm. Trong package.json, tạo các lệnh rõ ràng:
"scripts": {
"test:smoke": "newman run collections/01-smoke.collection.json -e environments/staging.postman_environment.json",
"test:regression": "newman run collections/*.collection.json -e environments/staging.postman_environment.json -r cli,htmlextra"
}
Bước 8 — Viết README. Đây là bước hay bị bỏ. README trả lời: cách chạy, cấu trúc thư mục nghĩa là gì, quy ước đặt tên, cách thêm test mới. Một bộ test có kiến trúc tốt mà không có README vẫn khó onboard.
Bước 9 — Gắn smoke vào CI cho mỗi PR, regression cho lịch đêm. Kiến trúc phân tier phát huy tác dụng đúng ở đây.
Lỗi thường gặp & mẹo
Lỗi 1 — Over-engineering ngay từ đầu. Đừng tạo 20 collection với 50 folder con cho một API 8 endpoint. Kiến trúc phải tương xứng quy mô. Bắt đầu đơn giản, tách nhỏ khi thực sự đau. Nguyên tắc: tách collection khi nó vượt ~50 request hoặc khi hai người thường xuyên đụng nhau.
Lỗi 2 — Test phụ thuộc thứ tự ngầm. Test B ngầm giả định test A đã tạo dữ liệu và để lại biến. Khi chạy riêng B, nó fail. Mẹo: hoặc làm phụ thuộc tường minh (chained request có ý đồ, đọc bài 11), hoặc mỗi test tự setup và cleanup dữ liệu của mình (independent test).
Lỗi 3 — Không có teardown/cleanup. Test tạo 500 đơn hàng rác mỗi lần chạy, làm bẩn database staging. Thêm bước cleanup ở cuối luồng hoặc dùng dữ liệu có prefix nhận diện (test_auto_...) để dọn định kỳ.
Lỗi 4 — Trộn test data vào test logic. Hardcode "email": "a@b.com" trong body. Khi cần test 10 email, phải sửa 10 request. Đưa data ra file CSV/JSON (bài 15).
Lỗi 5 — Commit secret vào Git. Merchant key, token thật nằm trong file environment được push lên repo. Đây là lỗ hổng bảo mật nghiêm trọng. Luôn tách secret ra biến CI hoặc Postman Vault (bài 21).
Mẹo vàng: coi bộ test như một sản phẩm phần mềm thật sự — nó có kiến trúc, có version control, có code review, có README, có convention. Bộ test được đối xử như "sản phẩm hạng hai" luôn thối rữa; bộ test được đối xử như sản phẩm sẽ sống lâu và tạo giá trị.
Bài tập thực hành
- Tái cấu trúc thực tế. Lấy một collection phẳng bất kỳ của bạn (hoặc tạo giả lập 30 request cho một API e-commerce). Tách nó thành cấu trúc:
smoke,auth,products,orders. Đưa mọi URL/credential vào file environment. Ghi lại: bạn tách được ra bao nhiêu file, xóa được bao nhiêu giá trị hardcode.
- Thiết kế cây thư mục cho một hệ thống thật. Chọn một domain bạn hiểu (ví dụ: hệ thống đặt sân bóng, hay app giao đồ ăn). Vẽ ra danh sách collection theo domain, và với mỗi collection liệt kê folder con. Giải thích tại sao bạn nhóm như vậy.
- Trích xuất logic chung. Tìm ba assertion bạn thường lặp lại (status, response time, schema). Viết chúng thành collection-level test script và một hàm tái sử dụng. Đo xem bạn giảm được bao nhiêu dòng lặp.
- Viết README. Cho bộ test ở bài tập 1, viết một README ngắn: cấu trúc, cách chạy smoke vs regression, quy ước đặt tên, cách thêm test mới. Đưa cho một đồng nghiệp chưa biết bộ test và xem họ có tự chạy được không.
Tóm tắt
Kiến trúc bộ test API không phải là một tính năng Postman — nó là cách bạn tổ chức mọi thứ đã học thành hệ thống bền vững. Bốn trụ cột cần nhớ:
- Phân tầng theo mục đích: smoke (nhanh, mỗi deploy) — regression (đầy đủ, ban đêm) — contract. Đừng bắt CI chạy tất cả mọi lúc.
- Tách theo domain nghiệp vụ, không theo kỹ thuật: collection gộp theo tài nguyên (auth, orders, payments), đánh số theo phụ thuộc.
- Tách biệt ba thứ: logic test — environment — test data. Zero hardcode, zero secret trong Git.
- DRY và maintainability: logic lặp lại đưa lên collection-level; mọi thứ dưới version control, có README, có convention.