跳轉到

5.1 單元測試:內建的測試框架#

Rust 測試的優勢:內建、零設定#

Rust 內建測試框架——不需要選擇 JUnit、pytest、Jest,cargo test 就是全部。測試程式碼直接寫在原始碼裡,用 #[test] 標記:

// src/lib.rs(或 src/main.rs 內也可以)

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

#[cfg(test)]          // 這個模組只在測試時編譯
mod tests {
    use super::*;     // 引入上層的項目

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 4);
    }
}
cargo test
# running 1 test
# test tests::test_add ... FAILED
#
# failures:
#     tests::test_add
#
# assertion `left == right` failed
#   left: 5
#  right: 4

斷言三兄弟#

#[test]
fn test_assertions() {
    assert!(1 + 1 == 2);                    // 布林條件
    assert_eq!(add(2, 3), 5);               // 相等(值要實作 Debug 與 PartialEq)
    assert_ne!(add(2, 3), 4);               // 不相等

    assert!(v.is_empty(), "v 應該是空的,實際:{:?}", v);  // 自訂失敗訊息
}

注意:assert_eq! 的值需要 #[derive(Debug, PartialEq)]——忘記 derive 時編譯器會提醒你。

慣例:測試模組放哪#

Rust 的慣例是測試與被測程式碼放在同一個檔案的 #[cfg(test)] mod tests 中:

// src/lib.rs
pub fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 { None } else { Some(a / b) }
}

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

    #[test]
    fn divide_normal() {
        assert_eq!(divide(6.0, 3.0), Some(2.0));
    }

    #[test]
    fn divide_by_zero() {
        assert_eq!(divide(6.0, 0.0), None);   // 測「錯誤路徑」
    }
}

工程意義:

  • 測試就在被測程式碼旁邊,改程式碼時順手看到測試
  • 可以測試私有函式(同一個 crate 內看得見)
  • #[cfg(test)] 保證測試程式碼不會被編進正式產物

測試錯誤路徑:Result 與 should_panic#

// 測回傳 Result 的函式:直接用 ?,失敗會讓測試失敗
#[test]
fn read_ok() -> Result<(), std::io::Error> {
    let s = read_config()?;
    assert!(s.contains("port"));
    Ok(())
}

// 測 panic:應該崩潰的才對
#[test]
#[should_panic]
fn invalid_input() {
    process(-1);   // 預期 panic
}

cargo test 的常用操作#

cargo test                  # 跑全部測試
cargo test divide           # 只跑名稱含 divide 的測試
cargo test -- --nocapture   # 顯示測試中的 println! 輸出
cargo test -- --ignored     # 跑標記 #[ignore] 的測試

測試是平行執行的(多執行緒)——如果你的測試共用檔案或環境變數,要注意互相干擾(或加 -- --test-threads=1)。

本章小結#

  • 測試框架內建:#[test] + cargo test,零設定
  • 斷言:assert!、assert_eq!、assert_ne!(值需 derive Debug、PartialEq)
  • 慣例:測試放同檔案的 #[cfg(test)] mod tests,可測私有函式
  • 測 Result 直接用 ?;測 panic 用 #[should_panic]
  • 別忘了測錯誤路徑——錯誤路徑常常比成功路徑更需要測

想一想#

  1. 「測試與程式碼同檔案」和其他語言「tests/ 目錄」的取捨各是什麼?
  2. 為什麼錯誤路徑更需要測試?Rust 的 Option/Result 對此有什麼幫助?
  3. 測試平行執行會造成什麼問題?你的測試有哪些共用狀態?