Chiến lược kiểm thử API: Hướng dẫn thực tế để có API đáng tin cậy
Chiến lược kiểm thử API: Hướng dẫn thực tế để có API đáng tin cậy
Hầu hết lỗi API không đến từ những tình huống quá đặc biệt. Chúng thường là trường bị thiếu, mã trạng thái sai, timeout khi tải cao, hoặc thay đổi phá vỡ hợp đồng được deploy vì không ai kiểm tra. Kiểm thử ad-hoc đôi khi bắt được vài lỗi như vậy. Một chiến lược kiểm thử API giúp bạn bắt chúng một cách có chủ đích.
Chiến lược kiểm thử API thực sự có nghĩa là gì
Một chiến lược kiểm thử API là kế hoạch xác định bạn kiểm thử cái gì, ở lớp nào và khi nào trong chu trình delivery. Nó cho biết kiểm tra nào chạy ở mỗi commit, kiểm tra nào chạy hàng đêm, và kiểm tra nào bắt buộc trước release. Mục tiêu là đạt độ phủ cao nhất với chi phí bảo trì thấp nhất.
Trước khi viết assertion, hãy trả lời bốn câu hỏi sau.
1. Bạn kiểm thử cái gì?
Ưu tiên endpoint và luồng có rủi ro hoặc giá trị kinh doanh cao. Ví dụ:
- API thanh toán cần độ phủ cao hơn endpoint health-check
- Endpoint cập nhật quyền người dùng cần kiểm thử bảo mật kỹ hơn endpoint đọc cấu hình public
- Luồng tạo đơn hàng cần kiểm thử tích hợp nhiều hơn endpoint trả về metadata
Đừng chọn endpoint chỉ vì nó dễ gọi. Hãy xếp hạng theo:
- Lưu lượng truy cập
- Mức độ ảnh hưởng khi lỗi
- Dữ liệu nhạy cảm
- Số lượng service phụ thuộc
- Tần suất thay đổi
2. Kiểm thử ở lớp nào?
Không phải mọi kiểm tra đều nên là end-to-end test. Một số lỗi có thể bắt bằng một request đơn lẻ. Một số lỗi khác cần kiểm tra nhiều service cùng lúc. Nếu bạn đưa tất cả vào lớp cao nhất, test suite sẽ chậm, dễ flaky và khó debug.
3. Khi nào test chạy?
Chia test theo tốc độ và mục đích:
- Test nhanh: chạy ở mỗi push hoặc pull request
- Test chậm: chạy hàng đêm hoặc trước release
- Test tải và bảo mật: chạy theo lịch hoặc trước các mốc quan trọng
Nếu trộn tất cả vào cùng một pipeline, bạn sẽ gặp một trong hai vấn đề: feedback quá chậm hoặc độ phủ quá mỏng.
4. Điều gì được coi là pass?
Một test chỉ kiểm tra 200 OK gần như không đủ. Hãy xác định rõ:
- Status code mong đợi
- Response schema
- Giá trị field quan trọng
- Error message
- Response time
- Side effect, ví dụ record được tạo hoặc trạng thái được cập nhật
Ví dụ assertion cho endpoint lấy user:
expect(response.status).toBe(200)
expect(response.body.id).toBe("42")
expect(response.body.email).toMatch(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
expect(response.body).toMatchSchema(UserSchema)
Khi bạn trả lời được bốn câu hỏi này cho API của mình, bạn đã có nền tảng của một chiến lược kiểm thử.
Tại sao chiến lược tốt hơn kiểm thử ad-hoc
Kiểm thử ad-hoc nghĩa là gửi request, nhìn nhanh response, rồi tiếp tục. Cách này hữu ích khi demo hoặc debug nhanh, nhưng không đủ cho hệ thống thật.
Ad-hoc không lặp lại được
Nếu bạn kiểm tra thủ công, người khác khó chạy lại chính xác cùng một bước. Regression bug vì thế dễ quay lại. Một test tự động đã lưu sẽ chạy cùng một cách mỗi lần.
Ad-hoc thiên về happy path
Khi test bằng tay, bạn thường gửi dữ liệu hợp lệ vì muốn xác nhận endpoint hoạt động. Bạn ít khi thử:
- Payload sai format
- Token hết hạn
- Thiếu field bắt buộc
- Số âm
- Danh sách 10.000 item
- Quyền truy cập sai tenant
Nhưng đây lại là các tình huống hay gây lỗi trong production.
Ad-hoc không mở rộng
Một service có 40 endpoint và 5 environment tương đương 200 lượt kiểm tra thủ công cho mỗi release. Không đội nào duy trì được việc này lâu dài. Chiến lược kiểm thử biến chi phí thiết lập ban đầu thành độ phủ lặp lại: viết test một lần, chạy trên mọi thay đổi.
Kim tự tháp kiểm thử API
Kim tự tháp kiểm thử giúp quyết định nên viết bao nhiêu test ở mỗi lớp.
/\
/ \
End-to-end / \
/ workflow /------\
tests (ít, / \
chậm, giá trị / \
cao) /------------\
/Integration \
/ contract /--------------\
tests (vừa / \
phải, tốc độ/ \
trung bình) /--------------------\
/Unit / single-request\
/ tests (nhiều, nhanh, \
/ rẻ) \
/____________________________\
Lớp dưới cùng: kiểm tra request đơn lẻ
Mỗi test gọi một endpoint và kiểm chứng response. Ưu điểm: nhanh; rẻ để viết; dễ debug; phù hợp chạy ở mỗi commit.
Ví dụ:
GET /api/users/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
Assertion: status là 200; body khớp schema User; id bằng 42; email hợp lệ.
Phần lớn test API nên nằm ở lớp này.
Lớp giữa: kiểm thử tích hợp và hợp đồng
Lớp này xác minh:
- Nhiều service giao tiếp đúng với nhau
- Consumer và provider đồng ý về request/response schema
- Flow nhiều bước trả về kết quả đúng
Ví dụ: Tạo đơn hàng. Gọi service thanh toán. Kiểm tra trạng thái đơn hàng được cập nhật.
Các test này chậm hơn request đơn lẻ, nhưng bắt được lỗi mà unit/API-level test không thấy.
Lớp trên cùng: workflow end-to-end
Đây là các test chạy toàn bộ hành trình người dùng: Tạo user. Đăng nhập. Tạo đơn hàng. Thanh toán. Kiểm tra trạng thái.
E2E test cho mức độ tự tin cao nhất nhưng tốn chi phí bảo trì cao nhất. Chỉ dùng cho các luồng critical.
Sai lầm phổ biến là đảo ngược kim tự tháp: quá nhiều E2E test chậm, quá ít test nhanh. Nếu một lỗi có thể bắt ở lớp thấp hơn, hãy đưa test xuống lớp thấp hơn.
Các loại kiểm thử và khi nào áp dụng
Một chiến lược hoàn chỉnh dùng nhiều loại test, mỗi loại bắt một nhóm lỗi khác nhau.
Kiểm thử chức năng
Kiểm thử chức năng xác minh endpoint hoạt động đúng theo đặc tả. Nên kiểm tra:
- Status code
- Response schema
- Field bắt buộc
- Business rule
- Side effect nếu có
Đây là loại test đầu tiên nên tự động hóa cho endpoint mới.
Ví dụ request:
GET /api/users/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
Checklist assertion:
- [ ] Status là
200 - [ ] Body khớp schema
User - [ ]
idbằng42 - [ ]
emaillà chuỗi email hợp lệ - [ ] Không trả về field nội bộ như
passwordHash
Kiểm thử tích hợp
Kiểm thử tích hợp xác minh các endpoint, database, queue, payment provider hoặc service hạ nguồn hoạt động đúng cùng nhau. Một functional test có thể pass với mocked dependency nhưng fail khi kết nối dependency thật. Integration test giúp bắt khoảng trống đó.
Ví dụ flow:
POST /ordersPOST /paymentsGET /orders/{id}
Kỳ vọng: order được tạo; payment thành công; trạng thái order chuyển sang paid.
Kiểm thử hồi quy
Kiểm thử hồi quy chạy lại test suite hiện có sau mỗi thay đổi để đảm bảo hành vi cũ không bị hỏng.
Bạn không cần viết một nhóm test hoàn toàn mới cho regression. Bạn dùng lại:
- Functional tests
- Negative tests
- Integration tests
- Contract tests
Sau đó chạy chúng:
- Ở mỗi pull request
- Trước release
- Theo lịch nếu hệ thống thay đổi thường xuyên
Kiểm thử hợp đồng
Kiểm thử hợp đồng xác minh provider và consumer đồng ý về hình dạng request/response. Nó bắt các thay đổi gây lỗi như:
- Đổi tên field
- Đổi kiểu dữ liệu
- Xóa field
- Xóa endpoint
- Thay đổi status code
- Thay đổi format error response
Nếu API của bạn được nhóm khác hoặc khách hàng bên ngoài sử dụng, contract testing là bắt buộc.
Kiểm thử tải và hiệu năng
Kiểm thử tải đo API dưới lưu lượng đồng thời. Các metric cần theo dõi:
- p95/p99 response time
- Error rate
- Throughput
- CPU/memory
- Điểm bắt đầu suy giảm
- Timeout hoặc queue backlog
Không nên chạy load test ở mỗi commit. Hãy chạy:
- Trước launch
- Trước chiến dịch có traffic lớn
- Định kỳ hàng tuần hoặc hàng tháng
- Sau thay đổi lớn về database, cache hoặc infrastructure
Kiểm thử bảo mật
Kiểm thử bảo mật xác minh API từ chối đúng các request không hợp lệ hoặc không được phép. Tối thiểu cần kiểm tra:
- Request không có token trả về
401 - Token hết hạn trả về
401 - Token hợp lệ nhưng thiếu quyền trả về
403 - User không đọc được dữ liệu của user khác
- Input có injection payload không gây lỗi hoặc lộ dữ liệu
- Dữ liệu nhạy cảm không xuất hiện trong response
| Loại kiểm thử | Phát hiện | Chạy khi |
|---|---|---|
| Chức năng | Status, schema, field value sai | Mỗi commit |
| Tích hợp | Luồng service-to-service bị lỗi | Mỗi commit hoặc hàng đêm |
| Hồi quy | Hành vi cũ bị hỏng | Mỗi commit và trước release |
| Hợp đồng | Thay đổi phá vỡ interface | Mỗi commit trên provider |
| Tải | Chậm hoặc lỗi dưới traffic | Trước launch và theo lịch |
| Bảo mật | Auth, injection, lộ dữ liệu | Trước release và theo lịch |
Các trường hợp tích cực, tiêu cực và biên
Với mỗi endpoint, hãy viết test cho ba nhóm input.
Trường hợp tích cực
Gửi input hợp lệ và mong đợi thành công. Ví dụ tạo user:
POST /api/users HTTP/1.1
Content-Type: application/json
{
"name": "Nguyen Van A",
"email": "a@example.com"
}
Kỳ vọng: status 201; response có id; email đúng với input; record được tạo trong hệ thống.
Trường hợp tiêu cực
Gửi input không hợp lệ và mong đợi lỗi rõ ràng. Ví dụ thiếu field bắt buộc:
POST /api/users HTTP/1.1
Content-Type: application/json
{
"name": "Nguyen Van A"
}
Kỳ vọng: status 400; error message chỉ rõ email bị thiếu; không tạo record.
Một vài negative case nên có:
- Thiếu token
- Token hết hạn
- Sai quyền
- Body sai schema
- Field sai kiểu
- ID không tồn tại
- Truy cập dữ liệu không thuộc user hiện tại
Trường hợp biên
Gửi input ở ranh giới hợp lệ. Ví dụ:
- Chuỗi dài đúng bằng giới hạn tối đa
- Danh sách rỗng
- Danh sách có số lượng item tối đa
- Số
0 - Số âm nếu không hợp lệ
- Unicode name
- Timestamp ở ranh giới timezone hoặc daylight saving time
Quy tắc thực tế: với mỗi positive test, hãy viết ít nhất một negative test.
Ví dụ endpoint tạo đơn hàng:
POST /api/orders HTTP/1.1
Content-Type: application/json
{
"customerId": "c_123",
"quantity": -5
}
Kỳ vọng: status 400; body chứa lỗi validation cho quantity; không tạo order. Nếu request này trả về 201 hoặc 500, bạn đã tìm thấy lỗi mà happy-path test không bao giờ bắt được.
Dữ liệu kiểm thử và môi trường
Test chỉ đáng tin nếu dữ liệu và môi trường ổn định.
Sử dụng dữ liệu kiểm thử chuyên dụng
Không nên test dựa trên production record hoặc một row tình cờ tồn tại trong database. Thay vào đó:
- Tạo dữ liệu trong bước setup
- Seed dataset cố định trước khi chạy
- Dọn dữ liệu sau test
- Dùng test user/test tenant riêng
Làm test độc lập
Mỗi test nên tự setup trạng thái cần thiết. Không nên phụ thuộc vào thứ tự chạy như: Test B chỉ pass nếu Test A đã tạo user trước đó.
Nếu cần truyền dữ liệu giữa các bước trong cùng một scenario, hãy làm rõ ràng:
POST /users- Lưu
response.body.idvào biếnuserId GET /users/{{userId}}
Tránh dùng state toàn cục chia sẻ giữa nhiều test.
Cô lập môi trường
Tách riêng: local; CI; staging; production. Mỗi môi trường nên có:
- Base URL riêng
- Token riêng
- Database hoặc dataset riêng
- Cấu hình timeout riêng
- Secret riêng
Đừng hard-code host hoặc token trong test. Ví dụ dùng biến:
GET {{baseUrl}}/api/users/{{userId}}
Authorization: Bearer {{accessToken}}
Cùng một test có thể chạy trên local, staging hoặc CI bằng cách đổi environment variable.
Shift Left: kiểm thử sớm hơn, không chỉ nhiều hơn
Shift left nghĩa là đưa kiểm thử về sớm hơn trong vòng đời phát triển. Một lỗi schema phát hiện ở giai đoạn thiết kế có thể sửa trong vài phút. Cùng lỗi đó ở production có thể trở thành incident.
1. Thiết kế hợp đồng trước
Xác định request và response schema trước khi build endpoint. Ví dụ:
User:
type: object
required:
- id
- email
properties:
id:
type: string
email:
type: string
format: email
Từ contract này, bạn có thể:
- Tạo mock server
- Viết test trước
- Cho frontend tích hợp sớm
- Phát hiện breaking change trước khi deploy
2. Test với mock khi backend chưa hoàn thiện
Frontend hoặc service consumer không cần chờ backend hoàn tất. Nếu mock được tạo từ schema, consumer có thể kiểm tra:
- Request đúng format
- Response parsing
- Error handling
- UI state cho loading/error/success
3. Chạy test nhanh ở mỗi commit
Functional test và contract test nhanh nên nằm trong developer feedback loop. Đừng đợi batch hàng đêm mới biết endpoint vừa bị hỏng. Càng phát hiện sớm, chi phí sửa càng thấp.
Tự động hóa kiểm thử trong CI
Chiến lược chỉ hiệu quả nếu test chạy tự động. Mục tiêu của pipeline:
- Chạy test ở mỗi push hoặc pull request
- Fail build nếu assertion fail
- Xuất report dễ đọc
- Cho phép debug nhanh
Một pipeline CI cho API test thường có ba nhóm:
- Mỗi push: chạy functional test và contract test nhanh
- Hàng đêm hoặc trước release: chạy integration, E2E, load và security test
- Luôn luôn: xuất report, ví dụ JUnit XML, để CI hiển thị số pass/fail
Ví dụ GitHub Actions tối thiểu:
name: api-tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: npm ci
- name: Run API tests
run: npm test
Pattern chung: checkout code; cài runtime; cài dependencies; inject secrets bằng CI variables; chạy test; fail build nếu exit code khác 0; publish report.
Ví dụ dùng biến môi trường:
- name: Run API tests
env:
API_BASE_URL: ${{ secrets.API_BASE_URL }}
API_TOKEN: ${{ secrets.API_TOKEN }}
run: npm test
Apidog phù hợp với chiến lược như thế nào
Chiến lược ở trên không phụ thuộc công cụ. Bạn có thể dùng nhiều công cụ riêng cho thiết kế API, mock, test và tài liệu. Vấn đề là các artifact dễ bị lệch nhau:
- Spec thay đổi nhưng test chưa đổi
- Mock không khớp contract mới
- Tài liệu khác với behavior thật
- CI chạy test cũ
Apidog gom các phần này vào một nơi:
- Thiết kế hợp đồng API
- Viết test scenario dựa trên hợp đồng
- Tạo mock từ cùng schema
- Xuất bản tài liệu
- Chạy test đã lưu trong CI
Vì test và mock dùng cùng contract, contract testing và shift-left testing trở thành một phần tự nhiên của workflow.
Chạy test Apidog trong CI bằng CLI
Apidog CLI chạy scenario và test suite đã lưu mà không cần giao diện UI. CLI là một package Node, nên có thể dùng trong hầu hết CI runner có Node.
Cài đặt:
npm install -g apidog-cli
Chạy một scenario hoặc test suite:
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t <scenarioOrSuiteId> \
-e <environmentId> \
-r cli,html,junit
Ý nghĩa các tham số:
--access-token: token xác thực. Lưu trong CI secrets-t: ID của scenario, folder hoặc test suite cần chạy-e: ID environment
Comments
No comments yet. Start the discussion.