0Pricing
Learn Rust Coding · บทเรียน

การทดสอบ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การตั้งค่าโครงการ
  2. ปลายทางและแบบจำลอง
  3. การผสานรวมฐานข้อมูล
  4. การทดสอบ API
← กลับไปที่ Learn Rust Coding