API và HTTP cho tester: đọc request, response và mã trạng thái
Xem thửThứ Sáu, 16:40. Tạp Hóa Cô Tấm nhập hàng bằng hai nơi: màn hình web Thêm sản phẩm của nhân viên Lan, và ứng dụng đọc mã vạch trên điện thoại vừa ra mắt tuần này. Tester Bình đã kiểm thử màn hình web rất kỹ: 12 test case trên form, từ ô tên để trống tới ô giá nhập chữ cái. Form chặn hết và kết quả là 12 trên 12 đạt.
Sáng thứ Hai, cô Tấm mở báo cáo và thấy tổng giá trị tồn kho thấp hơn thực tế 1.250.000 đồng. Bình kiểm tra cơ sở dữ liệu và tìm ra 4 sản phẩm mang giá âm, tất cả do ứng dụng đọc mã vạch gửi lên. Form web chưa bao giờ cho nhập giá âm, nhưng máy chủ phía sau form thì vẫn nhận.
Trước khi đọc tiếp, hãy viết ra giấy: vì sao bộ 12 test case trên form không thể bắt được lỗi này, và nếu không dùng form thì bạn kiểm tra máy chủ bằng cách nào?
Sau bài này, bạn có thể
- Mô tả các phần của một request (method, URL, header, body) và một response (mã trạng thái, header, body JSON) khi nhân viên bấm Lưu để thêm sản phẩm.
- Phân biệt nhóm mã 2xx, 3xx, 4xx, 5xx và nói đúng ý nghĩa của 200, 201, 204, 400, 401, 403, 404, 409, 422, 500.
- Mở tab Network của trình duyệt, tìm đúng một request và đọc được nó đã gửi gì, nhận về gì.
- Giải thích ba lý do tester cần kiểm thử API chứ không chỉ kiểm thử giao diện.
Cần biết trước: chương 1 đến 6 (test case, kết quả mong đợi, báo bug). Bài này không cần biết lập trình.
Về dữ liệu trong cả chương này
Cả chương dùng một hệ thống quản lý bán hàng nhỏ của Tạp Hóa Cô Tấm. Địa chỉ gốc của API là https://api.tap-hoa-co-tam.example/v1. Tên miền kết thúc bằng .example được dành riêng cho tài liệu minh họa (RFC 2606), nên không có máy chủ thật nào đứng sau địa chỉ này. Mọi request và phản hồi in trong chương là minh họa viết tay theo hợp đồng API đã định, không phải kết quả gọi thật.
Hợp đồng API (API contract) là tài liệu cho biết mỗi địa chỉ nhận dữ liệu gì, trả dữ liệu gì và trả mã trạng thái nào trong từng tình huống. Với tester, hợp đồng đóng vai trò của tài liệu yêu cầu: nó quyết định kết quả mong đợi. Hợp đồng của chương này tóm tắt như sau.
| Tài nguyên | Địa chỉ | Trường chính |
|---|---|---|
| Sản phẩm | /products | id, name, price, stock |
| Khách hàng | /customers | id, name, phone |
| Đơn hàng | /orders | id, customerId, items, total |
| Đăng nhập | /auth/login | gửi email và mật khẩu, nhận token |
Có hai vai trò đăng nhập: admin (cô Tấm) và staff (nhân viên Lan). Mọi địa chỉ trừ /auth/login đều yêu cầu token.
Bằng chứng: bấm Lưu thì phía sau chuyện gì chạy
Khi Lan nhập sản phẩm Nuoc rua chen, giá 18000, tồn 24 rồi bấm Lưu, trình duyệt không "gửi form" theo nghĩa gửi một tờ giấy. Nó gửi một thông điệp văn bản có cấu trúc cố định tới máy chủ. Thông điệp đó gọi là request (yêu cầu):
POST /v1/products HTTP/1.1
Host: api.tap-hoa-co-tam.example
Authorization: Bearer demo-admin-token
Content-Type: application/json
{"name": "Nuoc rua chen", "price": 18000, "stock": 24}
Máy chủ xử lý rồi gửi lại một thông điệp khác, gọi là response (phản hồi):
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Location: /v1/products/104
{"id": 104, "name": "Nuoc rua chen", "price": 18000, "stock": 24}
Nếu ứng dụng đọc mã vạch gửi "price": -5000, một máy chủ viết đúng hợp đồng sẽ từ chối:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json; charset=utf-8
{"error": {"code": "INVALID_VALUE", "message": "price must be an integer from 1000 to 5000000"}}
Hãy so hai phản hồi. Cả hai đều là văn bản, nhưng dòng đầu tiên đã cho biết ngay kết quả: số 201 hay số 422. Phần còn lại giải thích chi tiết. Tester đọc phản hồi theo đúng thứ tự đó: mã trạng thái trước, header sau, body cuối.
Trong sự cố của cô Tấm, máy chủ đã trả một dòng 201 Created cho giá -5000. Form web không bao giờ gửi giá âm nên chưa ai thấy dòng đó.
Mô hình tư duy 1: request và response là một cuộc hỏi đáp có khuôn cố định
Giao thức HTTP (HyperText Transfer Protocol) là bộ quy tắc để trình duyệt, ứng dụng điện thoại hay công cụ kiểm thử nói chuyện với máy chủ. Mỗi lần nói chuyện gồm đúng một request và một response. Máy chủ không tự gửi gì khi chưa được hỏi.
Một request gồm bốn phần:
| Phần | Vai trò | Ví dụ trong sự cố |
|---|---|---|
| Method | Muốn làm gì | POST (tạo mới) |
| URL | Làm với cái gì, ở đâu | https://api.tap-hoa-co-tam.example/v1/products |
| Header | Thông tin kèm theo: ai hỏi, dữ liệu dạng gì | Authorization, Content-Type |
| Body | Dữ liệu gửi đi, thường là JSON | {"name": ..., "price": ..., "stock": ...} |
Một response gồm ba phần: mã trạng thái (status code) cho biết kết quả, header cho biết thông tin kèm theo (ví dụ Location chỉ địa chỉ sản phẩm vừa tạo), và body chứa dữ liệu hoặc mô tả lỗi.
Cách này tương ứng khít với test case bạn đã học: đầu vào của test case là request, kết quả mong đợi là response. Khác duy nhất là bây giờ đầu vào có hình dạng chính xác, từng ký tự đều kiểm tra được.
Method: động từ của request
Năm method hay gặp nhất trong chương này:
| Method | Ý nghĩa | Ví dụ ở Tạp Hóa Cô Tấm | Có đổi dữ liệu? |
|---|---|---|---|
GET | Đọc | Xem sản phẩm 101 | Không |
POST | Tạo mới | Thêm sản phẩm mới | Có |
PUT | Thay toàn bộ | Gửi lại đủ tên, giá, tồn của sản phẩm 101 | Có |
PATCH | Sửa một phần | Chỉ đổi giá của sản phẩm 101 | Có |
DELETE | Xóa | Xóa sản phẩm 101 | Có |
URL và kiểu thiết kế REST
URL gồm vài phần: https là giao thức, api.tap-hoa-co-tam.example là máy chủ, /v1/products/104 là đường dẫn (path), và phần sau dấu ? là tham số truy vấn (query), ví dụ ?page=1&pageSize=20 để lấy trang đầu của danh sách. Chữ v1 là phiên bản API, để lần sau đổi cách làm mà không phá ứng dụng cũ.
REST (REpresentational State Transfer) là một cách đặt tên địa chỉ phổ biến: mỗi loại dữ liệu là một tài nguyên (danh từ, thường số nhiều), mỗi món cụ thể có địa chỉ riêng, và method đóng vai động từ. Ví dụ /products là cả danh sách sản phẩm, /products/104 là sản phẩm có id 104. Bạn không cần thiết kế REST, chỉ cần đọc hiểu để biết request nào đang làm gì.
Body JSON và những lỗi kiểu dữ liệu
JSON (JavaScript Object Notation) là dạng văn bản để mô tả dữ liệu bằng cặp tên và giá trị. Có sáu kiểu giá trị: chuỗi ("Nuoc rua chen"), số (18000), true hoặc false, null, đối tượng (cặp tên và giá trị lồng nhau) và mảng (danh sách). Với tester, điều quan trọng là kiểu:
| Giá trị gửi | Kiểu | Chuyện gì có thể xảy ra |
|---|---|---|
"price": 18000 | số | Đúng như hợp đồng |
"price": "18000" | chuỗi | Máy chủ chặt từ chối, máy chủ lỏng nhận rồi lưu thành chuỗi |
"price": null | null | Dễ gây lỗi 500 nếu dev quên xử lý |
Dấu ngoặc kép quanh số tạo ra một bug nhìn bằng mắt rất khó thấy, nhưng công cụ kiểm thử thấy ngay vì kiểu là một phần của kiểm tra.
Sơ đồ trình tự
Sơ đồ dưới đây cho thấy thứ tự trao đổi giữa form web, API và cơ sở dữ liệu, cùng hai nhánh kết quả của việc thêm sản phẩm.
Đang tải sơ đồ…
Cách đọc hình: mũi tên đặc là lời hỏi, mũi tên nét đứt là lời đáp, thời gian trôi từ trên xuống. API không chuyển ngay dữ liệu xuống database: nó kiểm tra trước. Chỉ khi dữ liệu hợp lệ thì database mới có thêm dòng và form mới nhận được 201. Nhánh else mới là nhánh quyết định chất lượng: nếu API quên kiểm tra thì nhánh này không tồn tại và dữ liệu xấu đi thẳng xuống database, đúng như sự cố giá âm.
Mô hình tư duy 2: mã trạng thái là câu trả lời đầu tiên
Mã trạng thái là số ba chữ số ở dòng đầu của response. Chữ số đầu cho biết ai chịu trách nhiệm cho kết quả:
| Nhóm | Ý nghĩa | Ai "sai" nếu có vấn đề |
|---|---|---|
| 1xx | Thông tin tạm thời | Hầu như không gặp khi test API nghiệp vụ |
| 2xx | Thành công | Không ai |
| 3xx | Chuyển hướng, ví dụ đường dẫn đã dời chỗ | Cấu hình hoặc địa chỉ cũ |
| 4xx | Lỗi từ phía người gọi (client) | Request sai: thiếu quyền, thiếu dữ liệu, sai địa chỉ |
| 5xx | Lỗi từ phía máy chủ | Máy chủ hỏng khi xử lý, gần như luôn là bug cần báo |
Mười mã bạn sẽ gặp trong cả chương, với ví dụ ở API của Tạp Hóa Cô Tấm:
| Mã | Tên | Khi nào xuất hiện |
|---|---|---|
| 200 | OK | Đọc hoặc sửa thành công, body có dữ liệu |
| 201 | Created | Tạo mới thành công (POST /products), thường kèm header Location |
| 204 | No Content | Thành công và không có body, thường là sau DELETE |
| 400 | Bad Request | Request sai cấu trúc: JSON hỏng, thiếu trường, sai kiểu dữ liệu |
| 401 | Unauthorized | Chưa đăng nhập hoặc token sai, hết hạn |
| 403 | Forbidden | Đã đăng nhập nhưng không đủ quyền (nhân viên Lan xóa sản phẩm) |
| 404 | Not Found | Địa chỉ hoặc tài nguyên không tồn tại, ví dụ sản phẩm 9999 |
| 409 | Conflict | Xung đột với trạng thái hiện có, ví dụ tên sản phẩm đã tồn tại |
| 422 | Unprocessable Content | Cấu trúc đúng nhưng giá trị vi phạm quy tắc, ví dụ giá âm |
| 500 | Internal Server Error | Máy chủ gặp lỗi không xử lý được |
Ba cặp mã dễ nhầm, và cách phân biệt:
- 401 và 403. 401 nghĩa là máy chủ không biết bạn là ai. 403 nghĩa là máy chủ biết bạn là ai nhưng bạn không được làm việc này. Tên mã 401 trong chuẩn là "Unauthorized" nhưng thực chất nói về xác thực (authentication), còn quyền hạn (authorization) thuộc về 403.
- 400 và 422. Nhiều API dùng 400 cho mọi lỗi dữ liệu, một số tách ra như hợp đồng của chương này: 400 cho lỗi cấu trúc (JSON hỏng, thiếu trường, sai kiểu) và 422 cho lỗi giá trị (giá âm, tên rỗng). Không có đáp án đúng chung: hãy đọc hợp đồng của dự án và dùng nó làm kết quả mong đợi.
- 404 và 409. 404 là "không có cái bạn hỏi", 409 là "có rồi, và việc bạn làm đụng với nó".
Các mã 3xx hiếm khi cần viết test case trong chương này. Ví dụ 301 là địa chỉ đã chuyển vĩnh viễn và 304 nghĩa là dữ liệu chưa đổi so với lần trước.
Mã trạng thái không đủ một mình
Tester cẩn thận luôn đọc cả body, vì có API trả 200 OK kèm body {"error": "price is invalid"}. Mã nói thành công nhưng nội dung nói lỗi: đây là một bug thiết kế và test case phải bắt được. Chương sau sẽ dạy cách viết kiểm tra cho cả mã lẫn body.
Hình dung hành trình của một request
Animation dưới đây đi từng bước một request POST /products qua các cổng kiểm tra rồi mới tới database. Hãy chạy bản bình thường trước, sau đó bấm nút đổi biến thể để thử quên header Authorization. Hình cho thấy điều Mermaid ở trên chưa cho thấy: thứ tự các cổng và việc request dừng ở cổng nào thì database còn nguyên.
Đang tải Animation…
Khi đã thấy request dừng ở cổng token, bạn đọc được hệ quả: nếu mã trả về là 401 thì không cần nghi ngờ body, vì máy chủ chưa đọc tới body. Ngược lại nếu mã là 422 thì token chắc chắn đã qua. Mã trạng thái cho biết request dừng ở cổng nào, nên nó là công cụ chẩn đoán đầu tiên của tester.
Quan sát thật: đọc một request trong tab Network
Bạn không cần công cụ nào ngoài trình duyệt để thấy các request thật. Làm theo các bước với bất kỳ trang web nào có ô tìm kiếm hoặc nút bấm tải dữ liệu (Chrome và Edge dùng DevTools, Firefox có công cụ tương tự):
- Nhấn
F12(hoặc chuột phải, chọn Inspect) và mở tab Network. - Bấm nút lọc Fetch/XHR. Đây là các request mà trang tự gửi để lấy dữ liệu, không lẫn ảnh, font hay CSS.
- Thực hiện một thao tác trên trang (tìm kiếm, bấm tải thêm, bấm Lưu). Mỗi thao tác thường tạo một hoặc vài dòng mới trong danh sách.
- Bấm vào một dòng. Bảng chi tiết có các thẻ chính: Headers (method, URL, mã trạng thái, header gửi đi và nhận về), Payload (body đã gửi), Preview và Response (body nhận về), Timing (thời gian từng giai đoạn).
| Thẻ | Bạn đọc gì | Tương ứng trong test case |
|---|---|---|
| Headers, phần General | Method, URL, mã trạng thái | Đầu vào và mã mong đợi |
| Headers, phần Request Headers | Authorization, Content-Type | Điều kiện xác thực |
| Payload | Body JSON đã gửi | Dữ liệu kiểm thử |
| Preview hoặc Response | Body trả về | Nội dung mong đợi |
| Timing | Tổng thời gian chờ | Thời gian phản hồi |
Một mẹo hữu ích: chuột phải vào dòng request, chọn Copy rồi Copy as cURL. Bạn nhận được một lệnh curl tái tạo đúng request đó. Với sản phẩm của Cô Tấm, lệnh có dạng:
curl -i -X POST "https://api.tap-hoa-co-tam.example/v1/products" \
-H "Authorization: Bearer demo-admin-token" \
-H "Content-Type: application/json" \
-d '{"name": "Nuoc rua chen", "price": 18000, "stock": 24}'
| Phần của lệnh | Máy làm gì | Vì sao |
|---|---|---|
-i | In cả dòng trạng thái và header của response | Để thấy mã trạng thái, không chỉ body |
-X POST | Chọn method POST | Mặc định curl gửi GET |
-H "..." | Thêm một header | Gửi token và khai báo body là JSON |
-d '...' | Gửi nội dung này làm body | Đây là dữ liệu kiểm thử |
Bài sau sẽ dùng Postman để làm đúng việc này bằng giao diện đồ họa. Hãy ghi nhớ một điều an toàn: token trong Authorization là chìa khóa vào hệ thống thật. Đừng gửi ảnh chụp tab Network có token vào kênh chat chung hay đính vào báo cáo bug công khai.
Nếu muốn thử gọi một API công khai thật, https://dummyjson.com/products/1 là một API dùng thử trả JSON có id, price, stock. Đây là dữ liệu mô phỏng của bên thứ ba, không liên quan Tạp Hóa Cô Tấm.
Mô hình tư duy 3: giao diện chặn chưa đủ, API phải tự bảo vệ
Quay lại sự cố ở đầu bài. Form web có kiểm tra "giá phải là số dương" bằng JavaScript chạy ngay trong trình duyệt. Kiểm tra này chỉ là tiện lợi cho người dùng, không phải là hàng rào bảo vệ dữ liệu, vì bất kỳ ai cũng có thể gửi request mà không đi qua form: ứng dụng điện thoại khác, một đoạn script, Postman, hay kẻ cố tình phá. Hàng rào thật nằm ở máy chủ.
Từ đó ra ba lý do tester kiểm thử API:
- Bắt lỗi sớm. API thường xong trước giao diện vài tuần. Nếu Bình chờ giao diện, lỗi giá âm tồn tại thêm vài tuần và nhiều chỗ khác bắt đầu dựa vào nó. Kiểm thử API ngay khi có địa chỉ giúp báo bug khi còn rẻ để sửa.
- Nhanh và ổn định. Một test trên form phải mở trang, tìm ô, gõ, bấm, chờ. Một request chỉ là một lần gửi và nhận. Ước lượng minh họa: 12 ca trên form mất khoảng 24 phút nếu mỗi ca 2 phút, còn 12 request chạy tự động xong trong vài giây. Giao diện đổi nút hay đổi màu cũng không làm test hỏng.
- Chạm được những trường hợp giao diện không cho gửi. Giá âm, thiếu trường, token sai,
idlạ, JSON hỏng: form không cho tạo ra các request này, nhưng ứng dụng khác hoàn toàn có thể. Kiểm thử API là cách duy nhất để chứng minh máy chủ xử lý đúng chúng.
Kiểm thử API còn giúp khoanh vùng lỗi. Nếu API trả đúng mà màn hình hiển thị sai, lỗi nằm ở giao diện. Nếu API trả sai, lỗi nằm ở máy chủ, dù màn hình có che giấu được hay không.
Animation thứ hai tái hiện đúng sự cố của cô Tấm. Mặc định bạn thấy request đi qua form, nơi kiểm tra chặn giá âm. Bấm nút đổi biến thể để thấy một ứng dụng khác gửi thẳng tới máy chủ thiếu kiểm tra.
Đang tải Animation…
Hiểu lầm thường gặp
- "Giao diện đã chặn rồi, API không cần kiểm tra nữa." Sai ở chỗ coi giao diện là tầng duy nhất gọi API. Ứng dụng điện thoại, trang quản trị khác hay một đoạn script đều gọi trực tiếp. Dữ liệu chỉ an toàn khi máy chủ tự kiểm tra, và tester phải chứng minh điều đó bằng request không đi qua form.
- "Mã 200 nghĩa là mọi thứ đúng." Mã 200 chỉ nói máy chủ xử lý xong request. Dữ liệu trong body có thể sai giá trị, thiếu trường hoặc báo lỗi trong nội dung. Với thao tác tạo mới, hợp đồng của chương này yêu cầu 201, nên một phản hồi 200 cho
POST /productsđã là sai hợp đồng. - "Kiểm thử API là việc của lập trình viên." Lập trình viên thường viết unit test cho từng hàm. Tester kiểm thử API ở mức hợp đồng: gửi request như một ứng dụng thật và so sánh response với yêu cầu. Việc đó không cần viết hệ thống, chỉ cần đọc hợp đồng và biết gửi request.
Điều kiện và giới hạn
- Chương này nói về API kiểu REST truyền JSON qua HTTP, loại phổ biến nhất khi kiểm thử ứng dụng web và di động. Còn có những kiểu khác như SOAP hay GraphQL, mỗi kiểu có cách gọi riêng.
- Tên mã và cách dùng 400 hay 422, có trả
Locationhay không, nhân viên có được xóa hay không: tất cả do hợp đồng của từng dự án. Hãy luôn kiểm tra tài liệu trước khi suy ra kết quả mong đợi. - Phản hồi trong bài chỉ là minh họa. Khi làm việc thật, bạn lấy phản hồi từ máy chủ thử nghiệm của đội.
Tự kiểm tra (có đáp án)
Câu 1. Một request tới POST /products trả về 401. Bạn có cần kiểm tra thêm giá trị price trong body để tìm nguyên nhân không?
Đáp án: Không cần ở bước này. Mã 401 cho biết request dừng ở cổng xác thực, tức máy chủ chưa xét tới body. Việc đầu tiên là kiểm tra header Authorization có mặt, đúng định dạng Bearer và token còn hạn.
Câu 2. Phản hồi 403 khác 401 ở điểm nào, và nhân viên Lan xóa sản phẩm sẽ nhận mã nào theo hợp đồng của chương?
Đáp án: 401 là chưa biết bạn là ai, 403 là biết rồi nhưng bạn không đủ quyền. Lan đã đăng nhập thành công nên danh tính đã rõ, còn hợp đồng chỉ cho admin xóa, vì vậy Lan nhận 403.
Câu 3. Vì sao 12 test case trên form web không đủ để khẳng định máy chủ chặn được giá âm?
Đáp án: Vì form tự chặn giá âm bằng JavaScript nên request giá âm không bao giờ tới máy chủ. Muốn kiểm chứng máy chủ, phải gửi request giá âm trực tiếp tới API (bằng Postman hoặc curl) và kiểm tra nhận lại 422 thay vì 201.
Điều cần nhớ
- Mỗi lần nói chuyện giữa ứng dụng và máy chủ là một request (method, URL, header, body) và một response (mã trạng thái, header, body).
- Chữ số đầu của mã trạng thái cho biết ai chịu trách nhiệm: 2xx thành công, 4xx request sai, 5xx máy chủ lỗi. Phân biệt 401 với 403 và 400 với 422 bằng hợp đồng của dự án.
- Tab Network cho bạn thấy request thật mà không cần công cụ nào khác.
- Kiểm tra ở giao diện không thay được kiểm tra ở máy chủ, nên tester phải gọi thẳng API.
- Bài kế tiếp trả lời câu hỏi: gửi những request nào, và chọn dữ liệu nào để bắt được lỗi.
Nguồn tham khảo
- MDN, Tổng quan về HTTP: cấu trúc request và response.
- MDN, HTTP request methods: ý nghĩa các method.
- MDN, HTTP response status codes: nhóm và ý nghĩa mã trạng thái, cùng các trang riêng của 401, 403, 422.
- MDN, Làm việc với JSON và thuật ngữ REST.
- Chrome for Developers, Network features reference: tab Network trong DevTools.
- IETF, RFC 2606: Reserved Top Level DNS Names: lý do
.examplekhông có máy chủ thật.