Cargo & Testing
Cargo: ระบบ Build และ Package Manager ของ Rust
หัวข้อที่มีชื่อว่า “Cargo: ระบบ Build และ Package Manager ของ Rust”Cargo คือระบบ build อย่างเป็นทางการและ package manager ของ Rust โปรเจกต์ Rust ทุกโปรเจกต์คือ crate และ Cargo จัดการ dependency, build, test และเอกสารของตัวเอง
คำสั่ง Cargo ที่จำเป็น
หัวข้อที่มีชื่อว่า “คำสั่ง Cargo ที่จำเป็น”# สร้างโปรเจกต์ binary ใหม่cargo new my_projectcd my_project
# สร้าง library crate ใหม่cargo new my_lib --lib
# Build ในโหมด debug (คอมไพล์เร็ว ไม่ optimize)cargo build
# Build ในโหมด release (คอมไพล์ช้า optimize แล้ว)cargo build --release
# Build และรันในขั้นตอนเดียวcargo run
# รันพร้อม argumentcargo run -- arg1 arg2
# ตรวจสอบ compile error โดยไม่สร้าง binarycargo check
# เพิ่ม dependencycargo add serde --features derive
# อัปเดต dependency เป็นเวอร์ชันที่เข้ากันได้ล่าสุดcargo update
# Format โค้ดcargo fmt
# รัน lintercargo clippyโครงสร้างโปรเจกต์
หัวข้อที่มีชื่อว่า “โครงสร้างโปรเจกต์”flowchart TD root["my_project/"] toml["Cargo.toml — manifest: name, version, dependencies"] lock["Cargo.lock — exact dependency versions (commit for binaries)"] src["src/"] main["main.rs — entry point for binary crates"] lib["lib.rs — entry point for library crates"] tests["tests/"] itest["integration_test.rs — integration tests (optional)"] root --> toml root --> lock root --> src root --> tests src --> main src --> lib tests --> itest
โครงสร้าง Cargo.toml
หัวข้อที่มีชื่อว่า “โครงสร้าง Cargo.toml”[package]name = "my_project"version = "0.1.0"edition = "2021"
[dependencies]serde = { version = "1", features = ["derive"] }tokio = { version = "1", features = ["full"] }anyhow = "1"
[dev-dependencies]# compile เฉพาะสำหรับ test และ benchmarkpretty_assertions = "1"Unit Testing ด้วย #[test]
หัวข้อที่มีชื่อว่า “Unit Testing ด้วย #[test]”Rust มีการทดสอบที่สร้างไว้ในภาษาและ Cargo — ไม่ต้องการ framework ทดสอบเพิ่มเติม ใส่ #[test] บนฟังก์ชันเพื่อทำเครื่องหมายว่าเป็น test
fn add(a: i32, b: i32) -> i32 { a + b}
#[test]fn test_add() { assert_eq!(add(2, 3), 5); assert_eq!(add(-1, 1), 0); assert_ne!(add(2, 3), 6);}รัน test ทั้งหมดด้วย:
cargo testรูปแบบ Module #[cfg(test)]
หัวข้อที่มีชื่อว่า “รูปแบบ Module #[cfg(test)]”สถานที่ idiomatic สำหรับ unit test คือภายในไฟล์ source ที่ทดสอบ ภายในบล็อก mod tests ที่ถูกปิดกั้นด้วย #[cfg(test)] Cargo compile module นี้เฉพาะเมื่อรัน cargo test ทำให้ไม่อยู่ใน binary ที่ใช้งานจริง
pub fn multiply(a: i32, b: i32) -> i32 { a * b}
pub fn divide(a: f64, b: f64) -> Option<f64> { if b == 0.0 { None } else { Some(a / b) }}
#[cfg(test)]mod tests { use super::*; // นำ item ของ parent module มาใช้ใน scope
#[test] fn test_multiply() { assert_eq!(multiply(3, 4), 12); assert_eq!(multiply(0, 100), 0); assert_eq!(multiply(-2, 5), -10); }
#[test] fn test_divide_by_zero() { assert_eq!(divide(10.0, 0.0), None); }
#[test] fn test_divide_normal() { let result = divide(10.0, 4.0).unwrap(); assert!((result - 2.5).abs() < f64::EPSILON); }}Test Assertion
หัวข้อที่มีชื่อว่า “Test Assertion”#[cfg(test)]mod tests { #[test] fn demonstrate_assertions() { // ความเท่ากัน assert_eq!(2 + 2, 4); assert_ne!(2 + 2, 5);
// boolean assert!(4 > 3); assert!(!"hello".is_empty());
// พร้อม custom message assert_eq!(1 + 1, 2, "basic arithmetic must hold");
// panic // ใช้ #[should_panic] เพื่อ assert ว่าฟังก์ชัน panic }
#[test] #[should_panic(expected = "divide by zero")] fn test_panic() { let _ = 1 / 0; // นี่ panic — test ผ่านเพราะเราคาดหวังมัน }}Integration Tests
หัวข้อที่มีชื่อว่า “Integration Tests”ไฟล์ภายใต้ tests/ คือ integration test ซึ่งทดสอบ public API ของ crate ของคุณในฐานะ consumer ภายนอก
use my_lib::multiply;
#[test]fn integration_multiply() { assert_eq!(multiply(6, 7), 42);}รัน integration test เฉพาะ:
cargo test --test integration_testรัน test เฉพาะด้วยชื่อ:
cargo test test_divideรัน test พร้อม output ที่มองเห็น:
cargo test -- --nocaptureการทดสอบ Async Code ด้วย tokio
หัวข้อที่มีชื่อว่า “การทดสอบ Async Code ด้วย tokio”สำหรับ async function ใช้ #[tokio::test]:
#[cfg(test)]mod tests { use super::*;
#[tokio::test] async fn test_async_fetch() { let result = fetch_data(1).await; assert!(!result.is_empty()); }}เพิ่ม tokio ใน [dev-dependencies] เพื่อใช้ #[tokio::test] โดยไม่ดึง tokio เข้า production binary ถ้าคุณไม่ต้องการที่นั่น