Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là QA Lead của một ví điện tử ở Việt Nam. Đội test của bạn có 40 test case chạy trên giao diện (UI): mở app, đăng nhập, nạp tiền, chuyển khoản, kiểm tra số dư. Mỗi lần regression mất 3 tiếng, và cứ 5 lần chạy thì có 1-2 lần "đỏ" không phải vì bug thật, mà vì một cái nút bị dời vị trí, animation load chậm, hay Appium bị timeout. Sếp hỏi: "Sao test cứ fail vặt hoài vậy?" — và bạn không có câu trả lời thuyết phục.
Vấn đề cốt lõi ở đây không phải năng lực của đội, mà là chiến lược test đặt sai tầng. Phần lớn logic nghiệp vụ của một sản phẩm hiện đại — tính phí chuyển khoản, kiểm tra hạn mức, xác thực OTP, ghi sổ giao dịch — đều nằm ở tầng backend, được gọi qua API (Application Programming Interface). UI chỉ là lớp áo ngoài hiển thị lại kết quả. Nếu bạn chỉ test qua UI, bạn đang cố gắng kiểm chứng logic backend một cách gián tiếp, chậm chạp và mong manh.
Bài này tập trung vào một câu hỏi chiến lược mà mọi QA Leader phải trả lời được: làm thế nào để xây một chiến lược test API bài bản — từ việc quyết định test cái gì ở tầng API, chọn loại kiểm thử nào (functional, contract, schema, security), đến cách tổ chức test API để chạy nhanh, ổn định và tích hợp vào pipeline. Đây là kỹ năng phân biệt một đội QA "click chuột" với một đội QA thực sự hiểu kiến trúc hệ thống.
Khái niệm cốt lõi
API testing là gì và nằm ở đâu trong bức tranh chiến lược
API testing là việc gửi request trực tiếp tới các endpoint của backend (thường qua HTTP: REST, GraphQL, hoặc gRPC) và kiểm chứng response — về mặt trạng thái (status code), cấu trúc dữ liệu (schema/body), header, thời gian phản hồi và các hiệu ứng phụ (như bản ghi được tạo trong database). Bạn không cần trình duyệt, không cần chờ giao diện render.
Trong mô hình Test Pyramid (kim tự tháp kiểm thử), API test nằm ở tầng giữa — thường gọi là integration/service test. Nó rộng hơn và nhanh hơn UI test, nhưng bao phủ được nhiều luồng nghiệp vụ hơn unit test. Một chiến lược QA khỏe mạnh thường dồn phần lớn kiểm chứng nghiệp vụ vào tầng API này, chỉ giữ lại một số ít test UI cho các luồng đầu-cuối (end-to-end) quan trọng nhất.
Vì sao nên test API tách riêng khỏi UI
Đây chính là phần ý tưởng gốc của bài. Có bốn lý do chiến lược:
1. Nhanh hơn nhiều lần. Một API test gọi thẳng endpoint và nhận JSON, thường chỉ mất vài chục đến vài trăm mili-giây. Một UI test tương đương phải khởi động trình duyệt/emulator, render trang, chờ element, click — thường 10-30 giây. Với một bộ regression 500 test, khác biệt là giữa 5 phút và 3 tiếng.
2. Ổn định hơn (ít flaky). UI test dễ "gãy vặt" vì phụ thuộc vào layout, CSS, animation, thời gian load. API test chỉ phụ thuộc vào hợp đồng dữ liệu (data contract) — vốn thay đổi ít hơn giao diện nhiều. Một cái nút đổi màu không làm hỏng API test.
3. Bao phủ được kịch bản khó hoặc không thể tái tạo từ UI. Đây là điểm mạnh dễ bị bỏ quên. Từ giao diện, bạn rất khó gửi một malformed request (thân request sai định dạng, thiếu trường bắt buộc, kiểu dữ liệu sai) vì UI thường đã validate sẵn. Bạn cũng khó test rate limiting (giới hạn số request/giây), test xác thực/phân quyền (gọi API của người khác bằng token của mình — lỗi IDOR), hay test các mã lỗi 4xx/5xx mà UI đã "che" bằng thông báo thân thiện. Ở tầng API, bạn tự do gửi bất kỳ payload nào.
4. Contract testing — bảo vệ ranh giới giữa các dịch vụ. Trong kiến trúc nhiều dịch vụ, frontend và backend (hoặc service A và service B) giao tiếp qua một "hợp đồng": tên trường, kiểu dữ liệu, mã lỗi. Contract testing kiểm chứng rằng hai bên vẫn tôn trọng hợp đồng đó. Khi backend lỡ đổi user_id từ số sang chuỗi, contract test bắt lỗi ngay — trước khi nó làm sập frontend trên production.
Các loại API test cần phân biệt trong chiến lược
Một chiến lược tốt không gộp tất cả làm một. Bạn cần chủ đích chọn tầng kiểm chứng:
- Functional testing: đúng nghiệp vụ chưa? Gửi request hợp lệ, kiểm chứng response đúng như mong đợi (ví dụ: chuyển 100k, số dư giảm đúng 100k + phí).
- Schema/contract validation: response có đúng cấu trúc đã cam kết không? Đúng kiểu dữ liệu, đủ trường bắt buộc, không thừa trường lạ. Thường validate bằng JSON Schema hoặc file OpenAPI/Swagger.
- Negative & boundary testing: gửi dữ liệu sai, thiếu, ở biên (số âm, chuỗi quá dài, ký tự đặc biệt). Kiểm chứng API trả về mã lỗi đúng và thông báo rõ ràng — không sập 500.
- Authorization testing: token hết hạn, token của user khác, không có token. Đây là lớp phòng thủ IDOR/broken-access-control cực kỳ quan trọng với fintech.
- Sequencing/stateful testing: nhiều API gọi nối tiếp phụ thuộc nhau (tạo đơn → thanh toán → hủy). Kiểm chứng trạng thái được duy trì đúng qua các bước.
Tình huống thực tế
Ví dụ 1 — Ví điện tử "MoMoPay" (giả định): dời logic test xuống API
Đội QA của MoMoPay có 320 UI test cho luồng thanh toán, chạy mất 2 giờ 40 phút mỗi đêm và tỷ lệ flaky khoảng 12%. Mỗi sáng, hai QA phải dành 45 phút "phân loại" xem test đỏ nào là bug thật, đỏ nào là flaky. Đội quyết định làm audit: họ phát hiện 78% các test case thực chất chỉ đang kiểm chứng logic backend (tính phí, hạn mức, ghi sổ), chỉ có 22% thật sự cần UI (hiển thị đúng số dư, luồng bấm nút OTP).
Họ chuyển 250 test xuống tầng API, viết bằng REST Assured (Java) và tích hợp vào CI. Kết quả sau 6 tuần: bộ API test chạy trong 6 phút, flaky giảm còn dưới 2%, và họ giữ lại đúng 45 UI test cho các luồng đầu-cuối trọng yếu. Quan trọng hơn: nhờ test API, họ bắt được một bug mà UI không bao giờ lộ ra — khi gửi số tiền âm (amount: -50000) qua request thô, API cũ vẫn xử lý và ghi có nhầm cho người nhận. Từ UI thì không thể nhập số âm nên bug này đã ẩn suốt 8 tháng.
Bài học: phần lớn giá trị test nằm ở tầng API. Hãy audit xem mỗi test đang thực sự kiểm chứng cái gì, rồi đặt nó vào đúng tầng. Và luôn test những payload mà UI không cho phép gửi.
Ví dụ 2 — Sàn TMĐT khu vực Đông Nam Á: contract testing cứu một release
Một sàn thương mại điện tử có đội frontend (web) và đội backend làm việc độc lập, deploy riêng. Một sprint nọ, đội backend "dọn dẹp" API danh mục sản phẩm, đổi trường price từ số nguyên (đơn vị đồng) sang một object { "amount": 199000, "currency": "VND" } cho gọn gàng hơn — và nghĩ rằng đã báo cho frontend.
Trước đây, một thay đổi như vậy sẽ chỉ bị phát hiện khi web production hiển thị "giá: [object Object]" cho hàng nghìn người dùng. Nhưng đội đã đưa contract test vào pipeline: frontend định nghĩa một "consumer contract" mô tả họ mong đợi price là số nguyên. Khi backend build bản mới, contract test chạy và fail ngay ở CI với thông báo: "Trường price mong đợi kiểu integer, nhận được object." Release bị chặn tự động, hai đội ngồi lại thống nhất phiên bản API mới (/v2/products) và giữ /v1 tương thích ngược.
Bài học: trong kiến trúc nhiều dịch vụ/nhiều đội, contract testing là lớp bảo vệ ranh giới. Nó biến các "hiểu lầm ngầm" giữa các đội thành lỗi build tường minh, phát hiện ở thời điểm rẻ nhất — trước khi lên production.
Ví dụ 3 — Startup fintech: test rate limiting và bảo mật qua API
Một startup cho vay tiêu dùng có endpoint gửi OTP: POST /auth/otp/send. Đội QA chỉ test qua app mobile, nơi nút "Gửi lại OTP" bị khóa 60 giây sau mỗi lần bấm — nên họ tin rằng hệ thống an toàn. Một chuyên gia bảo mật thuê ngoài đã gọi thẳng API bằng script, bỏ qua UI, và gửi được 4.000 request OTP trong 1 phút tới cùng một số điện thoại. Backend không có rate limit ở tầng server, chỉ có ở tầng app. Đây vừa là lỗ hổng bị lạm dụng (tốn phí SMS, quấy rối người dùng), vừa là rủi ro brute-force.
Sau sự việc, đội bổ sung một nhóm API test chuyên biệt: gửi liên tục N request và kiểm chứng rằng sau ngưỡng cho phép, API trả về 429 Too Many Requests; đồng thời test gọi endpoint nhạy cảm bằng token của user A để truy cập dữ liệu user B (phải nhận 403 Forbidden).
Bài học: những kịch bản như rate limit, brute-force, phân quyền chỉ test được ở tầng API. Đừng bao giờ tin vào validation phía client như một lớp bảo vệ — kẻ tấn công không dùng UI của bạn.
Hướng dẫn từng bước
Đây là quy trình xây dựng chiến lược test API cho một sản phẩm, dùng được ngay:
Bước 1 — Lập bản đồ API (API inventory). Thu thập tài liệu OpenAPI/Swagger hoặc Postman collection của tất cả endpoint. Với mỗi endpoint, ghi rõ: method, độ nhạy cảm (có động chạm tiền/dữ liệu cá nhân không), tần suất dùng, và ai là "consumer" của nó.
Bước 2 — Xếp ưu tiên theo rủi ro. Không test đều tay mọi endpoint. Ưu tiên các endpoint liên quan tiền, xác thực, dữ liệu cá nhân, và các endpoint được nhiều dịch vụ phụ thuộc. (Bài Risk-Based Testing trong khóa này đi sâu hơn về cách xếp hạng.)
Bước 3 — Định nghĩa các tầng kiểm chứng cho mỗi endpoint. Với endpoint ưu tiên cao, viết đủ: functional (happy path), negative/boundary, authorization, schema validation. Với endpoint ít rủi ro, có thể chỉ cần happy path + schema.
Bước 4 — Chọn công cụ phù hợp với đội. Postman/Newman cho đội thiên manual và muốn nhanh; REST Assured (Java), Playwright APIRequest, pytest + requests, hoặc Karate cho đội code. Contract testing thường dùng Pact hoặc so khớp với OpenAPI. Nguyên tắc: chọn cái đội duy trì được lâu dài, đừng chọn cái "xịn" nhất.
Bước 5 — Chuẩn hóa quản lý dữ liệu và môi trường test. Mỗi test nên tự tạo dữ liệu tiền đề của mình (setup) và dọn sạch sau đó (teardown), để test độc lập và chạy song song được. Dùng biến môi trường cho base URL và token, tránh hard-code.
Bước 6 — Viết assertion nhiều lớp. Đừng chỉ kiểm status code. Một test tốt kiểm: (a) status code, (b) schema/body đúng, (c) giá trị nghiệp vụ đúng, (d) tùy chọn: hiệu ứng phụ trong DB. Ví dụ chuyển tiền: 200 OK + response đúng schema + số dư giảm đúng + có bản ghi giao dịch mới.
Bước 7 — Tích hợp vào CI/CD. Cho bộ API test chạy trên mỗi pull request (bộ smoke nhanh) và chạy đầy đủ hằng đêm. Gắn kết quả vào quality gate: build đỏ thì chặn merge.
Bước 8 — Theo dõi và bảo trì. Đo thời gian chạy, tỷ lệ flaky, độ phủ endpoint. API test cũng cần được refactor như code sản phẩm.
Lỗi thường gặp & mẹo
Lỗi 1 — Chỉ test happy path. Nhiều đội chỉ gửi request đúng và mừng khi nhận 200. Nhưng phần lớn bug và lỗ hổng nằm ở các đường rẽ: input sai, thiếu quyền, dữ liệu ở biên. Mẹo: với mỗi endpoint, viết ít nhất 1 negative test và 1 authorization test.
Lỗi 2 — Chỉ assert status code. Nhận 200 nhưng body rỗng hoặc sai giá trị vẫn là bug. Luôn kiểm chứng cả nội dung và schema.
Lỗi 3 — Test phụ thuộc lẫn nhau và phụ thuộc thứ tự chạy. Nếu test B cần dữ liệu do test A tạo, thì khi A fail hoặc khi chạy song song, mọi thứ đổ vỡ dây chuyền. Mẹo: mỗi test tự lo dữ liệu của mình.
Lỗi 4 — Hard-code token, URL, dữ liệu môi trường. Token hết hạn là cả bộ test đỏ. Dùng cơ chế lấy token động và quản lý cấu hình theo môi trường.
Lỗi 5 — Không có contract test trong kiến trúc nhiều dịch vụ. Đây là nguồn gốc của những sự cố production "không ai đụng gì mà tự hỏng". Mẹo: nếu hai đội deploy độc lập, contract test gần như bắt buộc.
Lỗi 6 — Nhầm API test với performance test. Gửi 1 request kiểm chức năng khác với gửi 10.000 request kiểm tải. Chiến lược hiệu năng là chủ đề riêng — đừng gộp vào, kẻo cả hai đều làm hời hợt.
Mẹo tổng quát: giữ API test độc lập với chi tiết triển khai UI, nhưng bám sát hợp đồng dữ liệu. Đặt tên test theo nghiệp vụ ("chuyển tiền vượt hạn mức phải trả 400"), không theo kỹ thuật ("test 3"), để báo cáo lỗi tự giải thích.
Bài tập thực hành
- Audit tầng test. Lấy 15 test case UI hiện có của một sản phẩm bạn biết. Với mỗi cái, tự hỏi: "Cái này thực sự kiểm chứng logic backend hay hiển thị giao diện?" Đánh dấu bao nhiêu phần trăm có thể chuyển xuống tầng API. Viết lý do cho 3 case điển hình.
- Viết bộ test cho một endpoint chuyển tiền. Giả sử endpoint
POST /transfernhận{ from, to, amount }. Hãy liệt kê ít nhất 8 test case bao gồm: happy path, số tiền âm, số tiền vượt số dư, thiếu trườngto,amountkiểu chuỗi, token hết hạn, chuyển cho chính mình, và kiểm chứng schema response. Ghi rõ status code và assertion mong đợi cho từng case.
- Thiết kế một contract test đơn giản. Với endpoint trả về thông tin sản phẩm, viết ra "hợp đồng" mà frontend mong đợi (tên trường, kiểu dữ liệu, trường bắt buộc). Mô tả điều gì sẽ xảy ra khi backend đổi kiểu của một trường.
- Kịch bản rate limiting. Viết mô tả test kiểm chứng rằng endpoint gửi OTP chặn được lạm dụng: sau bao nhiêu request thì phải trả
429, và làm sao để test này không "đánh sập" môi trường test của chính bạn.
Tóm tắt
API testing là trụ cột của một chiến lược QA hiện đại, vì phần lớn logic nghiệp vụ sống ở tầng backend chứ không phải giao diện. Test API tách riêng khỏi UI cho bạn bốn lợi ích chiến lược: nhanh hơn nhiều lần, ổn định hơn (ít flaky), bao phủ được các kịch bản khó từ UI (malformed request, rate limit, phân quyền, mã lỗi), và mở đường cho contract testing để bảo vệ ranh giới giữa các dịch vụ và các đội.
Một chiến lược tốt không gộp mọi thứ làm một: hãy phân biệt functional, schema/contract, negative/boundary, authorization và stateful testing; xếp ưu tiên theo rủi ro; assert nhiều lớp; giữ test độc lập; và tích hợp vào CI như một quality gate. Ba tình huống — ví điện tử dời test xuống API, sàn TMĐT được contract test cứu một release, và startup fintech vá lỗ hổng rate limit — đều minh họa cùng một chân lý: đặt test đúng tầng, và đừng bao giờ tin rằng validation phía client là đủ. Là một QA Leader, khả năng thiết kế chiến lược test API bài bản chính là thứ nâng đội của bạn từ "chạy theo giao diện" lên "làm chủ kiến trúc".