本文 AI 產出,尚未審核

Rust – 測試與文件

單元:文件測試(Doc Tests)


簡介

在 Rust 生態系統中,文件測試(Doc Tests) 是一項極具威力的功能。它不只讓我們在撰寫 API 說明時,同步驗證範例程式碼的正確性;同時也讓程式碼與文件保持 同步,避免因為程式變更而導致說明文件過時。

對於 初學者,Doc Tests 能夠在閱讀文件時即時看到可執行的範例,降低學習門檻。
對於 中階開發者,則提供了一個自動化的回歸測試機制,確保公開的 API 行為不會在重構或升級時意外改變。

本篇文章將深入說明 Doc Tests 的原理、使用方式與實務技巧,並提供多個可直接套用的範例,讓你在日常開發中即刻受益。


核心概念

1. Doc Tests 的基本運作

Rust 的文件註解支援 Markdown,而在 Markdown 內使用以三個反引號包住的程式碼區塊,且程式碼前面加上 /////! 的說明文字,就會被 cargo test 自動抽取,編譯並執行。

/// 計算兩個整數的和
///
/// # 範例
/// ```
/// let sum = add(2, 3);
/// assert_eq!(sum, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

上述範例在執行 cargo test 時,會產生一個測試函式,將程式碼區塊視為 測試程式,若 assert_eq! 成功則測試通過,否則測試失敗。

重點:Doc Tests 只會執行 程式碼區塊,不會執行說明文字或標題。

2. 為什麼要使用 Doc Tests?

好處 說明
文件即測試 同步驗證說明與實作,避免文件過時。
示範最佳實踐 新手可以直接從文件範例學會正確的使用方式。
自動化回歸 每次 cargo test 都會跑過所有文件範例,確保 API 行為不變。
零額外成本 不需要額外的測試框架,Rust 標準測試工具已支援。

3. Doc Test 的語法要點

語法 說明
```rust 必須在程式碼區塊的開頭指定語言為 rust,讓測試引擎知道要使用 Rust 編譯。
# 前置指令 # 開頭的程式碼不會出現在產生的文件中,但會在測試時編譯,常用於 引入依賴設定環境
ignore 標記 /// ```ignore 可讓特定範例不被測試,適用於只能在特定平台執行的程式碼。
no_run 標記 /// ```no_run 只編譯不執行,適合需要長時間或外部資源的範例。

4. 常見的程式碼區塊寫法

4.1 基本範例

/// 產生一個向量,並計算其長度
///
/// # 範例
/// ```
/// let v = vec![1, 2, 3];
/// assert_eq!(v.len(), 3);
/// ```
pub fn make_vec() -> Vec<i32> {
    vec![1, 2, 3]
}

4.2 使用 # 隱藏設定

/// 取得環境變數 `HOME` 的值,若不存在回傳空字串
///
/// # 範例
/// ```
/// # use std::env;
/// # let _ = env::set_var("HOME", "/tmp"); // 測試時自行設定
/// let home = get_home();
/// assert_eq!(home, "/tmp");
/// ```
pub fn get_home() -> String {
    std::env::var("HOME").unwrap_or_default()
}

4.3 ignore 標記:平台限定

/// 在 Windows 上取得檔案路徑的原始字串
///
/// # 範例
/// ```ignore
/// // 此範例只能在 Windows 執行,Linux 會編譯失敗
/// let path = r"C:\Program Files\Rust";
/// println!("{}", path);
/// ```
pub fn windows_path() -> &'static str {
    r"C:\Program Files\Rust"
}

4.4 no_run 標記:只編譯不執行

/// 建立一個 TCP 連線(僅示範編譯)
///
/// # 範例
/// ```no_run
/// use std::net::TcpStream;
/// let _stream = TcpStream::connect("example.com:80");
/// ```
pub fn dummy() {}

4.5 多行程式碼與斷言

/// 計算階乘(遞迴)
///
/// # 範例
/// ```
/// fn factorial(n: u64) -> u64 {
///     if n == 0 { 1 } else { n * factorial(n - 1) }
/// }
///
/// assert_eq!(factorial(5), 120);
/// assert_eq!(factorial(0), 1);
/// ```
pub fn factorial(n: u64) -> u64 {
    if n == 0 { 1 } else { n * factorial(n - 1) }
}

5. 進階技巧:自訂測試環境

有時候測試需要 外部資源(例如資料庫、檔案系統),可以利用 #[cfg(test)] 模組內的 setup 函式,配合 # 隱藏指令,讓 Doc Test 在執行前先做好準備。

/// 讀取暫存檔案的內容
///
/// # 範例
/// ```
/// # use std::fs::{self, File};
/// # use std::io::Write;
/// # // 建立測試用的暫存檔
/// # let mut tmp = File::create("tmp.txt").unwrap();
/// # write!(tmp, "hello").unwrap();
/// let content = read_tmp_file();
/// assert_eq!(content, "hello");
/// # // 清理
/// # fs::remove_file("tmp.txt").unwrap();
/// ```
pub fn read_tmp_file() -> String {
    std::fs::read_to_string("tmp.txt").unwrap_or_default()
}

常見陷阱與最佳實踐

陷阱 說明 最佳實踐
忘記加 rust 標籤 未指定語言會導致測試失敗或被當成純文字。 每個程式碼區塊必須以 ```rust 開頭。
使用外部 crate 卻未在測試環境引入 Doc Test 只會編譯當前 crate,缺少依賴會編譯錯誤。 Cargo.toml[dev-dependencies] 中加入測試需要的 crate。
斷言寫錯導致測試失敗 範例本身的錯誤會被當成測試失敗,讓人以為程式碼有 bug。 在撰寫範例前先在本地執行一次,確保 assert! 正確。
長時間執行的範例 Doc Test 預設會在 60 秒內完成,超時會失敗。 使用 no_runignore,或將長時間操作抽離成普通測試。
平台限定程式碼未標記 在非目標平台執行會直接編譯失敗。 使用 ignorecfg#[cfg(target_os = "windows")] 來限制。

其他實用建議

  1. 保持範例簡潔:只示範關鍵步驟,避免過多額外程式碼。
  2. 使用 # 隱藏設定:讓範例在文件中看起來乾淨,同時在測試時提供必要的環境。
  3. 結合 cargo test --doc:若只想跑文件測試,可加上 --doc 參數,提高執行速度。
  4. 將錯誤訊息寫在文件:在說明中加入「此範例會因為 X 錯誤」的註解,讓讀者更易理解。

實際應用場景

1. 開源函式庫的 API 文件

在發佈 crate 時,每個公開函式 都配上說明與可執行範例,讓使用者在 docs.rs 上直接看到可執行的程式碼。這不僅提升使用者體驗,也讓維護者在升級時自動檢測相容性。

2. 內部工具的使用說明

公司內部的 CLI 工具或服務,往往需要快速上手的文件。透過 Doc Tests,開發者可以在 README 中加入即時可執行的範例,避免「文件與實作不符」的情況。

3. 教學課程與部落格

寫教學文章時,將每段程式碼寫成 Doc Test,讀者只要 cargo test 就能驗證自己是否正確跟上步驟,提升學習成效。

4. 測試錯誤處理與 panic 行為

Doc Test 也能驗證 #[should_panic] 的情況,確保錯誤訊息符合預期。

/// 嘗試除以零會 panic
///
/// # 範例
/// ```should_panic
/// let _ = 1 / 0;
/// ```
pub fn divide_by_zero() {}

總結

  • 文件測試(Doc Tests) 是 Rust 生態中兼具文件與測試的雙重功能,讓說明永遠保持與程式碼同步。
  • 只要在 /////! 註解內使用帶有 rust 標籤的程式碼區塊,cargo test 就會自動抽取、編譯並執行。
  • 透過 # 隱藏設定、ignoreno_runshould_panic 等標記,我們可以靈活控制測試範圍與行為。
  • 常見陷阱包括忘記語言標籤、缺少測試依賴、平台限定程式碼未標記等,遵循 最佳實踐 能大幅降低失誤。
  • 在開源函式庫、內部工具、教學課程等多種場景下,Doc Tests 都能提供即時回饋、降低文件過時的風險,提升開發效率與程式碼品質。

實務建議:在每次新增或修改公開 API 時,務必同步更新對應的 Doc Test,並在 CI 中加入 cargo test --doc,讓文件測試成為發佈流程的一部份。如此一來,你的程式碼與說明將永遠保持一致,開發者與使用者都能受惠。