Skip to content

Latest commit

 

History

History
192 lines (134 loc) · 7.21 KB

File metadata and controls

192 lines (134 loc) · 7.21 KB

16 — Testing & Debugging (workflow ที่ไม่หลงทาง)

TOC · Prev · Next

Keywords: testing, tooling, error handling, macros

อ่านแบบคน Python:

  • ถ้าอยากเอา “ภาพรวม” ก่อน: test คือกันชนเวลาคุณ refactor (ไม่ใช่ภาระ)
  • ถ้าอยาก “ลงมือทำ”: แตก logic ให้เป็นฟังก์ชันที่ pure-ish แล้วเขียน unit test ก่อนประกอบกับ I/O
  • ถ้าติด: เปิด 12-learning-playbook.md

บทนี้สอน test/debug แบบ practical สำหรับคนมาจาก Python: โครงสร้าง test ใน Rust, การอ่าน assertion error, การใช้ dbg!, และ workflow ที่เข้ากับ cargo test/clippy

แก่นของบทนี้:

  • แยก logic ออกจาก I/O แล้ว test เฉพาะส่วนที่ pure-ish
  • เขียน test ให้จับ “พฤติกรรม” (ไม่ใช่จับแค่บรรทัดโค้ด)
  • ใช้ dbg!/--nocapture เพื่อ debug ให้เร็ว แล้วค่อยลบ/เก็บให้สะอาด

1) Test ใน Rust อยู่ตรงไหน

Python (เทียบแนวคิด):

  • pytest มักรันไฟล์ test_*.py
  • หรือ unittest แบบ class-based

Rust มี 2 แบบหลัก:

  1. Unit tests
  • อยู่ไฟล์เดียวกับโค้ด
  • อยู่ใน mod tests และเปิดด้วย #[cfg(test)]
  1. Integration tests
  • อยู่ในโฟลเดอร์ tests/
  • เป็น crate แยก (เรียก public API ของ lib)

ตัวอย่าง unit test:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add_works() {
        assert_eq!(add(2, 3), 5);
    }
}

Output (example):

(no output — when tests pass, they usually produce no stdout)

2) Assert ที่ใช้บ่อย (และวิธีอ่านตอน fail)

พื้นฐาน:

  • assert!(cond)
  • assert_eq!(left, right)
  • assert_ne!(left, right)

เคล็ดลับ:

  • เวลา assert_eq! fail Rust มักแสดง diff ให้ → ช่วยไล่ bug เร็ว

หลักคิดแบบ Python:

  • assert_eq! เป็น “บรรทัดล็อกความหมาย” ของฟังก์ชัน มากกว่าเป็นแค่การเช็ก

3) Test ที่คาดว่าจะ panic (ใช้เท่าที่จำเป็น)

#[test]
#[should_panic]
fn it_panics() {
    panic!("boom");
}

Output (example):

(no output — this test passes because it panics as expected)

ข้อควรระวัง:

  • ถ้าเป็นโค้ดระดับแอป/ไลบรารีทั่วไป มักอยากให้คืน Result มากกว่า panic
  • ใช้ should_panic ได้ในเคสที่คุณกำลังทดสอบ guard rails หรือ invariants ภายใน

4) Debug แบบเร็ว: dbg! และ println!

Rust มี dbg! ที่พิมพ์ค่า + file:line แล้วคืนค่าเดิม

let x = dbg!(2 + 3);

Output (example):

[src/main.rs:LINE] 2 + 3 = 5

แนวคิด: LINE คือเลขบรรทัดจริงในเครื่องคุณ (dbg! พิมพ์ file:line อัตโนมัติ)

เหมาะมากเวลาอยู่ใน iterator chain แล้วอยากดูค่าระหว่างทาง:

let out: Vec<i32> = nums
    .iter()
    .map(|n| dbg!(n * 2))
    .collect();

Output (example):

[src/main.rs:LINE] n * 2 = 2
[src/main.rs:LINE] n * 2 = 4
[src/main.rs:LINE] n * 2 = 6

4.1 เห็น println! ตอน test ผ่านด้วย

โดย default ตอน test ผ่าน output อาจถูกซ่อนไว้

ถ้าต้องการเห็น output:

  • cargo test -- --nocapture

5) Pattern ที่ควรทำเป็นนิสัย: ทดสอบ function “pure-ish” ก่อน

คนมาจาก Python มักเริ่มจาก wiring ทั้งโปรแกรมแล้วค่อย debug

ใน Rust จะง่ายกว่า ถ้าคุณ:

  • แตก logic เป็น function ที่รับ input → คืน output (ไม่มี I/O)
  • เขียน unit test ครอบก่อน
  • แล้วค่อยประกอบเข้ากับ I/O

ตัวอย่างแนวคิด (โยงบท 09/11):

  • ทำ fn parse_config_str(text: &str) -> Result<Config, Error>
  • test ด้วย string literal ไม่ต้องอ่านไฟล์

6) cargo test workflow (คำสั่งที่ใช้จริง)

คำสั่งที่ใช้บ่อย:

  • cargo test
  • cargo test name_of_test (รันเฉพาะบางอัน)
  • cargo test -- --nocapture (เห็น stdout)

เวลาต้องไล่ bug ให้เร็ว:

  1. สร้าง test ที่ fail แบบ reproducible
  2. แก้โค้ดจน test ผ่าน
  3. ค่อย refactor (แล้วให้ test คุม)

7) Debug/Display และการอ่าน error

  • println!("{:?}", v) ต้องให้ type implement Debug
  • println!("{}", err) ต้องมี Display

ระหว่างพัฒนา:

  • ใส่ #[derive(Debug)] ช่วยได้มาก

แนวคิดสำคัญ:

  • error message ที่ดีช่วยลดเวลาทั้งของ dev และ user
  • เวลาใช้ Result ให้ใส่ context ให้ชัดว่า “พังตรงไหน” (เช่น file path)

8) แบบฝึกหัด (Exercises)

  1. เขียน fn normalize(s: &str) -> String ที่ trim + lowercase
  • เขียน unit tests อย่างน้อย 3 เคส (มีช่องว่าง/มีตัวพิมพ์ใหญ่/empty)
  1. เขียน fn parse_ints(xs: &[&str]) -> Result<Vec<i32>, std::num::ParseIntError>
  • เขียน test ทั้งเคสสำเร็จ และเคส error
  1. ทำ iterator chain แล้วแทรก dbg! เพื่อดูค่าระหว่าง map/filter

  2. (ท้าทาย) ทำ integration test แบบง่าย ๆ (ถ้าคุณมี Rust crate จริงใน repo นี้)

  • ถ้า repo นี้เป็นเอกสารอย่างเดียว ให้ข้ามข้อ 4