ข้ามไปยังเนื้อหา

Cargo & Testing

Cargo คือระบบ build อย่างเป็นทางการและ package manager ของ Rust โปรเจกต์ Rust ทุกโปรเจกต์คือ crate และ Cargo จัดการ dependency, build, test และเอกสารของตัวเอง

Terminal window
# สร้างโปรเจกต์ binary ใหม่
cargo new my_project
cd my_project
# สร้าง library crate ใหม่
cargo new my_lib --lib
# Build ในโหมด debug (คอมไพล์เร็ว ไม่ optimize)
cargo build
# Build ในโหมด release (คอมไพล์ช้า optimize แล้ว)
cargo build --release
# Build และรันในขั้นตอนเดียว
cargo run
# รันพร้อม argument
cargo run -- arg1 arg2
# ตรวจสอบ compile error โดยไม่สร้าง binary
cargo check
# เพิ่ม dependency
cargo add serde --features derive
# อัปเดต dependency เป็นเวอร์ชันที่เข้ากันได้ล่าสุด
cargo update
# Format โค้ด
cargo fmt
# รัน linter
cargo 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 project structure
[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 และ benchmark
pretty_assertions = "1"

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 ทั้งหมดด้วย:

Terminal window
cargo 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);
}
}
#[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 ผ่านเพราะเราคาดหวังมัน
}
}

ไฟล์ภายใต้ tests/ คือ integration test ซึ่งทดสอบ public API ของ crate ของคุณในฐานะ consumer ภายนอก

tests/integration_test.rs
use my_lib::multiply;
#[test]
fn integration_multiply() {
assert_eq!(multiply(6, 7), 42);
}

รัน integration test เฉพาะ:

Terminal window
cargo test --test integration_test

รัน test เฉพาะด้วยชื่อ:

Terminal window
cargo test test_divide

รัน test พร้อม output ที่มองเห็น:

Terminal window
cargo test -- --nocapture

สำหรับ 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 ถ้าคุณไม่ต้องการที่นั่น

#[cfg(test)] บน mod block ทำอะไร?
macro assertion ใดที่ตรวจสอบว่าสองค่าไม่เท่ากัน?
ควรวาง integration test ไว้ที่ไหนใน Cargo project?
ใช้ attribute ใดเพื่อทดสอบ async function ด้วย tokio?