การทดสอบ API
การทดสอบการผสานรวม
การทดสอบ API เป็นบทเรียน Learn Rust Coding ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Learn Rust Coding และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดจึงต้องทดสอบเอพีไอ
การทดสอบช่วยให้คุณมั่นใจว่าจุดปลายทางทำงานถูกต้องและยังคงทำงานได้เมื่อคุณเปลี่ยนแปลงโค้ด สำหรับ REST API การทดสอบที่มีประโยชน์ที่สุดคือ การทดสอบการทำงานร่วมกัน ซึ่งจะทดสอบเราเตอร์จริงตั้งแต่ต้นจนจบ โดยส่งคำขอและตรวจสอบการตอบกลับ
บทเรียนนี้ครอบคลุมการทดสอบหน่วย เทคนิค oneshot ของ Tower และการทดสอบการทำงานร่วมกันแบบครบถ้วน
การทดสอบหน่วยสำหรับตรรกะบริสุทธิ์
ตรรกะที่ไม่แตะต้องเครือข่าย เช่น การตรวจสอบความถูกต้อง สามารถทดสอบด้วยการทดสอบหน่วยของ Rust แบบปกติได้ ให้ใส่การทดสอบเหล่านี้ไว้ในมอดูล #[cfg(test)] ถัดจากโค้ด การทดสอบเหล่านี้ทำงานได้รวดเร็วด้วย cargo test
fn validate_title(title: &str) -> bool {
!title.trim().is_empty()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_empty() {
assert!(!validate_title(" "));
assert!(validate_title("buy milk"));
}
}การทดสอบแบบอะซิงโครนัส
ตัวจัดการเป็นแบบอะซิงโครนัส ดังนั้นฟังก์ชันทดสอบที่มีการรอคอยต้องทำงานบนรันไทม์ ให้ใช้ #[tokio::test] แทน #[test] ซึ่งจะเริ่มรันไทม์ของ Tokio สำหรับการทดสอบนั้น ทำให้คุณเรียกใช้โค้ดแบบอะซิงโครนัสด้วย .await ได้
async fn add(a: i32, b: i32) -> i32 { a + b }
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn adds() {
assert_eq!(add(2, 3).await, 5);
}
}การทดสอบโดยไม่ใช้เครือข่าย
เราเตอร์ Axum ใช้คุณลักษณะ Service ของ Tower ดังนั้นคุณจึงส่งคำขอให้เราเตอร์โดยตรงได้โดยไม่ต้องผูกพอร์ต เมธอด oneshot รับ Request หนึ่งรายการและส่งคืน Response วิธีนี้ทำให้การทดสอบการทำงานร่วมกันรวดเร็วและให้ผลแน่นอน
// dev-dependencies needed: tower (for ServiceExt), http-body-util
use axum::{Router, routing::get};
use axum::http::{Request, StatusCode};
use axum::body::Body;
use tower::ServiceExt; // brings in oneshot
async fn check() {
let app = Router::new().route("/health", get(|| async { "ok" }));
let res = app
.oneshot(Request::builder().uri("/health").body(Body::empty()).unwrap())
.await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}การตรวจสอบรหัสสถานะ
สิ่งแรกที่การทดสอบส่วนใหญ่มักตรวจสอบคือสถานะ HTTP ทรัพยากรที่ไม่มีอยู่ควรส่งคืน 404 การสร้างที่สำเร็จควรส่งคืน 201 และเนื้อหาที่ไม่ถูกต้องควรส่งคืน 400 สร้างคำขอสำหรับเส้นทางนั้น แล้วเปรียบเทียบ res.status() กับรหัสที่คาดไว้
use axum::http::{Request, StatusCode};
use axum::body::Body;
use tower::ServiceExt;
async fn missing_returns_404(app: axum::Router) {
let res = app
.oneshot(Request::builder()
.uri("/todos/999")
.body(Body::empty()).unwrap())
.await.unwrap();
assert_eq!(res.status(), StatusCode::NOT_FOUND);
}การอ่านเนื้อหาการตอบกลับ
หากต้องการตรวจสอบ JSON ให้รวบรวมเนื้อหาการตอบกลับเป็นไบต์แล้วแปลงกลับเป็นข้อมูลชนิดที่เหมาะสม ตัวช่วย http_body_util::BodyExt::collect จะรวบรวมเนื้อหา จากนั้น serde_json จะแยกวิเคราะห์เนื้อหาให้เป็นโมเดลของคุณ เพื่อให้ตรวจสอบแต่ละฟิลด์ได้
use http_body_util::BodyExt;
async fn read_json(res: axum::http::Response<axum::body::Body>) {
let bytes = res.into_body().collect().await.unwrap().to_bytes();
let todo: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
assert_eq!(todo["done"], false);
}การส่งเนื้อหา JSON
หากต้องการทดสอบ POST ให้สร้างคำขอที่มีเนื้อหา JSON และส่วนหัวชนิดเนื้อหาที่ถูกต้อง แปลงโครงสร้างข้อมูลนำเข้าของคุณเป็นข้อความ กำหนด content-type: application/json แล้วส่งคำขอผ่าน oneshot
use axum::http::{Request, StatusCode, header};
use axum::body::Body;
use tower::ServiceExt;
async fn create_returns_201(app: axum::Router) {
let body = serde_json::json!({ "title": "test" }).to_string();
let res = app
.oneshot(Request::builder()
.method("POST").uri("/todos")
.header(header::CONTENT_TYPE, "application/json")
.body(Body::from(body)).unwrap())
.await.unwrap();
assert_eq!(res.status(), StatusCode::CREATED);
}ตัวสร้างแอปสำหรับการทดสอบที่นำกลับมาใช้ใหม่ได้
การทดสอบแต่ละครั้งควรเริ่มจากสถานะที่สะอาด ให้เขียนตัวช่วยที่สร้างเราเตอร์ใหม่พร้อมฐานข้อมูลในหน่วยความจำหรือฐานข้อมูลสำหรับการทดสอบใหม่ การเรียกใช้ตัวช่วยนี้ในการทดสอบทุกครั้งจะแยกการทดสอบออกจากกัน ทำให้การทดสอบหนึ่งไม่สามารถส่งผลต่ออีกการทดสอบได้
use axum::Router;
use std::sync::{Arc, Mutex};
fn test_app() -> Router {
let store = Arc::new(Mutex::new(Vec::new()));
build(store) // same build() the real server uses
}ไดเรกทอรี tests
การทดสอบการทำงานร่วมกันจะอยู่ในโฟลเดอร์ระดับบนสุด tests/ แต่ละไฟล์ในโฟลเดอร์นี้จะถูกคอมไพล์เป็น crate แยกต่างหากที่ใช้เอพีไอสาธารณะของไลบรารี วิธีนี้บังคับให้คุณทดสอบผ่านส่วนติดต่อแบบเดียวกับที่ผู้ใช้จริงมองเห็น
tests/api.rsเก็บการทดสอบจุดปลายทางของคุณ- เรียกใช้ทั้งหมดด้วย
cargo test
// tests/api.rs
use my_api::build_router; // exported from lib.rs
#[tokio::test]
async fn health_ok() {
let _app = build_router();
// send a request and assert ...
}การทดสอบกับฐานข้อมูล
เมื่อตัวจัดการใช้ sqlx การทดสอบก็ต้องมีฐานข้อมูลด้วย กลยุทธ์ที่ใช้กันทั่วไป ได้แก่ ฐานข้อมูลเฉพาะสำหรับการทดสอบ ธุรกรรมที่ย้อนกลับหลังการทดสอบแต่ละครั้ง หรือแมโคร #[sqlx::test] ของ sqlx ซึ่งจัดเตรียมฐานข้อมูลที่สะอาดสำหรับการทดสอบแต่ละครั้งโดยอัตโนมัติ
// requires sqlx test features and a DATABASE_URL
use sqlx::PgPool;
#[sqlx::test]
async fn inserts_todo(pool: PgPool) {
let todo = create(&pool, "learn rust").await.unwrap();
assert_eq!(todo.title, "learn rust");
assert_eq!(todo.done, false);
}สิ่งที่ควรทดสอบ
มุ่งสร้างชุดการทดสอบที่สมดุล:
- กรณีปกติ: คำขอที่ถูกต้องส่งคืนสถานะและเนื้อหาที่ถูกต้อง
- ข้อผิดพลาด: ทรัพยากรที่ไม่มีอยู่ส่งคืน 404 ส่วนข้อมูลนำเข้าที่ไม่ถูกต้องส่งคืน 400
- กรณีขอบเขต: รายการว่าง ค่าเขตขอบ และข้อมูลซ้ำ
เรียกใช้ cargo test ใน CI เพื่อให้ตรวจพบการถดถอยก่อนนำไปใช้งาน
ตรวจสอบอย่างรวดเร็ว
ทดสอบความเข้าใจของคุณเกี่ยวกับการทดสอบเอพีไอ
สรุปทบทวน
คุณได้เรียนรู้การทดสอบ REST API ของ Rust:
- ทดสอบหน่วยตรรกะบริสุทธิ์ และใช้
#[tokio::test]สำหรับโค้ดแบบอะซิงโครนัส oneshotขับเคลื่อนเราเตอร์โดยตรงโดยไม่ต้องใช้พอร์ต- สร้างคำขอพร้อมเนื้อหาและส่วนหัว รวบรวมและแยกวิเคราะห์เนื้อหาการตอบกลับ
- ใช้ตัวสร้างแอปใหม่ในการทดสอบแต่ละครั้งเพื่อแยกการทดสอบออกจากกัน และใส่การทดสอบการทำงานร่วมกันไว้ใน
tests/ #[sqlx::test]จัดเตรียมฐานข้อมูลที่สะอาด และครอบคลุมกรณีปกติ ข้อผิดพลาด และกรณีขอบเขต
คำถามที่พบบ่อย
บทเรียน “การทดสอบ API” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การทดสอบ API” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Learn Rust Coding ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การทดสอบ API”
การทดสอบการผสานรวม คุณปฏิบัติ Learn Rust Coding ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Learn Rust Coding หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Learn Rust Coding บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “การทดสอบ API” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Learn Rust Coding นี้ได้ไหม
ได้ บทเรียน Learn Rust Coding ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ