Rust cung cấp cho bạn một máy chủ HTTP nhanh, an toàn về kiểu dữ liệu chỉ trong vài trăm dòng mã. Điều mà nó không cung cấp cho bạn là một vòng lặp phản hồi nhanh để kiểm thử máy chủ đó. Chu trình biên dịch dài, cargo test chạy lại mọi thứ khi một trait duy nhất thay đổi, và hầu hết các framework HTTP của Rust yêu cầu bạn viết một bài kiểm thử tích hợp riêng cho từng endpoint trước khi bạn thậm chí gọi nó một lần. Nếu bạn muốn triển khai một API chứ không chỉ là một tệp nhị phân, bạn cần một công cụ nằm ngoài bộ công cụ của Rust và giao tiếp với máy chủ đang chạy.
Hướng dẫn này sẽ trình bày toàn bộ quy trình kiểm thử API Rust trong Apidog: trỏ Apidog tới máy chủ Axum hoặc Actix của bạn, xây dựng các yêu cầu tới các endpoint, xác thực JSON được Serde tuần tự hóa, xử lý xác thực JWT, tạo mock endpoint để frontend có thể tiếp tục phát triển trong khi bạn hoàn thiện handler, và đóng gói toàn bộ quy trình này thành một kịch bản kiểm thử CI. Cuối cùng, bạn sẽ có một dự án Apidog có thể tái sử dụng, giúp phát hiện lỗi lệch hợp đồng trước khi cargo build --release hoàn tất.
Nếu bạn đang chuyển từ quy trình làm việc với Postman hoặc curl, bạn cũng sẽ nhận được miễn phí các tính năng thiết kế-trước của Apidog: một đặc tả OpenAPI được tạo từ các yêu cầu đã lưu của bạn, các URL mock có thể chia sẻ và môi trường nhóm. Bạn có thể bỏ qua câu chuyện di chuyển từ Postman để đọc riêng; bài đăng này tập trung vào Rust.
Tóm tắt
- Chạy máy chủ Rust của bạn cục bộ (
cargo runđối với dự án Axum hoặc Actix-web), thêm URL cơ sởhttp://localhost:3000làm môi trường Apidog, và lưu trữ mọi bí mật dưới dạng biến. - Xây dựng yêu cầu đầu tiên tới
GET /healthz, lưu lại và tái sử dụng xác thực cùng URL cơ sở cho mọi handler trong thư mục. - Đối với các endpoint JSON, dán cấu trúc Serde của bạn vào trình chỉnh sửa body của Apidog và xác nhận định dạng phản hồi bằng các tập lệnh Tests chạy sau mỗi lần gửi.
- Đối với các tuyến đường được bảo vệ, tạo một JWT một lần, lưu nó dưới dạng
{{token}}và áp dụng xác thực Bearer ở cấp thư mục để mọi bài kiểm thử đều kế thừa nó. - Tạo mock cho các handler Rust chưa hoàn thiện trong Apidog để đội ngũ frontend của bạn có thể hiển thị các phản hồi thực tế trước khi handler biên dịch sạch sẽ.
- Lưu bộ công việc dưới dạng Test Scenario (Kịch bản kiểm thử), xuất nó và chạy trong CI với
apidog-cli. Mọi PR chạm vào một handler giờ đây sẽ được kiểm tra hợp đồng trước khi hợp nhất.
Tại sao phải kiểm thử API Rust bên ngoài bộ công cụ Rust
cargo test thì tốt. Nhưng nó cũng chậm, khó hiểu đối với các thành viên nhóm không dùng Rust, và được xây dựng xung quanh mã thay vì HTTP. Nếu bạn muốn xác minh rằng handler của mình trả về mã trạng thái chính xác, định dạng JSON đúng, các header phù hợp và thông báo lỗi chính xác khi đầu vào bị sai định dạng, bạn sẽ phải viết một lệnh gọi tower::ServiceExt::oneshot mới cho từng trường hợp. Sau đó, bạn duy trì bài kiểm thử đó khi handler thay đổi. Rồi bạn lại viết lại nó bằng JavaScript để frontend có thể gọi một mock.
Apidog cung cấp cho bạn một lớp hợp đồng duy nhất nằm trên máy chủ đang chạy. Yêu cầu chỉ tồn tại một lần. Các khẳng định nằm ngay cạnh yêu cầu. Các thành viên nhóm frontend mở cùng một dự án và thấy các yêu cầu giống như bạn. Khi Serde có thêm thuộc tính #[serde(rename_all = "camelCase")] ba tuần sau, bài kiểm thử bị lỗi sẽ là bài trong Apidog, chứ không phải bài được triển khai ra môi trường sản xuất.
Ba lý do cụ thể để thêm Apidog vào quy trình làm việc với Rust:
- Kiểm tra hợp đồng tách rời khỏi quá trình xây dựng. Apidog chạy trên một tệp nhị phân đang hoạt động. Bạn không còn phải chờ
rustcxác thực rằng endpoint của bạn vẫn trả về200. - Mock có thể chia sẻ. Một nhà phát triển frontend ở múi giờ khác nhận được một URL trả về JSON chính xác, chứ không phải một tin nhắn Slack nói rằng “handler chưa xong.”
- OpenAPI miễn phí. Apidog có thể tạo tài liệu OpenAPI 3.1 từ các yêu cầu đã lưu. Bạn giao nó cho bất kỳ ai muốn một client có kiểu dữ liệu mà không cần viết chú thích
utoipahoặcaidetrên mỗi route.
Bước 1: Thêm máy chủ Rust của bạn làm môi trường Apidog
Khởi động API Rust của bạn. Đối với một dự án Axum, mã mẫu là:
use axum::{routing::get, Router};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
let app = Router::new().route("/healthz", get(|| async { "ok" }));
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Mở Apidog, tạo một dự án mới, sau đó mở Quản lý Môi trường (dropdown phía trên bên phải) và thêm một môi trường tên Rust Local:
| Biến | Giá trị |
|---|---|
baseUrl |
http://localhost:3000 |
token |
để trống hiện tại |
apiVersion |
v1 |
Thêm một môi trường thứ hai tên Rust Staging với URL cơ sở đã triển khai. Apidog giới hạn phạm vi biến theo môi trường, vì vậy bạn có thể chuyển từ môi trường cục bộ sang môi trường staging chỉ bằng một cú nhấp chuột vào dropdown. Không cần tìm và thay thế trong các yêu cầu đã lưu.
Bước 2: Gửi yêu cầu đến endpoint đầu tiên
Tạo một thư mục tên Rust API bên trong dự án, sau đó tạo một yêu cầu mới:
- Phương thức:
GET - URL:
{{baseUrl}}/healthz
Nhấn Gửi. Nếu máy chủ của bạn đang chạy, bạn sẽ nhận được mã 200 với nội dung ok. Lưu yêu cầu này dưới tên health-check. Đây là bài kiểm thử khói đơn giản nhất có thể, và nó xác nhận môi trường cùng URL cơ sở hoạt động trước khi bạn viết bất kỳ điều gì phức tạp hơn.
Nếu bạn nhận được lỗi từ chối kết nối, máy chủ của bạn không được gắn vào 0.0.0.0 hoặc cổng không chính xác. Mặc định TcpListener::bind("127.0.0.1:3000") của Rust sẽ từ chối các yêu cầu đến từ bất kỳ thứ gì phân giải thành localhost trên một giao diện khác; hãy gắn vào 0.0.0.0 cho quá trình phát triển cục bộ để Apidog và các container Docker có thể truy cập được.
Bước 3: Kiểm thử yêu cầu và phản hồi JSON bằng Serde
Hình dạng API Rust phổ biến nhất là một handler JSON-in, JSON-out được hỗ trợ bởi một cấu trúc Serde. Thêm một route POST /users:
use axum::{extract::Json, routing::post, Router};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: u64,
name: String,
email: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
Json(User { id: 1, name: payload.name, email: payload.email })
}
let app = Router::new().route("/users", post(create_user));
Trong Apidog, tạo một yêu cầu:
- Phương thức:
POST - URL:
{{baseUrl}}/users - Body (JSON):
{
"name": "Ada Lovelace",
"email": "ada@example.com"
}
Gửi nó đi. Bạn sẽ nhận lại JSON của User. Lưu dưới tên create-user.
Bây giờ mở tab Tests và thêm các khẳng định:
pm.test("Status is 200", () => {
pm.expect(pm.response.code).to.eql(200);
});
pm.test("Body has id, name, email", () => {
const body = pm.response.json();
pm.expect(body).to.have.property("id");
pm.expect(body.name).to.eql("Ada Lovelace");
pm.expect(body.email).to.match(/^[^@]+@[^@]+$/);
});
Lần tới khi ai đó thêm #[serde(rename_all = "camelCase")] vào cấu trúc và định dạng phản hồi của bạn thay đổi từ user_id thành userId, bài kiểm thử này sẽ thất bại trước khi thay đổi được triển khai. Đó là hợp đồng mà Apidog cung cấp cho bạn mà cargo test không có, bởi vì cargo test chạy mã Rust của bạn với các kiểu dữ liệu Rust của bạn và sẽ vui vẻ vượt qua với bất kỳ định dạng nào.
Bước 4: Xử lý các trường hợp từ chối của Serde
Phần thú vị trong việc xử lý JSON của Rust là cách Serde xử lý đầu vào không hợp lệ. Theo mặc định, Axum trả về một 422 Unprocessable Entity mà không có chi tiết. Xây dựng ba yêu cầu cố ý phá vỡ lược đồ:
| Yêu cầu | Body | Mong đợi |
|---|---|---|
create-user-missing-email |
{ "name": "Ada" } |
422, body đề cập đến missing field email |
create-user-extra-field |
{ "name": "Ada", "email": "a@b.c", "admin": true } |
200 nếu thiếu #[serde(deny_unknown_fields)]; 422 nếu không |
create-user-wrong-type |
{ "name": 1, "email": "a@b.c" } |
422, đề cập đến invalid type: integer |
Xác nhận từng mã trạng thái trong Tests. Đây là cách rẻ nhất để ghi lại chính sách xác thực thực tế của bạn. Nếu bạn bật deny_unknown_fields sau này, bài kiểm thử thứ hai sẽ chuyển sang màu đỏ và cho bạn biết hợp đồng công khai đã thay đổi.
Bước 5: Kiểm thử các tuyến đường được bảo vệ bằng JWT
Hầu hết các API Rust trong môi trường sản xuất đều ẩn các handler đằng sau một middleware xác thực. Bộ trích xuất JWT của axum-extra của Axum là mẫu phổ biến:
use axum_extra::extract::cookie::PrivateCookieJar;
use jsonwebtoken::{decode, DecodingKey, Validation};
async fn me(jar: PrivateCookieJar) -> Result<Json<User>, StatusCode> {
let token = jar.get("token").ok_or(StatusCode::UNAUTHORIZED)?;
let claims = decode::<Claims>(token.value(), &DecodingKey::from_secret(b"secret"), &Validation::default())
.map_err(|_| StatusCode::UNAUTHORIZED)?;
Ok(Json(User { id: claims.claims.sub, name: "Ada".into(), email: "ada@example.com".into() }))
}
Trong Apidog, bạn không cần phải tự tạo thủ công một JWT mỗi lần chạy kiểm thử. Tạo một Pre-Request Script (Tập lệnh trước yêu cầu) trên thư mục:
const jwt = require("jsonwebtoken");
const token = jwt.sign(
{ sub: 1, exp: Math.floor(Date.now() / 1000) + 3600 },
"secret"
);
pm.environment.set("token", token);
Mở cài đặt thư mục, đặt Auth là Bearer Token, giá trị {{token}}. Mọi yêu cầu trong thư mục giờ đây sẽ ký và trình bày một JWT mới. Các lỗi token cũ sẽ biến mất khỏi các lần chạy kiểm thử của bạn. Để tìm hiểu sâu hơn về phía xác nhận, hãy xem cách kiểm thử xác thực JWT trong API.
Bước 6: Kiểm thử streaming và Server-Sent Events
Các framework web của Rust có khả năng streaming hạng nhất. Phản hồi Sse của Axum gói một futures::Stream và phát ra các đoạn text/event-stream. Định dạng truyền tải là data: { ... }\n\n cho mỗi khung, kết thúc bằng việc đóng kết nối hoặc một sự kiện done rõ ràng.
Một yêu cầu tiêu thụ dữ liệu này trông giống như bất kỳ GET nào, nhưng bảng phản hồi trong Apidog sẽ chuyển sang chế độ streaming khi Content-Type là text/event-stream. Bạn sẽ thấy từng khung khi nó đến, kèm theo dấu thời gian. Đó là chế độ xem bạn cần khi gỡ lỗi vấn đề áp lực ngược (backpressure) hoặc lỗi thiếu flush.
Những điều cần khẳng định:
- Chunk đầu tiên đến trong phạm vi SLA mà bạn đã công bố. Apidog hiển thị độ trễ từng chunk trong bảng streaming.
- Một tên sự kiện cụ thể được kích hoạt trước khi kết nối đóng (ví dụ:
event: done). - Tổng thời lượng streaming được giới hạn. Một handler quên thoát khỏi
while let Some(event) = stream.next().awaitsẽ streaming mãi mãi; Apidog sẽ hiển thị điều này và bạn có thể đặt một thời gian chờ cứng trong cài đặt yêu cầu để làm cho bài kiểm thử thất bại.
Nếu endpoint của bạn sử dụng WebSockets thay vì SSE, Apidog có một loại yêu cầu WebSocket riêng. Mô hình tương tự: xây dựng kết nối một lần, lưu chuỗi tin nhắn, xác nhận các phản hồi.
Bước 7: Mock API Rust cho việc phát triển frontend song song
Frontend hiếm khi bị chặn bởi thời gian biên dịch của Rust. Nó bị chặn bởi các handler chưa tồn tại. Các mock của Apidog cho phép bạn công bố một URL ổn định trả về hợp đồng mà bạn và frontend đã đồng ý, trước khi handler được triển khai.
Nhấp chuột phải vào create-user, chọn Smart Mock, và bật nó lên. Apidog giờ đây sẽ cung cấp một phản hồi User tổng hợp tại https://mock.apidog.com/m1/<projectId>/users. Nội dung mock khớp với ví dụ đã lưu của bạn. URL mock chấp nhận cùng định dạng body, vì vậy frontend có thể POST đến nó như thể đó là máy chủ Rust thực.
Đối với các mock động, chuyển sang Advanced Mock và viết một tập lệnh:
return {
id: Math.floor(Math.random() * 10000),
name: body.name,
email: body.email,
createdAt: new Date().toISOString()
};
Mock đó phản hồi với bất cứ điều gì frontend gửi, với một id và dấu thời gian được tạo. Khi handler Rust sẵn sàng, frontend chuyển URL cơ sở của nó trở lại http://localhost:3000 và không có gì khác thay đổi. Để biết thêm về mô hình này, nhóm cũng đề cập đến xây dựng và kiểm thử API Spring Boot và quy trình kiểm thử API nói chung; cùng một ý tưởng, các runtime khác nhau.
Bước 8: Lưu thành kịch bản kiểm thử CI
Apidog Test Scenarios (Kịch bản kiểm thử Apidog) xâu chuỗi các yêu cầu với các biến được chia sẻ và chạy chúng một cách không giao diện (headlessly). Xây dựng một kịch bản:
health-check, khẳng định200.create-user, khẳng định200, lấybody.idvào một biến.create-user-missing-email, khẳng định422.me(với yêu cầu tiền JWT), khẳng định200vàidtrả về khớp vớiidđã lấy.- Yêu cầu SSE, khẳng định luồng hoàn tất trong vòng 5 giây.
Xuất kịch bản dưới dạng JSON, commit nó vào kho lưu trữ của bạn trong thư mục tests/apidog/, và gọi nó từ CI:
- name: Run API contract tests
run: |
cargo build --release
./target/release/myserver &
sleep 2
apidog-cli run tests/apidog/contract.json --env "Rust Local"
Mọi PR chạm vào một handler giờ đây sẽ chạy với một tệp nhị phân Rust đang hoạt động kèm theo bộ hợp đồng đầy đủ. Nếu việc đổi tên Serde, thay đổi mã trạng thái hoặc điều chỉnh xác thực JWT phá vỡ định dạng công khai, CI sẽ phát hiện ra trước khi nút hợp nhất chuyển sang màu xanh.
Bước 9: Tạo OpenAPI từ các yêu cầu đã lưu
Khi bộ yêu cầu ổn định, mở menu Xuất của Apidog và chọn OpenAPI 3.1. Bạn sẽ nhận được một tài liệu đặc tả bao gồm mọi yêu cầu đã lưu, với các body bạn đã gửi làm ví dụ. Giao nó cho bất kỳ ai đang tạo một client có kiểu dữ liệu (TypeScript, Swift, Kotlin, Python) và họ sẽ nhận được một hợp đồng khớp với những gì máy chủ Rust của bạn trả về hôm nay, chứ không phải những gì ai đó đã viết tay trong một tệp .yaml sáu tháng trước.
Nếu bạn muốn đặc tả được kiểm tra vào kho lưu trữ Rust của mình, hãy chạy apidog-cli export từ CI và ghi nó vào openapi.json. Lần cargo build tiếp theo sẽ không thay đổi, nhưng mọi người tiêu thụ API của bạn sẽ nhận được sự thật trên đĩa.
Câu hỏi thường gặp
Apidog có hoạt động với cả Axum và Actix-web không? Có. Apidog giao tiếp bằng HTTP, không phải Rust. Bất kỳ thứ gì phản hồi một yêu cầu (Axum, Actix-web, Rocket, Warp, Poem, Loco) đều hoạt động theo cùng một cách. Điều cân nhắc duy nhất dành riêng cho Rust là gắn vào 0.0.0.0 thay vì 127.0.0.1 cho kiểm thử cục bộ.
Làm thế nào để kiểm thử các handler gây panic? Chạy máy chủ của bạn với CatchPanicLayer của tower-http phía trước bộ định tuyến. Panic sẽ chuyển thành mã 500 với một body JSON. Xây dựng một yêu cầu Apidog kích hoạt đường dẫn gây panic và xác nhận mã 500. Nếu bạn không bọc các panic, kết nối sẽ bị mất và Apidog báo cáo lỗi mạng, đây cũng là một bài kiểm thử hợp đồng hợp lệ.
Tôi có thể chạy Apidog đối với tệp nhị phân Rust trong Docker không? Có. Chỉ cần trỏ baseUrl vào cổng đã công khai của container là xong. Nếu container chạy bên trong Docker Compose, hãy cấp cho trình chạy Apidog của bạn cùng mạng hoặc sử dụng cổng được ánh xạ của máy chủ.
Còn gRPC thì sao? Apidog có một loại yêu cầu gRPC. Nhập các tệp .proto của bạn, chọn một dịch vụ và phương thức, điền vào payload yêu cầu và gửi. Mô hình xác thực, môi trường và kịch bản kiểm thử giống hệt như REST.
Kịch bản kiểm thử có thay thế cargo test không? Không. Các bài kiểm thử đơn vị cho mã Rust của bạn vẫn ở trong Rust. Apidog kiểm thử bề mặt đang chạy: hợp đồng HTTP. Hai lớp này bắt các lỗi khác nhau. Một bài kiểm thử đơn vị bắt một hàm bị lỗi; một bài kiểm thử Apidog bắt một định dạng phản hồi bị lỗi, một header CORS bị thiếu, hoặc một mã 400 đã trở thành 422. Bạn muốn cả hai.
Apidog có miễn phí cho các dự án mã nguồn mở Rust không? Có. Client Apidog miễn phí cho cá nhân và nhóm nhỏ. Các kịch bản kiểm thử, mock và xuất OpenAPI là một phần của gói miễn phí. Nếu bạn duy trì một API Rust công khai, bạn có thể đưa tệp dự án vào kho lưu trữ của mình để bất kỳ ai clone đều nhận được bộ kiểm thử.
Tổng kết
Các API Rust xứng đáng có một vòng lặp phản hồi không cần chờ đợi trình biên dịch. Một bộ sưu tập yêu cầu trong Apidog cung cấp cho bạn vòng lặp đó: HTTP thực, các khẳng định thực, các mock thực cho frontend và một kịch bản CI chạy với tệp nhị phân đang hoạt động. Xây dựng các yêu cầu trên một lần, và mọi thay đổi trong tương lai đối với handler Axum hoặc Actix của bạn sẽ trở thành một lần chạy kiểm thử có kiểm soát thay vì một bất ngờ trong runtime.
Tải Apidog và trỏ nó vào máy chủ Rust của bạn. Việc thiết lập mất chưa đầy mười phút. Phần thưởng là một hợp đồng mà bạn kiểm soát, được tách rời khỏi cargo, và một đội ngũ frontend không còn phải hỏi khi nào handler hoàn thành.
